CRUD of User Management, JWT Management Session, Protected Routes (#758)
This pull request introduces a comprehensive set of features aimed at enhancing user management, session handling using JWT, and implementing protected routes within the application. The changes focus on providing a seamless user experience while ensuring security and controlled access to resources.
|
|
@ -15,7 +15,8 @@
|
||||||
"actboy168.tasks",
|
"actboy168.tasks",
|
||||||
"GitHub.copilot",
|
"GitHub.copilot",
|
||||||
"ms-python.python",
|
"ms-python.python",
|
||||||
"eamodio.gitlens"
|
"eamodio.gitlens",
|
||||||
|
"GitHub.vscode-pull-request-github"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -3,32 +3,36 @@
|
||||||
{
|
{
|
||||||
"name": "LangChain Dev Container",
|
"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/python:1-3.10-bullseye",
|
||||||
|
|
||||||
|
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/aws-cli:1": {},
|
"ghcr.io/devcontainers/features/node": {},
|
||||||
"ghcr.io/devcontainers/features/docker-in-docker": {}
|
"ghcr.io/devcontainers-contrib/features/poetry": {}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 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": "make setup_devcontainer",
|
||||||
|
|
||||||
|
"containerEnv": {
|
||||||
|
"POETRY_VIRTUALENVS_IN_PROJECT": "true"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Configure tool-specific properties.
|
||||||
"customizations": {
|
"customizations": {
|
||||||
"vscode": {"extensions": [
|
"vscode": {"extensions": [
|
||||||
"actboy168.tasks",
|
"actboy168.tasks",
|
||||||
"GitHub.copilot",
|
"GitHub.copilot",
|
||||||
"ms-python.python",
|
"ms-python.python",
|
||||||
"sourcery.sourcery",
|
"sourcery.sourcery",
|
||||||
"eamodio.gitlens"
|
"eamodio.gitlens",
|
||||||
|
"ms-vscode.makefile-tools",
|
||||||
|
"GitHub.vscode-pull-request-github"
|
||||||
]}
|
]}
|
||||||
},
|
}
|
||||||
|
|
||||||
// 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": "poetry install"
|
|
||||||
|
|
||||||
// Configure tool-specific properties.
|
|
||||||
// "customizations": {},
|
|
||||||
|
|
||||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||||
// "remoteUser": "root"
|
// "remoteUser": "root"
|
||||||
|
|
|
||||||
24
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Set the default behavior, in case people don't have core.autocrlf set.
|
||||||
|
* text eol=lf
|
||||||
|
|
||||||
|
# Explicitly declare text files you want to always be normalized and converted
|
||||||
|
# to native line endings on checkout.
|
||||||
|
*.c text
|
||||||
|
*.h text
|
||||||
|
*.py text
|
||||||
|
*.js text
|
||||||
|
*.ts text
|
||||||
|
*.jsx text
|
||||||
|
*.md text
|
||||||
|
*.mdx text
|
||||||
|
|
||||||
|
# Declare files that will always have CRLF line endings on checkout.
|
||||||
|
*.sln text eol=crlf
|
||||||
|
|
||||||
|
# Denote all files that are truly binary and should not be modified.
|
||||||
|
*.png binary
|
||||||
|
*.jpg binary
|
||||||
|
*.mp4 binary
|
||||||
|
*.svg binary
|
||||||
|
*.ico binary
|
||||||
|
*.gif binary
|
||||||
12
Makefile
|
|
@ -34,13 +34,25 @@ lint:
|
||||||
install_frontend:
|
install_frontend:
|
||||||
cd src/frontend && npm install
|
cd src/frontend && npm install
|
||||||
|
|
||||||
|
install_frontendc:
|
||||||
|
cd src/frontend && rm -rf node_modules package-lock.json && npm install
|
||||||
|
|
||||||
run_frontend:
|
run_frontend:
|
||||||
cd src/frontend && npm start
|
cd src/frontend && npm start
|
||||||
|
|
||||||
|
setup_devcontainer:
|
||||||
|
make init
|
||||||
|
make build_frontend
|
||||||
|
poetry run langflow --path src/frontend/build
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
make install_frontend
|
make install_frontend
|
||||||
make run_frontend
|
make run_frontend
|
||||||
|
|
||||||
|
frontendc:
|
||||||
|
make install_frontendc
|
||||||
|
make run_frontend
|
||||||
|
|
||||||
install_backend:
|
install_backend:
|
||||||
poetry install
|
poetry install
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,33 @@
|
||||||
version: "3.4"
|
version: "3.4"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
backend:
|
backend:
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/app
|
- ./:/app
|
||||||
build:
|
build:
|
||||||
context: ./
|
context: ./
|
||||||
dockerfile: ./dev.Dockerfile
|
dockerfile: ./dev.Dockerfile
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
"sh",
|
"sh",
|
||||||
"-c",
|
"-c",
|
||||||
"pip install debugpy -t /tmp && python /tmp/debugpy --wait-for-client --listen 0.0.0.0:5678 -m uvicorn --factory src.backend.langflow.main:create_app --host 0.0.0.0 --port 7860 --reload",
|
"pip install debugpy -t /tmp && python /tmp/debugpy --wait-for-client --listen 0.0.0.0:5678 -m uvicorn --factory src.backend.langflow.main:create_app --host 0.0.0.0 --port 7860 --reload",
|
||||||
]
|
]
|
||||||
ports:
|
ports:
|
||||||
- 7860:7860
|
- 7860:7860
|
||||||
- 5678:5678
|
- 5678:5678
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
build:
|
build:
|
||||||
context: ./src/frontend
|
context: ./src/frontend
|
||||||
dockerfile: ./dev.Dockerfile
|
dockerfile: ./dev.Dockerfile
|
||||||
args:
|
args:
|
||||||
- BACKEND_URL=http://backend:7860
|
- BACKEND_URL=http://backend:7860
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
volumes:
|
volumes:
|
||||||
- ./src/frontend/public:/home/node/app/public
|
- ./src/frontend/public:/home/node/app/public
|
||||||
- ./src/frontend/src:/home/node/app/src
|
- ./src/frontend/src:/home/node/app/src
|
||||||
- ./src/frontend/package.json:/home/node/app/package.json
|
- ./src/frontend/package.json:/home/node/app/package.json
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,58 @@ import Admonition from '@theme/Admonition';
|
||||||
<p>
|
<p>
|
||||||
We appreciate your understanding as we polish our documentation – it may contain some rough edges. Share your feedback or report issues to help us improve! 🛠️📝
|
We appreciate your understanding as we polish our documentation – it may contain some rough edges. Share your feedback or report issues to help us improve! 🛠️📝
|
||||||
</p>
|
</p>
|
||||||
</Admonition>
|
</Admonition>
|
||||||
|
|
||||||
|
|
||||||
|
### BingSearchRun
|
||||||
|
|
||||||
|
Bing Search is a web search engine owned and operated by Microsoft. It provides search results for various types of content, including web pages, images, videos, and news articles. It uses a combination of algorithms and human editors to deliver search results to users.
|
||||||
|
|
||||||
|
**Params**
|
||||||
|
|
||||||
|
- **Api Wrapper:** A BingSearchAPIWrapper component that takes the search URL and a subscription key.
|
||||||
|
|
||||||
|
|
||||||
|
### Calculator
|
||||||
|
|
||||||
|
The calculator tool provides mathematical calculation capabilities to an agent by leveraging an LLMMathChain. It allows the agent to perform math when needed to answer questions.
|
||||||
|
|
||||||
|
**Params**
|
||||||
|
|
||||||
|
- **LLM:** Language Model to use in the calculation.
|
||||||
|
|
||||||
|
|
||||||
|
### GoogleSearchResults
|
||||||
|
|
||||||
|
A wrapper around Google Search. Useful for when the user needs to answer questions about with more control over the JSON data returned from the API. It returns the full JSON response configured based on the parameters passed to the API wrapper.
|
||||||
|
|
||||||
|
**Params**
|
||||||
|
|
||||||
|
- **Api Wrapper:** A GoogleSearchAPIWrapper with Google API key and CSE ID
|
||||||
|
|
||||||
|
|
||||||
|
### GoogleSearchRun
|
||||||
|
|
||||||
|
A quick wrapper around Google Search. It executes the search query and returns just the first result snippet from the highest-priority result type.
|
||||||
|
|
||||||
|
**Params**
|
||||||
|
|
||||||
|
- **Api Wrapper:** A GoogleSearchAPIWrapper with Google API key and CSE ID
|
||||||
|
|
||||||
|
|
||||||
|
### GoogleSerperRun
|
||||||
|
|
||||||
|
A low-cost Google Search API.
|
||||||
|
|
||||||
|
**Params**
|
||||||
|
|
||||||
|
- **Api Wrapper:** A GoogleSerperAPIWrapper component with API key and result keys
|
||||||
|
|
||||||
|
|
||||||
|
### InfoSQLDatabaseTool
|
||||||
|
|
||||||
|
Tool for getting metadata about a SQL database. The input to this tool is a comma-separated list of tables, and the output is the schema and sample rows for those tables. Example Input: `“table1`, `table2`, `table3”`.
|
||||||
|
|
||||||
|
**Params**
|
||||||
|
|
||||||
|
- **Db:** SQLDatabase to query.
|
||||||
BIN
docs/static/img/logo.svg
vendored
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
BIN
docs/static/img/new_langflow.gif
vendored
|
Before Width: | Height: | Size: 3.2 MiB After Width: | Height: | Size: 3.2 MiB |
BIN
docs/static/img/new_langflow2.gif
vendored
|
Before Width: | Height: | Size: 3.2 MiB After Width: | Height: | Size: 3.2 MiB |
BIN
docs/static/videos/langflow_api.mp4
vendored
BIN
docs/static/videos/langflow_build.mp4
vendored
BIN
docs/static/videos/langflow_collection.mp4
vendored
BIN
docs/static/videos/langflow_collection_example.mp4
vendored
BIN
docs/static/videos/langflow_fork.mp4
vendored
BIN
docs/static/videos/langflow_parameters.mp4
vendored
BIN
docs/static/videos/langflow_widget.mp4
vendored
|
Before Width: | Height: | Size: 2 MiB After Width: | Height: | Size: 2 MiB |
1551
package-lock.json
generated
152
poetry.lock
generated
|
|
@ -146,13 +146,13 @@ files = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alembic"
|
name = "alembic"
|
||||||
version = "1.11.2"
|
version = "1.11.3"
|
||||||
description = "A database migration tool for SQLAlchemy."
|
description = "A database migration tool for SQLAlchemy."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "alembic-1.11.2-py3-none-any.whl", hash = "sha256:7981ab0c4fad4fe1be0cf183aae17689fe394ff874fd2464adb774396faf0796"},
|
{file = "alembic-1.11.3-py3-none-any.whl", hash = "sha256:d6c96c2482740592777c400550a523bc7a9aada4e210cae2e733354ddae6f6f8"},
|
||||||
{file = "alembic-1.11.2.tar.gz", hash = "sha256:678f662130dc540dac12de0ea73de9f89caea9dbea138f60ef6263149bf84657"},
|
{file = "alembic-1.11.3.tar.gz", hash = "sha256:3db4ce81a9072e1b5aa44c2d202add24553182672a12daf21608d6f62a8f9cf9"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
|
@ -165,20 +165,20 @@ tz = ["python-dateutil"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anthropic"
|
name = "anthropic"
|
||||||
version = "0.3.9"
|
version = "0.3.10"
|
||||||
description = "Client library for the anthropic API"
|
description = "Client library for the anthropic API"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7,<4.0"
|
python-versions = ">=3.7,<4.0"
|
||||||
files = [
|
files = [
|
||||||
{file = "anthropic-0.3.9-py3-none-any.whl", hash = "sha256:23e8daf266c707faa0b85328ada03dcf885e62c2f236eb0159352e29b9c4e2e3"},
|
{file = "anthropic-0.3.10-py3-none-any.whl", hash = "sha256:95cd73168296a4f91a5899a28660991e044322cf94442d07b99901d4ca74acd6"},
|
||||||
{file = "anthropic-0.3.9.tar.gz", hash = "sha256:c19b75308c07cb1ecbea03ffcdde9ba68e5ca22a22479217712396385678bde3"},
|
{file = "anthropic-0.3.10.tar.gz", hash = "sha256:d1f66efc541fbff0ecfd37fd4d3690f9daaa748fc42d9ded5863a10815a5d97b"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
anyio = ">=3.5.0,<4"
|
anyio = ">=3.5.0,<4"
|
||||||
distro = ">=1.7.0,<2"
|
distro = ">=1.7.0,<2"
|
||||||
httpx = ">=0.23.0,<1"
|
httpx = ">=0.23.0,<1"
|
||||||
pydantic = ">=1.9.0,<2.0.0"
|
pydantic = ">=1.9.0,<3"
|
||||||
tokenizers = ">=0.13.0"
|
tokenizers = ">=0.13.0"
|
||||||
typing-extensions = ">=4.5,<5"
|
typing-extensions = ">=4.5,<5"
|
||||||
|
|
||||||
|
|
@ -792,13 +792,13 @@ sqlalchemy = ["sqlalchemy (>1.3.21,<2.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cohere"
|
name = "cohere"
|
||||||
version = "4.20.0"
|
version = "4.20.1"
|
||||||
description = ""
|
description = ""
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7,<4.0"
|
python-versions = ">=3.7,<4.0"
|
||||||
files = [
|
files = [
|
||||||
{file = "cohere-4.20.0-py3-none-any.whl", hash = "sha256:bebe4b1d21da0719aaa7db2cc180b5f3d0a802c19ad2d893d627971286e50497"},
|
{file = "cohere-4.20.1-py3-none-any.whl", hash = "sha256:4466c7abdbb168fe2893e5b5123882fe91691d0e3fe43ee254b411917f780dfc"},
|
||||||
{file = "cohere-4.20.0.tar.gz", hash = "sha256:a16e86981945c201bab67ad6d47e57f42a37bf2a774f6ac6b8b12233aae6409a"},
|
{file = "cohere-4.20.1.tar.gz", hash = "sha256:533e4a45b38dc338f8a27f24e098b652b4f5ed9f9fbb4719780d1a9fcd1af39e"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
|
@ -1630,13 +1630,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "google-api-python-client"
|
name = "google-api-python-client"
|
||||||
version = "2.96.0"
|
version = "2.97.0"
|
||||||
description = "Google API Client Library for Python"
|
description = "Google API Client Library for Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "google-api-python-client-2.96.0.tar.gz", hash = "sha256:f712373d03d338af57b9f5fe98c91f4b5baaa8765469b015bc623c4681c5bd51"},
|
{file = "google-api-python-client-2.97.0.tar.gz", hash = "sha256:48277291894876a1ca7ed4127e055e81f81e6343ced1b544a7200ae2c119dcd7"},
|
||||||
{file = "google_api_python_client-2.96.0-py2.py3-none-any.whl", hash = "sha256:38c2b61b10d15bb41ec8f89303e3837ec2d2c3e4e38de5800c05ee322492f937"},
|
{file = "google_api_python_client-2.97.0-py2.py3-none-any.whl", hash = "sha256:5215f4cd577753fc4192ccfbe0bb8b55d4bb5fd68fa6268ac5cf271b6305de31"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
|
@ -2979,13 +2979,13 @@ test = ["psutil", "pytest", "pytest-asyncio"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langsmith"
|
name = "langsmith"
|
||||||
version = "0.0.22"
|
version = "0.0.24"
|
||||||
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
|
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8.1,<4.0"
|
python-versions = ">=3.8.1,<4.0"
|
||||||
files = [
|
files = [
|
||||||
{file = "langsmith-0.0.22-py3-none-any.whl", hash = "sha256:1bc94a2e5bfa355ca15d9e658c2c2d04c8cc45c61892a1be08a7c3b40f2fd3f4"},
|
{file = "langsmith-0.0.24-py3-none-any.whl", hash = "sha256:f9f951d070aa1919123d700642aca9c781edfc8797a65ab1161aa12f89bed707"},
|
||||||
{file = "langsmith-0.0.22.tar.gz", hash = "sha256:5726c7841294db2a9e5863e20718878d16e28722bdaf3169a278ff3bda2f0be7"},
|
{file = "langsmith-0.0.24.tar.gz", hash = "sha256:9c066dd915752324490a735692997b0db0958f5dfc1e0a0dfbf752c6e62c7529"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
|
@ -3573,33 +3573,38 @@ dill = ">=0.3.7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mypy"
|
name = "mypy"
|
||||||
version = "1.5.0"
|
version = "1.5.1"
|
||||||
description = "Optional static typing for Python"
|
description = "Optional static typing for Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "mypy-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ad3109bec37cc33654de8db30fe8ff3a1bb57ea65144167d68185e6dced9868d"},
|
{file = "mypy-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f33592ddf9655a4894aef22d134de7393e95fcbdc2d15c1ab65828eee5c66c70"},
|
||||||
{file = "mypy-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b4ea3a0241cb005b0ccdbd318fb99619b21ae51bcf1660b95fc22e0e7d3ba4a1"},
|
{file = "mypy-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:258b22210a4a258ccd077426c7a181d789d1121aca6db73a83f79372f5569ae0"},
|
||||||
{file = "mypy-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fe816e26e676c1311b9e04fd576543b873576d39439f7c24c8e5c7728391ecf"},
|
{file = "mypy-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9ec1f695f0c25986e6f7f8778e5ce61659063268836a38c951200c57479cc12"},
|
||||||
{file = "mypy-1.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:42170e68adb1603ccdc55a30068f72bcfcde2ce650188e4c1b2a93018b826735"},
|
{file = "mypy-1.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:abed92d9c8f08643c7d831300b739562b0a6c9fcb028d211134fc9ab20ccad5d"},
|
||||||
{file = "mypy-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:d145b81a8214687cfc1f85c03663a5bbe736777410e5580e54d526e7e904f564"},
|
{file = "mypy-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a156e6390944c265eb56afa67c74c0636f10283429171018446b732f1a05af25"},
|
||||||
{file = "mypy-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c36011320e452eb30bec38b9fd3ba20569dc9545d7d4540d967f3ea1fab9c374"},
|
{file = "mypy-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ac9c21bfe7bc9f7f1b6fae441746e6a106e48fc9de530dea29e8cd37a2c0cc4"},
|
||||||
{file = "mypy-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f3940cf5845b2512b3ab95463198b0cdf87975dfd17fdcc6ce9709a9abe09e69"},
|
{file = "mypy-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51cb1323064b1099e177098cb939eab2da42fea5d818d40113957ec954fc85f4"},
|
||||||
{file = "mypy-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9166186c498170e1ff478a7f540846b2169243feb95bc228d39a67a1a450cdc6"},
|
{file = "mypy-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:596fae69f2bfcb7305808c75c00f81fe2829b6236eadda536f00610ac5ec2243"},
|
||||||
{file = "mypy-1.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:725b57a19b7408ef66a0fd9db59b5d3e528922250fb56e50bded27fea9ff28f0"},
|
{file = "mypy-1.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:32cb59609b0534f0bd67faebb6e022fe534bdb0e2ecab4290d683d248be1b275"},
|
||||||
{file = "mypy-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:eec5c927aa4b3e8b4781840f1550079969926d0a22ce38075f6cfcf4b13e3eb4"},
|
{file = "mypy-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:159aa9acb16086b79bbb0016145034a1a05360626046a929f84579ce1666b315"},
|
||||||
{file = "mypy-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79c520aa24f21852206b5ff2cf746dc13020113aa73fa55af504635a96e62718"},
|
{file = "mypy-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6b0e77db9ff4fda74de7df13f30016a0a663928d669c9f2c057048ba44f09bb"},
|
||||||
{file = "mypy-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:769ddb6bfe55c2bd9c7d6d7020885a5ea14289619db7ee650e06b1ef0852c6f4"},
|
{file = "mypy-1.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26f71b535dfc158a71264e6dc805a9f8d2e60b67215ca0bfa26e2e1aa4d4d373"},
|
||||||
{file = "mypy-1.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbf18f8db7e5f060d61c91e334d3b96d6bb624ddc9ee8a1cde407b737acbca2c"},
|
{file = "mypy-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc3a600f749b1008cc75e02b6fb3d4db8dbcca2d733030fe7a3b3502902f161"},
|
||||||
{file = "mypy-1.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a2500ad063413bc873ae102cf655bf49889e0763b260a3a7cf544a0cbbf7e70a"},
|
{file = "mypy-1.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:26fb32e4d4afa205b24bf645eddfbb36a1e17e995c5c99d6d00edb24b693406a"},
|
||||||
{file = "mypy-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:84cf9f7d8a8a22bb6a36444480f4cbf089c917a4179fbf7eea003ea931944a7f"},
|
{file = "mypy-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:82cb6193de9bbb3844bab4c7cf80e6227d5225cc7625b068a06d005d861ad5f1"},
|
||||||
{file = "mypy-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a551ed0fc02455fe2c1fb0145160df8336b90ab80224739627b15ebe2b45e9dc"},
|
{file = "mypy-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4a465ea2ca12804d5b34bb056be3a29dc47aea5973b892d0417c6a10a40b2d65"},
|
||||||
{file = "mypy-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:372fd97293ed0076d52695849f59acbbb8461c4ab447858cdaeaf734a396d823"},
|
{file = "mypy-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9fece120dbb041771a63eb95e4896791386fe287fefb2837258925b8326d6160"},
|
||||||
{file = "mypy-1.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8a7444d6fcac7e2585b10abb91ad900a576da7af8f5cffffbff6065d9115813"},
|
{file = "mypy-1.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d28ddc3e3dfeab553e743e532fb95b4e6afad51d4706dd22f28e1e5e664828d2"},
|
||||||
{file = "mypy-1.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:35b13335c6c46a386577a51f3d38b2b5d14aa619e9633bb756bd77205e4bd09f"},
|
{file = "mypy-1.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:57b10c56016adce71fba6bc6e9fd45d8083f74361f629390c556738565af8eeb"},
|
||||||
{file = "mypy-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:2c9d570f53908cbea326ad8f96028a673b814d9dca7515bf71d95fa662c3eb6f"},
|
{file = "mypy-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:ff0cedc84184115202475bbb46dd99f8dcb87fe24d5d0ddfc0fe6b8575c88d2f"},
|
||||||
{file = "mypy-1.5.0-py3-none-any.whl", hash = "sha256:69b32d0dedd211b80f1b7435644e1ef83033a2af2ac65adcdc87c38db68a86be"},
|
{file = "mypy-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8f772942d372c8cbac575be99f9cc9d9fb3bd95c8bc2de6c01411e2c84ebca8a"},
|
||||||
{file = "mypy-1.5.0.tar.gz", hash = "sha256:f3460f34b3839b9bc84ee3ed65076eb827cd99ed13ed08d723f9083cada4a212"},
|
{file = "mypy-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5d627124700b92b6bbaa99f27cbe615c8ea7b3402960f6372ea7d65faf376c14"},
|
||||||
|
{file = "mypy-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:361da43c4f5a96173220eb53340ace68cda81845cd88218f8862dfb0adc8cddb"},
|
||||||
|
{file = "mypy-1.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:330857f9507c24de5c5724235e66858f8364a0693894342485e543f5b07c8693"},
|
||||||
|
{file = "mypy-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:c543214ffdd422623e9fedd0869166c2f16affe4ba37463975043ef7d2ea8770"},
|
||||||
|
{file = "mypy-1.5.1-py3-none-any.whl", hash = "sha256:f757063a83970d67c444f6e01d9550a7402322af3557ce7630d3c957386fa8f5"},
|
||||||
|
{file = "mypy-1.5.1.tar.gz", hash = "sha256:b031b9601f1060bf1281feab89697324726ba0c0bae9d7cd7ab4b690940f0b92"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
|
@ -5890,29 +5895,42 @@ python-versions = "*"
|
||||||
files = [
|
files = [
|
||||||
{file = "safetensors-0.3.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b6a66989075c2891d743153e8ba9ca84ee7232c8539704488f454199b8b8f84d"},
|
{file = "safetensors-0.3.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:b6a66989075c2891d743153e8ba9ca84ee7232c8539704488f454199b8b8f84d"},
|
||||||
{file = "safetensors-0.3.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:670d6bc3a3b377278ce2971fa7c36ebc0a35041c4ea23b9df750a39380800195"},
|
{file = "safetensors-0.3.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:670d6bc3a3b377278ce2971fa7c36ebc0a35041c4ea23b9df750a39380800195"},
|
||||||
|
{file = "safetensors-0.3.2-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:564f42838721925b5313ae864ba6caa6f4c80a9fbe63cf24310c3be98ab013cd"},
|
||||||
{file = "safetensors-0.3.2-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:7f80af7e4ab3188daaff12d43d078da3017a90d732d38d7af4eb08b6ca2198a5"},
|
{file = "safetensors-0.3.2-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:7f80af7e4ab3188daaff12d43d078da3017a90d732d38d7af4eb08b6ca2198a5"},
|
||||||
|
{file = "safetensors-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec30d78f20f1235b252d59cbb9755beb35a1fde8c24c89b3c98e6a1804cfd432"},
|
||||||
|
{file = "safetensors-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16063d94d8f600768d3c331b1e97964b1bf3772e19710105fe24ec5a6af63770"},
|
||||||
{file = "safetensors-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb44e140bf2aeda98d9dde669dbec15f7b77f96a9274469b91a6cf4bcc5ec3b"},
|
{file = "safetensors-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb44e140bf2aeda98d9dde669dbec15f7b77f96a9274469b91a6cf4bcc5ec3b"},
|
||||||
{file = "safetensors-0.3.2-cp310-cp310-win32.whl", hash = "sha256:2961c1243fd0da46aa6a1c835305cc4595486f8ac64632a604d0eb5f2de76175"},
|
{file = "safetensors-0.3.2-cp310-cp310-win32.whl", hash = "sha256:2961c1243fd0da46aa6a1c835305cc4595486f8ac64632a604d0eb5f2de76175"},
|
||||||
{file = "safetensors-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c813920482c337d1424d306e1b05824a38e3ef94303748a0a287dea7a8c4f805"},
|
{file = "safetensors-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c813920482c337d1424d306e1b05824a38e3ef94303748a0a287dea7a8c4f805"},
|
||||||
{file = "safetensors-0.3.2-cp311-cp311-macosx_10_11_universal2.whl", hash = "sha256:707df34bd9b9047e97332136ad98e57028faeccdb9cfe1c3b52aba5964cc24bf"},
|
{file = "safetensors-0.3.2-cp311-cp311-macosx_10_11_universal2.whl", hash = "sha256:707df34bd9b9047e97332136ad98e57028faeccdb9cfe1c3b52aba5964cc24bf"},
|
||||||
{file = "safetensors-0.3.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:becc5bb85b2947eae20ed23b407ebfd5277d9a560f90381fe2c42e6c043677ba"},
|
{file = "safetensors-0.3.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:becc5bb85b2947eae20ed23b407ebfd5277d9a560f90381fe2c42e6c043677ba"},
|
||||||
|
{file = "safetensors-0.3.2-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:30a75707be5cc9686490bde14b9a371cede4af53244ea72b340cfbabfffdf58a"},
|
||||||
{file = "safetensors-0.3.2-cp311-cp311-macosx_13_0_universal2.whl", hash = "sha256:54ad6af663e15e2b99e2ea3280981b7514485df72ba6d014dc22dae7ba6a5e6c"},
|
{file = "safetensors-0.3.2-cp311-cp311-macosx_13_0_universal2.whl", hash = "sha256:54ad6af663e15e2b99e2ea3280981b7514485df72ba6d014dc22dae7ba6a5e6c"},
|
||||||
|
{file = "safetensors-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37764b3197656ef507a266c453e909a3477dabc795962b38e3ad28226f53153b"},
|
||||||
|
{file = "safetensors-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4939067736783acd8391d83cd97d6c202f94181951ce697d519f9746381b6a39"},
|
||||||
{file = "safetensors-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada0fac127ff8fb04834da5c6d85a8077e6a1c9180a11251d96f8068db922a17"},
|
{file = "safetensors-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada0fac127ff8fb04834da5c6d85a8077e6a1c9180a11251d96f8068db922a17"},
|
||||||
{file = "safetensors-0.3.2-cp311-cp311-win32.whl", hash = "sha256:155b82dbe2b0ebff18cde3f76b42b6d9470296e92561ef1a282004d449fa2b4c"},
|
{file = "safetensors-0.3.2-cp311-cp311-win32.whl", hash = "sha256:155b82dbe2b0ebff18cde3f76b42b6d9470296e92561ef1a282004d449fa2b4c"},
|
||||||
{file = "safetensors-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:a86428d196959619ce90197731be9391b5098b35100a7228ef4643957648f7f5"},
|
{file = "safetensors-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:a86428d196959619ce90197731be9391b5098b35100a7228ef4643957648f7f5"},
|
||||||
{file = "safetensors-0.3.2-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:c1f8ab41ed735c5b581f451fd15d9602ff51aa88044bfa933c5fa4b1d0c644d1"},
|
{file = "safetensors-0.3.2-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:c1f8ab41ed735c5b581f451fd15d9602ff51aa88044bfa933c5fa4b1d0c644d1"},
|
||||||
{file = "safetensors-0.3.2-cp37-cp37m-macosx_13_0_x86_64.whl", hash = "sha256:bc9cfb3c9ea2aec89685b4d656f9f2296f0f0d67ecf2bebf950870e3be89b3db"},
|
{file = "safetensors-0.3.2-cp37-cp37m-macosx_13_0_x86_64.whl", hash = "sha256:bc9cfb3c9ea2aec89685b4d656f9f2296f0f0d67ecf2bebf950870e3be89b3db"},
|
||||||
|
{file = "safetensors-0.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ace5d471e3d78e0d93f952707d808b5ab5eac77ddb034ceb702e602e9acf2be9"},
|
||||||
|
{file = "safetensors-0.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de3e20a388b444381bcda1a3193cce51825ddca277e4cf3ed1fe8d9b2d5722cd"},
|
||||||
{file = "safetensors-0.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d7d70d48585fe8df00725aa788f2e64fd24a4c9ae07cd6be34f6859d0f89a9c"},
|
{file = "safetensors-0.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d7d70d48585fe8df00725aa788f2e64fd24a4c9ae07cd6be34f6859d0f89a9c"},
|
||||||
{file = "safetensors-0.3.2-cp37-cp37m-win32.whl", hash = "sha256:6ff59bc90cdc857f68b1023be9085fda6202bbe7f2fd67d06af8f976d6adcc10"},
|
{file = "safetensors-0.3.2-cp37-cp37m-win32.whl", hash = "sha256:6ff59bc90cdc857f68b1023be9085fda6202bbe7f2fd67d06af8f976d6adcc10"},
|
||||||
{file = "safetensors-0.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8b05c93da15fa911763a89281906ca333ed800ab0ef1c7ce53317aa1a2322f19"},
|
{file = "safetensors-0.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8b05c93da15fa911763a89281906ca333ed800ab0ef1c7ce53317aa1a2322f19"},
|
||||||
{file = "safetensors-0.3.2-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:8969cfd9e8d904e8d3c67c989e1bd9a95e3cc8980d4f95e4dcd43c299bb94253"},
|
{file = "safetensors-0.3.2-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:8969cfd9e8d904e8d3c67c989e1bd9a95e3cc8980d4f95e4dcd43c299bb94253"},
|
||||||
{file = "safetensors-0.3.2-cp38-cp38-macosx_13_0_x86_64.whl", hash = "sha256:f54148ac027556eb02187e9bc1556c4d916c99ca3cb34ca36a7d304d675035c1"},
|
{file = "safetensors-0.3.2-cp38-cp38-macosx_13_0_x86_64.whl", hash = "sha256:f54148ac027556eb02187e9bc1556c4d916c99ca3cb34ca36a7d304d675035c1"},
|
||||||
|
{file = "safetensors-0.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:caec25fedbcf73f66c9261984f07885680f71417fc173f52279276c7f8a5edd3"},
|
||||||
|
{file = "safetensors-0.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:50224a1d99927ccf3b75e27c3d412f7043280431ab100b4f08aad470c37cf99a"},
|
||||||
{file = "safetensors-0.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa98f49e95f02eb750d32c4947e7d5aa43883149ebd0414920866446525b70f0"},
|
{file = "safetensors-0.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa98f49e95f02eb750d32c4947e7d5aa43883149ebd0414920866446525b70f0"},
|
||||||
{file = "safetensors-0.3.2-cp38-cp38-win32.whl", hash = "sha256:33409df5e28a83dc5cc5547a3ac17c0f1b13a1847b1eb3bc4b3be0df9915171e"},
|
{file = "safetensors-0.3.2-cp38-cp38-win32.whl", hash = "sha256:33409df5e28a83dc5cc5547a3ac17c0f1b13a1847b1eb3bc4b3be0df9915171e"},
|
||||||
{file = "safetensors-0.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:e04a7cbbb3856159ab99e3adb14521544f65fcb8548cce773a1435a0f8d78d27"},
|
{file = "safetensors-0.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:e04a7cbbb3856159ab99e3adb14521544f65fcb8548cce773a1435a0f8d78d27"},
|
||||||
{file = "safetensors-0.3.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:7c864cf5dcbfb608c5378f83319c60cc9c97263343b57c02756b7613cd5ab4dd"},
|
{file = "safetensors-0.3.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:7c864cf5dcbfb608c5378f83319c60cc9c97263343b57c02756b7613cd5ab4dd"},
|
||||||
{file = "safetensors-0.3.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:14e8c19d6dc51d4f70ee33c46aff04c8ba3f95812e74daf8036c24bc86e75cae"},
|
{file = "safetensors-0.3.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:14e8c19d6dc51d4f70ee33c46aff04c8ba3f95812e74daf8036c24bc86e75cae"},
|
||||||
|
{file = "safetensors-0.3.2-cp39-cp39-macosx_13_0_arm64.whl", hash = "sha256:042a60f633c3c7009fdf6a7c182b165cb7283649d2a1e9c7a4a1c23454bd9a5b"},
|
||||||
{file = "safetensors-0.3.2-cp39-cp39-macosx_13_0_x86_64.whl", hash = "sha256:fafd95e5ef41e8f312e2a32b7031f7b9b2a621b255f867b221f94bb2e9f51ae8"},
|
{file = "safetensors-0.3.2-cp39-cp39-macosx_13_0_x86_64.whl", hash = "sha256:fafd95e5ef41e8f312e2a32b7031f7b9b2a621b255f867b221f94bb2e9f51ae8"},
|
||||||
|
{file = "safetensors-0.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ed77cf358abce2307f03634694e0b2a29822e322a1623e0b1aa4b41e871bf8b"},
|
||||||
|
{file = "safetensors-0.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d344e8b2681a33aafc197c90b0def3229b3317d749531c72fa6259d0caa5c8c"},
|
||||||
{file = "safetensors-0.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87ff0024ef2e5722a79af24688ce4a430f70601d0cf712a744105ed4b8f67ba5"},
|
{file = "safetensors-0.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87ff0024ef2e5722a79af24688ce4a430f70601d0cf712a744105ed4b8f67ba5"},
|
||||||
{file = "safetensors-0.3.2-cp39-cp39-win32.whl", hash = "sha256:827af9478b78977248ba93e2fd97ea307fb63f463f80cef4824460f8c2542a52"},
|
{file = "safetensors-0.3.2-cp39-cp39-win32.whl", hash = "sha256:827af9478b78977248ba93e2fd97ea307fb63f463f80cef4824460f8c2542a52"},
|
||||||
{file = "safetensors-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9b09f27c456efa301f98681ea14b12f81f2637889f6336223ccab71e42c34541"},
|
{file = "safetensors-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9b09f27c456efa301f98681ea14b12f81f2637889f6336223ccab71e42c34541"},
|
||||||
|
|
@ -6113,18 +6131,18 @@ files = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "setuptools"
|
name = "setuptools"
|
||||||
version = "68.0.0"
|
version = "68.1.0"
|
||||||
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"},
|
{file = "setuptools-68.1.0-py3-none-any.whl", hash = "sha256:e13e1b0bc760e9b0127eda042845999b2f913e12437046e663b833aa96d89715"},
|
||||||
{file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"},
|
{file = "setuptools-68.1.0.tar.gz", hash = "sha256:d59c97e7b774979a5ccb96388efc9eb65518004537e85d52e81eaee89ab6dd91"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
|
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
|
||||||
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
||||||
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
|
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -6514,13 +6532,13 @@ doc = ["reno", "sphinx", "tornado (>=4.5)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "textual"
|
name = "textual"
|
||||||
version = "0.32.0"
|
version = "0.33.0"
|
||||||
description = "Modern Text User Interface framework"
|
description = "Modern Text User Interface framework"
|
||||||
optional = true
|
optional = true
|
||||||
python-versions = ">=3.7,<4.0"
|
python-versions = ">=3.7,<4.0"
|
||||||
files = [
|
files = [
|
||||||
{file = "textual-0.32.0-py3-none-any.whl", hash = "sha256:81fc68406c8806bc864e2f035874a868b4ff0cf466289dce5f7b31869949383b"},
|
{file = "textual-0.33.0-py3-none-any.whl", hash = "sha256:698a093add0fd21c786232bcacde9ff427a9d5dc9ea5deca93437d9453e4ead1"},
|
||||||
{file = "textual-0.32.0.tar.gz", hash = "sha256:f7b6683bc18faee6fd3c47cfbad43fbf8273c5fecc12230d52ce5ee089021327"},
|
{file = "textual-0.33.0.tar.gz", hash = "sha256:e0a98b1d9c4458c5bb4269c65d0a7f3e926f197411242d2f8faf80183d47a728"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
|
@ -6939,6 +6957,17 @@ files = [
|
||||||
{file = "types_cachetools-5.3.0.6-py3-none-any.whl", hash = "sha256:f7f8a25bfe306f2e6bc2ad0a2f949d9e72f2d91036d509c36d3810bf728bc6e1"},
|
{file = "types_cachetools-5.3.0.6-py3-none-any.whl", hash = "sha256:f7f8a25bfe306f2e6bc2ad0a2f949d9e72f2d91036d509c36d3810bf728bc6e1"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "types-passlib"
|
||||||
|
version = "1.7.7.13"
|
||||||
|
description = "Typing stubs for passlib"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "types-passlib-1.7.7.13.tar.gz", hash = "sha256:f152639f1f2103d7f59a56e2aec5f9398a75a80830991d0d68aac5c2b9c32a77"},
|
||||||
|
{file = "types_passlib-1.7.7.13-py3-none-any.whl", hash = "sha256:414b5ee9c88313357c9261cfcf816509b1e8e4673f0796bd61e9ef249f6fe076"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "types-pillow"
|
name = "types-pillow"
|
||||||
version = "9.5.0.6"
|
version = "9.5.0.6"
|
||||||
|
|
@ -6950,6 +6979,31 @@ files = [
|
||||||
{file = "types_Pillow-9.5.0.6-py3-none-any.whl", hash = "sha256:1d238abaa9d529b04941d805b7f4d3f7df30702bb14521ec507617f117406fb4"},
|
{file = "types_Pillow-9.5.0.6-py3-none-any.whl", hash = "sha256:1d238abaa9d529b04941d805b7f4d3f7df30702bb14521ec507617f117406fb4"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "types-pyasn1"
|
||||||
|
version = "0.4.0.6"
|
||||||
|
description = "Typing stubs for pyasn1"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "types-pyasn1-0.4.0.6.tar.gz", hash = "sha256:8f1965d0b79152f9d1efc89f9aa9a8cdda7cd28b2619df6737c095cbedeff98b"},
|
||||||
|
{file = "types_pyasn1-0.4.0.6-py3-none-any.whl", hash = "sha256:dd5fc818864e63a66cd714be0a7a59a493f4a81b87ee9ac978c41f1eaa9a0cef"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "types-python-jose"
|
||||||
|
version = "3.3.4.8"
|
||||||
|
description = "Typing stubs for python-jose"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "types-python-jose-3.3.4.8.tar.gz", hash = "sha256:3c316675c3cee059ccb9aff87358254344915239fa7f19cee2787155a7db14ac"},
|
||||||
|
{file = "types_python_jose-3.3.4.8-py3-none-any.whl", hash = "sha256:95592273443b45dc5cc88f7c56aa5a97725428753fb738b794e63ccb4904954e"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
types-pyasn1 = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "types-pytz"
|
name = "types-pytz"
|
||||||
version = "2023.3.0.1"
|
version = "2023.3.0.1"
|
||||||
|
|
@ -7693,4 +7747,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"]
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = ">=3.9,<3.11"
|
python-versions = ">=3.9,<3.11"
|
||||||
content-hash = "0f7d3f932eb130e7f3b6d234fc36e403ef409d63cfc825ba9f4c470203425ad2"
|
content-hash = "8ad605e7ea30f2819dbc03eac6c2e67576a98d1efa4890912414a7568fc27441"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "langflow"
|
name = "langflow"
|
||||||
version = "0.4.7"
|
version = "0.4.11"
|
||||||
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 = [
|
||||||
|
|
@ -97,6 +97,8 @@ pandas-stubs = "^2.0.0.230412"
|
||||||
types-pillow = "^9.5.0.2"
|
types-pillow = "^9.5.0.2"
|
||||||
types-appdirs = "^1.4.3.5"
|
types-appdirs = "^1.4.3.5"
|
||||||
types-pyyaml = "^6.0.12.8"
|
types-pyyaml = "^6.0.12.8"
|
||||||
|
types-python-jose = "^3.3.4.8"
|
||||||
|
types-passlib = "^1.7.7.13"
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.extras]
|
[tool.poetry.extras]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import httpx
|
import httpx
|
||||||
|
from langflow.services.manager import initialize_settings_manager
|
||||||
from langflow.services.utils import get_settings_manager
|
from langflow.services.utils import get_settings_manager
|
||||||
from langflow.utils.util import get_number_of_workers
|
from langflow.utils.util import get_number_of_workers
|
||||||
from multiprocess import Process # type: ignore
|
from multiprocess import Process # type: ignore
|
||||||
|
|
@ -30,6 +31,7 @@ def update_settings(
|
||||||
"""Update the settings from a config file."""
|
"""Update the settings from a config file."""
|
||||||
|
|
||||||
# Check for database_url in the environment variables
|
# Check for database_url in the environment variables
|
||||||
|
initialize_settings_manager()
|
||||||
settings_manager = get_settings_manager()
|
settings_manager = get_settings_manager()
|
||||||
if config:
|
if config:
|
||||||
logger.debug(f"Loading settings from {config}")
|
logger.debug(f"Loading settings from {config}")
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from typing import Annotated, Optional
|
from typing import Annotated, Optional, Union
|
||||||
|
|
||||||
from langflow.services.cache.utils import save_uploaded_file
|
from langflow.services.cache.utils import save_uploaded_file
|
||||||
from langflow.services.database.models.flow import Flow
|
from langflow.services.database.models.flow import Flow
|
||||||
|
|
@ -44,15 +44,21 @@ def get_all():
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Building custom components from {settings_manager.settings.COMPONENTS_PATH}"
|
f"Building custom components from {settings_manager.settings.COMPONENTS_PATH}"
|
||||||
)
|
)
|
||||||
custom_component_dicts = [
|
|
||||||
build_langchain_custom_component_list_from_path(str(path))
|
custom_component_dicts = []
|
||||||
for path in settings_manager.settings.COMPONENTS_PATH
|
processed_paths = []
|
||||||
]
|
for path in settings_manager.settings.COMPONENTS_PATH:
|
||||||
|
if str(path) in processed_paths:
|
||||||
|
continue
|
||||||
|
custom_component_dict = build_langchain_custom_component_list_from_path(
|
||||||
|
str(path)
|
||||||
|
)
|
||||||
|
custom_component_dicts.append(custom_component_dict)
|
||||||
|
processed_paths.append(str(path))
|
||||||
|
|
||||||
logger.info(f"Loading {len(custom_component_dicts)} category(ies)")
|
logger.info(f"Loading {len(custom_component_dicts)} category(ies)")
|
||||||
for custom_component_dict in custom_component_dicts:
|
for custom_component_dict in custom_component_dicts:
|
||||||
# custom_component_dict is a dict of dicts
|
# custom_component_dict is a dict of dicts
|
||||||
if not custom_component_dict:
|
|
||||||
continue
|
|
||||||
category = list(custom_component_dict.keys())[0]
|
category = list(custom_component_dict.keys())[0]
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Loading {len(custom_component_dict[category])} component(s) from category {category}"
|
f"Loading {len(custom_component_dict[category])} component(s) from category {category}"
|
||||||
|
|
@ -75,6 +81,7 @@ async def process_flow(
|
||||||
inputs: Optional[dict] = None,
|
inputs: Optional[dict] = None,
|
||||||
tweaks: Optional[dict] = None,
|
tweaks: Optional[dict] = None,
|
||||||
clear_cache: Annotated[bool, Body(embed=True)] = False, # noqa: F821
|
clear_cache: Annotated[bool, Body(embed=True)] = False, # noqa: F821
|
||||||
|
session_id: Annotated[Union[None, str], Body(embed=True)] = None, # noqa: F821
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
|
|
@ -94,10 +101,10 @@ async def process_flow(
|
||||||
graph_data = process_tweaks(graph_data, tweaks)
|
graph_data = process_tweaks(graph_data, tweaks)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(f"Error processing tweaks: {exc}")
|
logger.error(f"Error processing tweaks: {exc}")
|
||||||
response = process_graph_cached(graph_data, inputs, clear_cache)
|
response, session_id = process_graph_cached(
|
||||||
return ProcessResponse(
|
graph_data, inputs, clear_cache, session_id
|
||||||
result=response,
|
|
||||||
)
|
)
|
||||||
|
return ProcessResponse(result=response, session_id=session_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Log stack trace
|
# Log stack trace
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ class ProcessResponse(BaseModel):
|
||||||
"""Process response schema."""
|
"""Process response schema."""
|
||||||
|
|
||||||
result: dict
|
result: dict
|
||||||
|
session_id: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class ChatMessage(BaseModel):
|
class ChatMessage(BaseModel):
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,8 @@ from fastapi.security import OAuth2PasswordBearer
|
||||||
from fastapi import Depends, HTTPException, status
|
from fastapi import Depends, HTTPException, status
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
from langflow.services.utils import get_settings_manager
|
from langflow.services.utils import get_settings_manager, get_session
|
||||||
|
|
||||||
from langflow.services.utils import get_session
|
|
||||||
from langflow.database.models.user import (
|
from langflow.database.models.user import (
|
||||||
User,
|
User,
|
||||||
get_user_by_id,
|
get_user_by_id,
|
||||||
|
|
@ -83,18 +82,40 @@ def create_token(data: dict, expires_delta: timedelta):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_user_longterm_token(
|
def create_super_user(db: Session = Depends(get_session)) -> User:
|
||||||
user_id: UUID, db: Session = Depends(get_session), update_last_login: bool = False
|
settings_manager = get_settings_manager()
|
||||||
) -> dict:
|
|
||||||
|
super_user = get_user_by_username(db, settings_manager.settings.FIRST_SUPERUSER)
|
||||||
|
|
||||||
|
if not super_user:
|
||||||
|
super_user = User(
|
||||||
|
username=settings_manager.settings.FIRST_SUPERUSER,
|
||||||
|
password=get_password_hash(
|
||||||
|
settings_manager.settings.FIRST_SUPERUSER_PASSWORD
|
||||||
|
),
|
||||||
|
is_superuser=True,
|
||||||
|
is_active=True,
|
||||||
|
last_login_at=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
db.add(super_user)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(super_user)
|
||||||
|
|
||||||
|
return super_user
|
||||||
|
|
||||||
|
|
||||||
|
def create_user_longterm_token(db: Session = Depends(get_session)) -> dict:
|
||||||
|
super_user = create_super_user(db)
|
||||||
|
|
||||||
access_token_expires_longterm = timedelta(days=365)
|
access_token_expires_longterm = timedelta(days=365)
|
||||||
access_token = create_token(
|
access_token = create_token(
|
||||||
data={"sub": str(user_id)},
|
data={"sub": str(super_user.id)},
|
||||||
expires_delta=access_token_expires_longterm,
|
expires_delta=access_token_expires_longterm,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update: last_login_at
|
# Update: last_login_at
|
||||||
if update_last_login:
|
update_user_last_login_at(super_user.id, db)
|
||||||
update_user_last_login_at(user_id, db)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"access_token": access_token,
|
"access_token": access_token,
|
||||||
|
|
@ -103,6 +124,23 @@ def create_user_longterm_token(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def create_user_api_key(user_id: UUID) -> dict:
|
||||||
|
access_token = create_token(
|
||||||
|
data={"sub": str(user_id), "role": "api_key"},
|
||||||
|
expires_delta=timedelta(days=365 * 2),
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"api_key": access_token}
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_id_from_token(token: str) -> UUID:
|
||||||
|
try:
|
||||||
|
user_id = jwt.get_unverified_claims(token)["sub"]
|
||||||
|
return UUID(user_id)
|
||||||
|
except (KeyError, JWTError, ValueError):
|
||||||
|
return UUID(int=0)
|
||||||
|
|
||||||
|
|
||||||
def create_user_tokens(
|
def create_user_tokens(
|
||||||
user_id: UUID, db: Session = Depends(get_session), update_last_login: bool = False
|
user_id: UUID, db: Session = Depends(get_session), update_last_login: bool = False
|
||||||
) -> dict:
|
) -> dict:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
from langflow import CustomComponent
|
||||||
|
from typing import Optional
|
||||||
|
from langchain.prompts import SystemMessagePromptTemplate
|
||||||
|
from langchain.tools import Tool
|
||||||
|
from langchain.schema.memory import BaseMemory
|
||||||
|
from langchain.chat_models import ChatOpenAI
|
||||||
|
|
||||||
|
from langchain.agents.agent import AgentExecutor
|
||||||
|
from langchain.agents.openai_functions_agent.base import OpenAIFunctionsAgent
|
||||||
|
from langchain.memory.token_buffer import ConversationTokenBufferMemory
|
||||||
|
from langchain.prompts.chat import MessagesPlaceholder
|
||||||
|
from langchain.agents.agent_toolkits.conversational_retrieval.openai_functions import (
|
||||||
|
_get_default_system_message,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ConversationalAgent(CustomComponent):
|
||||||
|
display_name: str = "OpenAI Conversational Agent"
|
||||||
|
description: str = "Conversational Agent that can use OpenAI's function calling API"
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
openai_function_models = [
|
||||||
|
"gpt-3.5-turbo-0613",
|
||||||
|
"gpt-3.5-turbo-16k-0613",
|
||||||
|
"gpt-4-0613",
|
||||||
|
"gpt-4-32k-0613",
|
||||||
|
]
|
||||||
|
return {
|
||||||
|
"tools": {"is_list": True, "display_name": "Tools"},
|
||||||
|
"memory": {"display_name": "Memory"},
|
||||||
|
"system_message": {"display_name": "System Message"},
|
||||||
|
"max_token_limit": {"display_name": "Max Token Limit"},
|
||||||
|
"model_name": {
|
||||||
|
"display_name": "Model Name",
|
||||||
|
"options": openai_function_models,
|
||||||
|
"value": openai_function_models[0],
|
||||||
|
},
|
||||||
|
"code": {"show": False},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(
|
||||||
|
self,
|
||||||
|
model_name: str,
|
||||||
|
openai_api_key: str,
|
||||||
|
openai_api_base: str,
|
||||||
|
tools: Tool,
|
||||||
|
memory: Optional[BaseMemory] = None,
|
||||||
|
system_message: Optional[SystemMessagePromptTemplate] = None,
|
||||||
|
max_token_limit: int = 2000,
|
||||||
|
) -> AgentExecutor:
|
||||||
|
llm = ChatOpenAI(
|
||||||
|
model=model_name,
|
||||||
|
openai_api_key=openai_api_key,
|
||||||
|
openai_api_base=openai_api_base,
|
||||||
|
)
|
||||||
|
if not memory:
|
||||||
|
memory_key = "chat_history"
|
||||||
|
memory = ConversationTokenBufferMemory(
|
||||||
|
memory_key=memory_key,
|
||||||
|
return_messages=True,
|
||||||
|
output_key="output",
|
||||||
|
llm=llm,
|
||||||
|
max_token_limit=max_token_limit,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
memory_key = memory.memory_key # type: ignore
|
||||||
|
|
||||||
|
_system_message = system_message or _get_default_system_message()
|
||||||
|
prompt = OpenAIFunctionsAgent.create_prompt(
|
||||||
|
system_message=_system_message, # type: ignore
|
||||||
|
extra_prompt_messages=[MessagesPlaceholder(variable_name=memory_key)],
|
||||||
|
)
|
||||||
|
agent = OpenAIFunctionsAgent(
|
||||||
|
llm=llm, tools=tools, prompt=prompt # type: ignore
|
||||||
|
)
|
||||||
|
return AgentExecutor(
|
||||||
|
agent=agent,
|
||||||
|
tools=tools, # type: ignore
|
||||||
|
memory=memory,
|
||||||
|
verbose=True,
|
||||||
|
return_intermediate_steps=True,
|
||||||
|
)
|
||||||
0
src/backend/langflow/components/agents/__init__.py
Normal file
|
|
@ -3,6 +3,10 @@ from typing import Any, Union
|
||||||
from langflow.interface.utils import extract_input_variables_from_prompt
|
from langflow.interface.utils import extract_input_variables_from_prompt
|
||||||
|
|
||||||
|
|
||||||
|
class UnbuiltObject:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def validate_prompt(prompt: str):
|
def validate_prompt(prompt: str):
|
||||||
"""Validate prompt."""
|
"""Validate prompt."""
|
||||||
if extract_input_variables_from_prompt(prompt):
|
if extract_input_variables_from_prompt(prompt):
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import ast
|
import ast
|
||||||
|
from langflow.graph.utils import UnbuiltObject
|
||||||
from langflow.interface.initialize import loading
|
from langflow.interface.initialize import loading
|
||||||
from langflow.interface.listing import lazy_load_dict
|
from langflow.interface.listing import lazy_load_dict
|
||||||
from langflow.utils.constants import DIRECT_TYPES
|
from langflow.utils.constants import DIRECT_TYPES
|
||||||
|
|
@ -22,7 +23,7 @@ class Vertex:
|
||||||
self.edges: List["Edge"] = []
|
self.edges: List["Edge"] = []
|
||||||
self.base_type: Optional[str] = base_type
|
self.base_type: Optional[str] = base_type
|
||||||
self._parse_data()
|
self._parse_data()
|
||||||
self._built_object = None
|
self._built_object = UnbuiltObject()
|
||||||
self._built = False
|
self._built = False
|
||||||
self.artifacts: Dict[str, Any] = {}
|
self.artifacts: Dict[str, Any] = {}
|
||||||
|
|
||||||
|
|
@ -245,8 +246,14 @@ class Vertex:
|
||||||
"""
|
"""
|
||||||
Checks if the built object is None and raises a ValueError if so.
|
Checks if the built object is None and raises a ValueError if so.
|
||||||
"""
|
"""
|
||||||
if self._built_object is None:
|
if isinstance(self._built_object, UnbuiltObject):
|
||||||
raise ValueError(f"Node type {self.vertex_type} not found")
|
raise ValueError(f"{self.vertex_type}: {self._built_object_repr()}")
|
||||||
|
elif self._built_object is None:
|
||||||
|
message = f"{self.vertex_type} returned None."
|
||||||
|
if self.base_type == "custom_components":
|
||||||
|
message += " Make sure your build method returns a component."
|
||||||
|
|
||||||
|
raise ValueError(message)
|
||||||
|
|
||||||
def build(self, force: bool = False) -> Any:
|
def build(self, force: bool = False) -> Any:
|
||||||
if not self._built or force:
|
if not self._built or force:
|
||||||
|
|
|
||||||
|
|
@ -226,7 +226,12 @@ class PromptVertex(Vertex):
|
||||||
# so the prompt format doesn't break
|
# so the prompt format doesn't break
|
||||||
artifacts.pop("handle_keys", None)
|
artifacts.pop("handle_keys", None)
|
||||||
try:
|
try:
|
||||||
template = self._built_object.template
|
if not hasattr(self._built_object, "template") and hasattr(
|
||||||
|
self._built_object, "prompt"
|
||||||
|
):
|
||||||
|
template = self._built_object.prompt.template
|
||||||
|
else:
|
||||||
|
template = self._built_object.template
|
||||||
for key, value in artifacts.items():
|
for key, value in artifacts.items():
|
||||||
if value:
|
if value:
|
||||||
replace_key = "{" + key + "}"
|
replace_key = "{" + key + "}"
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,13 @@ from langchain.text_splitter import TextSplitter
|
||||||
from langchain.tools import Tool
|
from langchain.tools import Tool
|
||||||
from langchain.vectorstores.base import VectorStore
|
from langchain.vectorstores.base import VectorStore
|
||||||
from langchain.schema import BaseOutputParser
|
from langchain.schema import BaseOutputParser
|
||||||
|
from langchain.schema.memory import BaseMemory
|
||||||
|
from langchain.memory.chat_memory import BaseChatMemory
|
||||||
|
from langchain.agents.agent import AgentExecutor
|
||||||
|
|
||||||
LANGCHAIN_BASE_TYPES = {
|
LANGCHAIN_BASE_TYPES = {
|
||||||
"Chain": Chain,
|
"Chain": Chain,
|
||||||
|
"AgentExecutor": AgentExecutor,
|
||||||
"Tool": Tool,
|
"Tool": Tool,
|
||||||
"BaseLLM": BaseLLM,
|
"BaseLLM": BaseLLM,
|
||||||
"PromptTemplate": PromptTemplate,
|
"PromptTemplate": PromptTemplate,
|
||||||
|
|
@ -22,6 +25,8 @@ LANGCHAIN_BASE_TYPES = {
|
||||||
"Embeddings": Embeddings,
|
"Embeddings": Embeddings,
|
||||||
"BaseRetriever": BaseRetriever,
|
"BaseRetriever": BaseRetriever,
|
||||||
"BaseOutputParser": BaseOutputParser,
|
"BaseOutputParser": BaseOutputParser,
|
||||||
|
"BaseMemory": BaseMemory,
|
||||||
|
"BaseChatMemory": BaseChatMemory,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Langchain base types plus Python base types
|
# Langchain base types plus Python base types
|
||||||
|
|
|
||||||
|
|
@ -51,8 +51,8 @@ class CustomComponent(Component, extra=Extra.allow):
|
||||||
|
|
||||||
for type_hint in TYPE_HINT_LIST:
|
for type_hint in TYPE_HINT_LIST:
|
||||||
if reader._is_type_hint_used_in_args(
|
if reader._is_type_hint_used_in_args(
|
||||||
"Optional", code
|
type_hint, code
|
||||||
) and not reader._is_type_hint_imported("Optional", code):
|
) and not reader._is_type_hint_imported(type_hint, code):
|
||||||
error_detail = {
|
error_detail = {
|
||||||
"error": "Type hint Error",
|
"error": "Type hint Error",
|
||||||
"traceback": f"Type hint '{type_hint}' is used but not imported in the code.",
|
"traceback": f"Type hint '{type_hint}' is used but not imported in the code.",
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,9 @@ def handle_partial_variables(prompt, format_kwargs: Dict):
|
||||||
}
|
}
|
||||||
# Remove handle_keys otherwise LangChain raises an error
|
# Remove handle_keys otherwise LangChain raises an error
|
||||||
partial_variables.pop("handle_keys", None)
|
partial_variables.pop("handle_keys", None)
|
||||||
return prompt.partial(**partial_variables)
|
if partial_variables and hasattr(prompt, "partial"):
|
||||||
|
return prompt.partial(**partial_variables)
|
||||||
|
return prompt
|
||||||
|
|
||||||
|
|
||||||
def handle_variable(params: Dict, input_variable: str, format_kwargs: Dict):
|
def handle_variable(params: Dict, input_variable: str, format_kwargs: Dict):
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from typing import Any, Dict, Tuple
|
||||||
from langflow.services.cache.utils import memoize_dict
|
from langflow.services.cache.utils import memoize_dict
|
||||||
from langflow.graph import Graph
|
from langflow.graph import Graph
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
|
|
@ -15,7 +16,7 @@ def build_langchain_object_with_caching(data_graph):
|
||||||
|
|
||||||
|
|
||||||
@memoize_dict(maxsize=10)
|
@memoize_dict(maxsize=10)
|
||||||
def build_sorted_vertices_with_caching(data_graph):
|
def build_sorted_vertices_with_caching(data_graph) -> Tuple[Any, Dict]:
|
||||||
"""
|
"""
|
||||||
Build langchain object from data_graph.
|
Build langchain object from data_graph.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from fastapi.responses import FileResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
from langflow.api import router
|
from langflow.api import router
|
||||||
from langflow.routers import login, users, health
|
from langflow.routers import api_key, login, users, health
|
||||||
|
|
||||||
from langflow.interface.utils import setup_llm_caching
|
from langflow.interface.utils import setup_llm_caching
|
||||||
from langflow.services.database.utils import initialize_database
|
from langflow.services.database.utils import initialize_database
|
||||||
|
|
@ -32,6 +32,7 @@ def create_app():
|
||||||
)
|
)
|
||||||
|
|
||||||
app.include_router(login.router)
|
app.include_router(login.router)
|
||||||
|
app.include_router(api_key.router)
|
||||||
app.include_router(users.router)
|
app.include_router(users.router)
|
||||||
app.include_router(health.router)
|
app.include_router(health.router)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ from langflow.api.v1.callback import (
|
||||||
)
|
)
|
||||||
from langflow.processing.process import fix_memory_inputs, format_actions
|
from langflow.processing.process import fix_memory_inputs, format_actions
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
|
from langchain.agents.agent import AgentExecutor
|
||||||
|
|
||||||
|
|
||||||
async def get_result_and_steps(langchain_object, inputs: Union[dict, str], **kwargs):
|
async def get_result_and_steps(langchain_object, inputs: Union[dict, str], **kwargs):
|
||||||
|
|
@ -20,7 +21,8 @@ async def get_result_and_steps(langchain_object, inputs: Union[dict, str], **kwa
|
||||||
# to display intermediate steps
|
# to display intermediate steps
|
||||||
langchain_object.return_intermediate_steps = True
|
langchain_object.return_intermediate_steps = True
|
||||||
try:
|
try:
|
||||||
fix_memory_inputs(langchain_object)
|
if not isinstance(langchain_object, AgentExecutor):
|
||||||
|
fix_memory_inputs(langchain_object)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(f"Error fixing memory inputs: {exc}")
|
logger.error(f"Error fixing memory inputs: {exc}")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,39 +85,55 @@ def get_input_str_if_only_one_input(inputs: dict) -> Optional[str]:
|
||||||
return list(inputs.values())[0] if len(inputs) == 1 else None
|
return list(inputs.values())[0] if len(inputs) == 1 else None
|
||||||
|
|
||||||
|
|
||||||
def process_graph_cached(
|
def get_build_result(data_graph, session_id):
|
||||||
data_graph: Dict[str, Any], inputs: Optional[dict] = None, clear_cache=False
|
# If session_id is provided, load the langchain_object from the session
|
||||||
):
|
# using build_sorted_vertices_with_caching.get_result_by_session_id
|
||||||
"""
|
# if it returns something different than None, return it
|
||||||
Process graph by extracting input variables and replacing ZeroShotPrompt
|
# otherwise, build the graph and return the result
|
||||||
with PromptTemplate,then run the graph and return the result and thought.
|
if session_id:
|
||||||
"""
|
logger.debug(f"Loading LangChain object from session {session_id}")
|
||||||
# Load langchain object
|
result = build_sorted_vertices_with_caching.get_result_by_session_id(session_id)
|
||||||
|
if result is not None:
|
||||||
|
logger.debug("Loaded LangChain object")
|
||||||
|
return result
|
||||||
|
|
||||||
|
logger.debug("Building langchain object")
|
||||||
|
return build_sorted_vertices_with_caching(data_graph)
|
||||||
|
|
||||||
|
|
||||||
|
def clear_caches_if_needed(clear_cache: bool):
|
||||||
if clear_cache:
|
if clear_cache:
|
||||||
build_sorted_vertices_with_caching.clear_cache()
|
build_sorted_vertices_with_caching.clear_cache()
|
||||||
logger.debug("Cleared cache")
|
logger.debug("Cleared cache")
|
||||||
langchain_object, artifacts = build_sorted_vertices_with_caching(data_graph)
|
|
||||||
logger.debug("Loaded LangChain object")
|
|
||||||
if inputs is None:
|
|
||||||
inputs = {}
|
|
||||||
|
|
||||||
# Add artifacts to inputs
|
|
||||||
# artifacts can be documents loaded when building
|
def load_langchain_object(
|
||||||
# the flow
|
data_graph: Dict[str, Any], session_id: str
|
||||||
for (
|
) -> Tuple[Union[Chain, VectorStore], Dict[str, Any], str]:
|
||||||
key,
|
langchain_object, artifacts = get_build_result(data_graph, session_id)
|
||||||
value,
|
session_id = build_sorted_vertices_with_caching.hash
|
||||||
) in artifacts.items():
|
logger.debug("Loaded LangChain object")
|
||||||
if key not in inputs or not inputs[key]:
|
|
||||||
inputs[key] = value
|
|
||||||
|
|
||||||
if langchain_object is None:
|
if langchain_object is None:
|
||||||
# Raise user facing error
|
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"There was an error loading the langchain_object. Please, check all the nodes and try again."
|
"There was an error loading the langchain_object. Please, check all the nodes and try again."
|
||||||
)
|
)
|
||||||
|
|
||||||
# Generate result and thought
|
return langchain_object, artifacts, session_id
|
||||||
|
|
||||||
|
|
||||||
|
def process_inputs(inputs: Optional[dict], artifacts: Dict[str, Any]) -> dict:
|
||||||
|
if inputs is None:
|
||||||
|
inputs = {}
|
||||||
|
|
||||||
|
for key, value in artifacts.items():
|
||||||
|
if key not in inputs or not inputs[key]:
|
||||||
|
inputs[key] = value
|
||||||
|
|
||||||
|
return inputs
|
||||||
|
|
||||||
|
|
||||||
|
def generate_result(langchain_object: Union[Chain, VectorStore], inputs: dict):
|
||||||
if isinstance(langchain_object, Chain):
|
if isinstance(langchain_object, Chain):
|
||||||
if inputs is None:
|
if inputs is None:
|
||||||
raise ValueError("Inputs must be provided for a Chain")
|
raise ValueError("Inputs must be provided for a Chain")
|
||||||
|
|
@ -130,9 +146,28 @@ def process_graph_cached(
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Unknown langchain_object type: {type(langchain_object).__name__}"
|
f"Unknown langchain_object type: {type(langchain_object).__name__}"
|
||||||
)
|
)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def process_graph_cached(
|
||||||
|
data_graph: Dict[str, Any],
|
||||||
|
inputs: Optional[dict] = None,
|
||||||
|
clear_cache=False,
|
||||||
|
session_id=None,
|
||||||
|
) -> Tuple[Any, str]:
|
||||||
|
clear_caches_if_needed(clear_cache)
|
||||||
|
# If session_id is provided, load the langchain_object from the session
|
||||||
|
# else build the graph and return the result and the new session_id
|
||||||
|
langchain_object, artifacts, session_id = load_langchain_object(
|
||||||
|
data_graph, session_id
|
||||||
|
)
|
||||||
|
processed_inputs = process_inputs(inputs, artifacts)
|
||||||
|
result = generate_result(langchain_object, processed_inputs)
|
||||||
|
|
||||||
|
return result, session_id
|
||||||
|
|
||||||
|
|
||||||
def load_flow_from_json(
|
def load_flow_from_json(
|
||||||
flow: Union[Path, str, dict], tweaks: Optional[dict] = None, build=True
|
flow: Union[Path, str, dict], tweaks: Optional[dict] = None, build=True
|
||||||
):
|
):
|
||||||
|
|
|
||||||
49
src/backend/langflow/routers/api_key.py
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
|
||||||
|
router = APIRouter(tags=["APIKey"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/api_key/{user_id}")
|
||||||
|
def get_api_key(user_id: str):
|
||||||
|
return {
|
||||||
|
"total_count": 3,
|
||||||
|
"user_id": user_id,
|
||||||
|
"api_keys": [
|
||||||
|
{
|
||||||
|
"id": "4425707e-cce4-4d1b-a54e-bd2632064657",
|
||||||
|
"api_key": "lf-...abcd",
|
||||||
|
"name": "my api_key name - 01",
|
||||||
|
"created_at": "2023-08-15T19:28:40.019613",
|
||||||
|
"last_used_at": "2023-08-16T18:38:20.875210",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6fb7282b-9f2e-4efe-9bda-0c3d8f899473",
|
||||||
|
"api_key": "lf-...abcd",
|
||||||
|
"name": "my api_key name - 02",
|
||||||
|
"created_at": "2023-08-15T19:41:30.077942",
|
||||||
|
"last_used_at": "2023-08-15T19:45:32.067899",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c55f3b32-4920-42b6-a5cd-698b4251806e",
|
||||||
|
"api_key": "lf-...abcd",
|
||||||
|
"name": "my api_key name - 03",
|
||||||
|
"created_at": "2023-08-15T20:29:40.577808",
|
||||||
|
"last_used_at": "2023-08-15T20:29:40.577816",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/api_key/{user_id}")
|
||||||
|
def create_api_key(user_id: str):
|
||||||
|
return {
|
||||||
|
"user_id": user_id,
|
||||||
|
"name": "my api-key 01",
|
||||||
|
"api_key": "lf-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1YTBmODM1ZS0yMTQxLTQ2YWItYmQ4NS0yMWEzMjQ1MTE2ZDAiLCJleHAiOjE2OTIyMTUwMTN9.c_s0ZPRtjSI9yUrhi8ACIwyXf0feRLYfaeIZEbRVKQg",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/api_key/{api_key_id}")
|
||||||
|
def delete_api_key(api_key_id: str):
|
||||||
|
return {"detail": "API Key deleted"}
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
from uuid import UUID
|
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
from fastapi.security import OAuth2PasswordRequestForm
|
from fastapi.security import OAuth2PasswordRequestForm
|
||||||
|
|
@ -38,8 +37,7 @@ async def auto_login(db: Session = Depends(get_session)):
|
||||||
settings_manager = get_settings_manager()
|
settings_manager = get_settings_manager()
|
||||||
|
|
||||||
if settings_manager.settings.AUTO_LOGIN:
|
if settings_manager.settings.AUTO_LOGIN:
|
||||||
user_id = UUID("3fa85f64-5717-4562-b3fc-2c963f66afa6")
|
return create_user_longterm_token(db)
|
||||||
return create_user_longterm_token(user_id, db)
|
|
||||||
|
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
|
|
||||||
|
|
@ -115,14 +115,13 @@ def add_super_user_for_testing_purposes_delete_me_before_merge_into_dev(
|
||||||
"""
|
"""
|
||||||
new_user = User(
|
new_user = User(
|
||||||
username="superuser",
|
username="superuser",
|
||||||
password="12345",
|
password=get_password_hash("12345"),
|
||||||
is_active=True,
|
is_active=True,
|
||||||
is_superuser=True,
|
is_superuser=True,
|
||||||
last_login_at=None,
|
last_login_at=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
new_user.password = get_password_hash(new_user.password)
|
|
||||||
db.add(new_user)
|
db.add(new_user)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(new_user)
|
db.refresh(new_user)
|
||||||
|
|
|
||||||
16
src/backend/langflow/services/cache/utils.py
vendored
|
|
@ -30,6 +30,7 @@ def create_cache_folder(func):
|
||||||
|
|
||||||
def memoize_dict(maxsize=128):
|
def memoize_dict(maxsize=128):
|
||||||
cache = OrderedDict()
|
cache = OrderedDict()
|
||||||
|
hash_to_key = {} # Mapping from hash to cache key
|
||||||
|
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
|
|
@ -39,16 +40,29 @@ def memoize_dict(maxsize=128):
|
||||||
if key not in cache:
|
if key not in cache:
|
||||||
result = func(*args, **kwargs)
|
result = func(*args, **kwargs)
|
||||||
cache[key] = result
|
cache[key] = result
|
||||||
|
hash_to_key[hashed] = key # Store the mapping
|
||||||
if len(cache) > maxsize:
|
if len(cache) > maxsize:
|
||||||
cache.popitem(last=False)
|
oldest_key = next(iter(cache))
|
||||||
|
oldest_hash = oldest_key[1]
|
||||||
|
del cache[oldest_key]
|
||||||
|
del hash_to_key[oldest_hash]
|
||||||
else:
|
else:
|
||||||
result = cache[key]
|
result = cache[key]
|
||||||
|
|
||||||
|
wrapper.session_id = hashed # Store hash in the wrapper
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def clear_cache():
|
def clear_cache():
|
||||||
cache.clear()
|
cache.clear()
|
||||||
|
hash_to_key.clear()
|
||||||
|
|
||||||
|
def get_result_by_session_id(session_id):
|
||||||
|
key = hash_to_key.get(session_id)
|
||||||
|
return cache.get(key) if key is not None else None
|
||||||
|
|
||||||
wrapper.clear_cache = clear_cache # type: ignore
|
wrapper.clear_cache = clear_cache # type: ignore
|
||||||
|
wrapper.get_result_by_session_id = get_result_by_session_id # type: ignore
|
||||||
|
wrapper.hash = None
|
||||||
wrapper.cache = cache # type: ignore
|
wrapper.cache = cache # type: ignore
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ class ServiceManager:
|
||||||
"""
|
"""
|
||||||
Registers a new factory.
|
Registers a new factory.
|
||||||
"""
|
"""
|
||||||
self.factories[service_factory.service_class.name] = service_factory
|
if service_factory.service_class.name not in self.factories:
|
||||||
|
self.factories[service_factory.service_class.name] = service_factory
|
||||||
|
|
||||||
def get(self, service_name: ServiceType):
|
def get(self, service_name: ServiceType):
|
||||||
"""
|
"""
|
||||||
|
|
@ -85,3 +86,12 @@ def initialize_services():
|
||||||
service_manager.register_factory(database_factory.DatabaseManagerFactory())
|
service_manager.register_factory(database_factory.DatabaseManagerFactory())
|
||||||
service_manager.register_factory(cache_factory.CacheManagerFactory())
|
service_manager.register_factory(cache_factory.CacheManagerFactory())
|
||||||
service_manager.register_factory(chat_factory.ChatManagerFactory())
|
service_manager.register_factory(chat_factory.ChatManagerFactory())
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_settings_manager():
|
||||||
|
"""
|
||||||
|
Initialize the settings manager.
|
||||||
|
"""
|
||||||
|
from langflow.services.settings import factory as settings_factory
|
||||||
|
|
||||||
|
service_manager.register_factory(settings_factory.SettingsManagerFactory())
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import contextlib
|
import contextlib
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
from shutil import copy2
|
||||||
import secrets
|
import secrets
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
@ -9,7 +10,8 @@ import yaml
|
||||||
from pydantic import BaseSettings, root_validator, validator
|
from pydantic import BaseSettings, root_validator, validator
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
|
|
||||||
BASE_COMPONENTS_PATH = str(Path(__file__).parent / "components")
|
# BASE_COMPONENTS_PATH = str(Path(__file__).parent / "components")
|
||||||
|
BASE_COMPONENTS_PATH = str(Path(__file__).parent.parent.parent / "components")
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
|
|
@ -30,6 +32,9 @@ class Settings(BaseSettings):
|
||||||
OUTPUT_PARSERS: dict = {}
|
OUTPUT_PARSERS: dict = {}
|
||||||
CUSTOM_COMPONENTS: dict = {}
|
CUSTOM_COMPONENTS: dict = {}
|
||||||
|
|
||||||
|
# Define the default LANGFLOW_DIR
|
||||||
|
CONFIG_DIR: Optional[str] = None
|
||||||
|
|
||||||
DEV: bool = False
|
DEV: bool = False
|
||||||
DATABASE_URL: Optional[str] = None
|
DATABASE_URL: Optional[str] = None
|
||||||
CACHE: str = "InMemoryCache"
|
CACHE: str = "InMemoryCache"
|
||||||
|
|
@ -41,12 +46,44 @@ class Settings(BaseSettings):
|
||||||
ALGORITHM: str = "HS256"
|
ALGORITHM: str = "HS256"
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60
|
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60
|
||||||
REFRESH_TOKEN_EXPIRE_MINUTES: int = 70
|
REFRESH_TOKEN_EXPIRE_MINUTES: int = 70
|
||||||
|
|
||||||
|
# API Key to execute /process endpoint
|
||||||
|
API_KEY_SECRET_KEY: Optional[
|
||||||
|
str
|
||||||
|
] = "b82818e0ad4ff76615c5721ee21004b07d84cd9b87ba4d9cb42374da134b841a"
|
||||||
|
API_KEY_ALGORITHM: str = "HS256"
|
||||||
|
|
||||||
# If AUTO_LOGIN = True
|
# If AUTO_LOGIN = True
|
||||||
# > The application does not request login and logs in automatically as a super user.
|
# > The application does not request login and logs in automatically as a super user.
|
||||||
AUTO_LOGIN: bool = True
|
AUTO_LOGIN: bool = True
|
||||||
|
FIRST_SUPERUSER: str = "langflow"
|
||||||
|
FIRST_SUPERUSER_PASSWORD: str = "langflow"
|
||||||
|
|
||||||
|
@validator("CONFIG_DIR", pre=True, allow_reuse=True)
|
||||||
|
def set_langflow_dir(cls, value):
|
||||||
|
if not value:
|
||||||
|
import appdirs
|
||||||
|
|
||||||
|
# Define the app name and author
|
||||||
|
app_name = "langflow"
|
||||||
|
app_author = "logspace"
|
||||||
|
|
||||||
|
# Get the cache directory for the application
|
||||||
|
cache_dir = appdirs.user_cache_dir(app_name, app_author)
|
||||||
|
|
||||||
|
# Create a .langflow directory inside the cache directory
|
||||||
|
value = Path(cache_dir)
|
||||||
|
value.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
if isinstance(value, str):
|
||||||
|
value = Path(value)
|
||||||
|
if not value.exists():
|
||||||
|
value.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
return str(value)
|
||||||
|
|
||||||
@validator("DATABASE_URL", pre=True)
|
@validator("DATABASE_URL", pre=True)
|
||||||
def set_database_url(cls, value):
|
def set_database_url(cls, value, values):
|
||||||
if not value:
|
if not value:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"No database_url provided, trying LANGFLOW_DATABASE_URL env variable"
|
"No database_url provided, trying LANGFLOW_DATABASE_URL env variable"
|
||||||
|
|
@ -56,7 +93,28 @@ class Settings(BaseSettings):
|
||||||
logger.debug("Using LANGFLOW_DATABASE_URL env variable.")
|
logger.debug("Using LANGFLOW_DATABASE_URL env variable.")
|
||||||
else:
|
else:
|
||||||
logger.debug("No DATABASE_URL env variable, using sqlite database")
|
logger.debug("No DATABASE_URL env variable, using sqlite database")
|
||||||
value = "sqlite:///./langflow.db"
|
# Originally, we used sqlite:///./langflow.db
|
||||||
|
# so we need to migrate to the new format
|
||||||
|
# if there is a database in that location
|
||||||
|
if not values["CONFIG_DIR"]:
|
||||||
|
raise ValueError(
|
||||||
|
"CONFIG_DIR not set, please set it or provide a DATABASE_URL"
|
||||||
|
)
|
||||||
|
|
||||||
|
new_path = f"{values['CONFIG_DIR']}/langflow.db"
|
||||||
|
if Path("./langflow.db").exists():
|
||||||
|
if Path(new_path).exists():
|
||||||
|
logger.debug(f"Database already exists at {new_path}, using it")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
logger.debug("Copying existing database to new location")
|
||||||
|
copy2("./langflow.db", new_path)
|
||||||
|
logger.debug(f"Copied existing database to {new_path}")
|
||||||
|
except Exception:
|
||||||
|
logger.error("Failed to copy database, using default path")
|
||||||
|
new_path = "./langflow.db"
|
||||||
|
|
||||||
|
value = f"sqlite:///{new_path}"
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
@ -139,12 +197,17 @@ class Settings(BaseSettings):
|
||||||
value = json.loads(str(value))
|
value = json.loads(str(value))
|
||||||
if isinstance(value, list):
|
if isinstance(value, list):
|
||||||
for item in value:
|
for item in value:
|
||||||
|
if isinstance(item, Path):
|
||||||
|
item = str(item)
|
||||||
if item not in getattr(self, key):
|
if item not in getattr(self, key):
|
||||||
getattr(self, key).append(item)
|
getattr(self, key).append(item)
|
||||||
logger.debug(f"Extended {key}")
|
logger.debug(f"Extended {key}")
|
||||||
else:
|
else:
|
||||||
getattr(self, key).append(value)
|
if isinstance(value, Path):
|
||||||
logger.debug(f"Appended {key}")
|
value = str(value)
|
||||||
|
if value not in getattr(self, key):
|
||||||
|
getattr(self, key).append(value)
|
||||||
|
logger.debug(f"Appended {key}")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
|
||||||
7716
src/frontend/package-lock.json
generated
|
|
@ -40,6 +40,7 @@
|
||||||
"esbuild": "^0.17.18",
|
"esbuild": "^0.17.18",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"lucide-react": "^0.233.0",
|
"lucide-react": "^0.233.0",
|
||||||
|
"moment": "^2.29.4",
|
||||||
"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",
|
||||||
|
|
@ -71,7 +72,8 @@
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"serve": "vite preview",
|
"serve": "vite preview",
|
||||||
"format": "npx prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\""
|
"format": "npx prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\"",
|
||||||
|
"type-check": "tsc --noEmit --pretty --project tsconfig.json && vite"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
|
|
@ -1,6 +1,6 @@
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import "reactflow/dist/style.css";
|
import "reactflow/dist/style.css";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
|
|
||||||
|
|
@ -9,10 +9,12 @@ import ErrorAlert from "./alerts/error";
|
||||||
import NoticeAlert from "./alerts/notice";
|
import NoticeAlert from "./alerts/notice";
|
||||||
import SuccessAlert from "./alerts/success";
|
import SuccessAlert from "./alerts/success";
|
||||||
import CrashErrorComponent from "./components/CrashErrorComponent";
|
import CrashErrorComponent from "./components/CrashErrorComponent";
|
||||||
import Header from "./components/headerComponent";
|
import LoadingComponent from "./components/loadingComponent";
|
||||||
import { alertContext } from "./contexts/alertContext";
|
import { alertContext } from "./contexts/alertContext";
|
||||||
|
import { AuthContext } from "./contexts/authContext";
|
||||||
import { locationContext } from "./contexts/locationContext";
|
import { locationContext } from "./contexts/locationContext";
|
||||||
import { TabsContext } from "./contexts/tabsContext";
|
import { TabsContext } from "./contexts/tabsContext";
|
||||||
|
import { autoLogin, getLoggedUser } from "./controllers/API";
|
||||||
import Router from "./routes";
|
import Router from "./routes";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
|
@ -25,6 +27,7 @@ export default function App() {
|
||||||
setIsStackedOpen(true);
|
setIsStackedOpen(true);
|
||||||
}, [location.pathname, setCurrent, setIsStackedOpen, setShowSideBar]);
|
}, [location.pathname, setCurrent, setIsStackedOpen, setShowSideBar]);
|
||||||
const { hardReset } = useContext(TabsContext);
|
const { hardReset } = useContext(TabsContext);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
errorData,
|
errorData,
|
||||||
errorOpen,
|
errorOpen,
|
||||||
|
|
@ -35,7 +38,11 @@ export default function App() {
|
||||||
successData,
|
successData,
|
||||||
successOpen,
|
successOpen,
|
||||||
setSuccessOpen,
|
setSuccessOpen,
|
||||||
|
setErrorData,
|
||||||
|
loading,
|
||||||
|
setLoading
|
||||||
} = useContext(alertContext);
|
} = useContext(alertContext);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
// Initialize state variable for the list of alerts
|
// Initialize state variable for the list of alerts
|
||||||
const [alertsList, setAlertsList] = useState<
|
const [alertsList, setAlertsList] = useState<
|
||||||
|
|
@ -49,6 +56,7 @@ export default function App() {
|
||||||
const isLoginPage = location.pathname.includes("login");
|
const isLoginPage = location.pathname.includes("login");
|
||||||
const isAdminPage = location.pathname.includes("admin");
|
const isAdminPage = location.pathname.includes("admin");
|
||||||
const isSignUpPage = location.pathname.includes("signup");
|
const isSignUpPage = location.pathname.includes("signup");
|
||||||
|
const isLocalHost = window.location.href.includes("localhost");
|
||||||
|
|
||||||
// Use effect hook to update alertsList when a new alert is added
|
// Use effect hook to update alertsList when a new alert is added
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -125,6 +133,37 @@ export default function App() {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//this function is to get the user logged in when the page is refreshed
|
||||||
|
const { setUserData, getAuthentication, login, setAutoLogin, logout } =
|
||||||
|
useContext(AuthContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
autoLogin().then((user) => {
|
||||||
|
if(user && user['access_token']){
|
||||||
|
user['refresh_token'] = "auto";
|
||||||
|
login(user['access_token'], user['refresh_token']);
|
||||||
|
setUserData(user);
|
||||||
|
setAutoLogin(true);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
setAutoLogin(false);
|
||||||
|
if (getAuthentication() && !isLoginPage) {
|
||||||
|
getLoggedUser()
|
||||||
|
.then((user) => {
|
||||||
|
setUserData(user);
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {});
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 500);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
//need parent component with width and height
|
//need parent component with width and height
|
||||||
<div className="flex h-full flex-col">
|
<div className="flex h-full flex-col">
|
||||||
|
|
@ -137,8 +176,15 @@ export default function App() {
|
||||||
}}
|
}}
|
||||||
FallbackComponent={CrashErrorComponent}
|
FallbackComponent={CrashErrorComponent}
|
||||||
>
|
>
|
||||||
{!isLoginPage && !isSignUpPage && <Header />}
|
{loading ? (
|
||||||
<Router />
|
<div className="loading-page-panel">
|
||||||
|
<LoadingComponent remSize={50} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Router />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
<div></div>
|
<div></div>
|
||||||
<div className="app-div" style={{ zIndex: 999 }}>
|
<div className="app-div" style={{ zIndex: 999 }}>
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,7 @@ export default function ParameterComponent({
|
||||||
let groupedObj = groupByFamily(myData, tooltipTitle!, left, flow!);
|
let groupedObj = groupByFamily(myData, tooltipTitle!, left, flow!);
|
||||||
|
|
||||||
if (groupedObj && groupedObj.length > 0) {
|
if (groupedObj && groupedObj.length > 0) {
|
||||||
|
//@ts-ignore
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
refHtml.current = groupedObj.map((item, index) => {
|
refHtml.current = groupedObj.map((item, index) => {
|
||||||
const Icon: any =
|
const Icon: any =
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { useSSE } from "../../contexts/SSEContext";
|
||||||
import { TabsContext } from "../../contexts/tabsContext";
|
import { TabsContext } from "../../contexts/tabsContext";
|
||||||
import { typesContext } from "../../contexts/typesContext";
|
import { typesContext } from "../../contexts/typesContext";
|
||||||
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
|
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
|
||||||
|
import { validationStatusType } from "../../types/components";
|
||||||
import { NodeDataType } from "../../types/flow";
|
import { NodeDataType } from "../../types/flow";
|
||||||
import { cleanEdges } from "../../utils/reactflowUtils";
|
import { cleanEdges } from "../../utils/reactflowUtils";
|
||||||
import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils";
|
import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils";
|
||||||
|
|
@ -20,13 +21,14 @@ export default function GenericNode({
|
||||||
}: {
|
}: {
|
||||||
data: NodeDataType;
|
data: NodeDataType;
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
}) {
|
}): JSX.Element {
|
||||||
const [data, setData] = useState(olddata);
|
const [data, setData] = useState(olddata);
|
||||||
const { updateFlow, flows, tabId } = useContext(TabsContext);
|
const { updateFlow, flows, tabId } = useContext(TabsContext);
|
||||||
const updateNodeInternals = useUpdateNodeInternals();
|
const updateNodeInternals = useUpdateNodeInternals();
|
||||||
const { types, deleteNode, reactFlowInstance } = useContext(typesContext);
|
const { types, deleteNode, reactFlowInstance } = useContext(typesContext);
|
||||||
const name = nodeIconsLucide[data.type] ? data.type : types[data.type];
|
const name = nodeIconsLucide[data.type] ? data.type : types[data.type];
|
||||||
const [validationStatus, setValidationStatus] = useState(null);
|
const [validationStatus, setValidationStatus] =
|
||||||
|
useState<validationStatusType | null>(null);
|
||||||
// State for outline color
|
// State for outline color
|
||||||
const { sseData, isBuilding } = useSSE();
|
const { sseData, isBuilding } = useSSE();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -41,7 +43,7 @@ export default function GenericNode({
|
||||||
nodes: flow.data.nodes,
|
nodes: flow.data.nodes,
|
||||||
},
|
},
|
||||||
updateEdge: (edge) => {
|
updateEdge: (edge) => {
|
||||||
flow.data.edges = edge;
|
flow.data!.edges = edge;
|
||||||
reactFlowInstance.setEdges(edge);
|
reactFlowInstance.setEdges(edge);
|
||||||
updateNodeInternals(data.id);
|
updateNodeInternals(data.id);
|
||||||
},
|
},
|
||||||
|
|
@ -77,7 +79,7 @@ export default function GenericNode({
|
||||||
"generic-node-div"
|
"generic-node-div"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{data.node.beta && (
|
{data.node?.beta && (
|
||||||
<div className="beta-badge-wrapper">
|
<div className="beta-badge-wrapper">
|
||||||
<div className="beta-badge-content">BETA</div>
|
<div className="beta-badge-content">BETA</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -90,9 +92,9 @@ export default function GenericNode({
|
||||||
iconColor={`${nodeColors[types[data.type]]}`}
|
iconColor={`${nodeColors[types[data.type]]}`}
|
||||||
/>
|
/>
|
||||||
<div className="generic-node-tooltip-div">
|
<div className="generic-node-tooltip-div">
|
||||||
<ShadTooltip content={data.node.display_name}>
|
<ShadTooltip content={data.node?.display_name}>
|
||||||
<div className="generic-node-tooltip-div text-primary">
|
<div className="generic-node-tooltip-div text-primary">
|
||||||
{data.node.display_name}
|
{data.node?.display_name}
|
||||||
</div>
|
</div>
|
||||||
</ShadTooltip>
|
</ShadTooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -117,7 +119,9 @@ export default function GenericNode({
|
||||||
{typeof validationStatus.params === "string"
|
{typeof validationStatus.params === "string"
|
||||||
? validationStatus.params
|
? validationStatus.params
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.map((line, index) => <div key={index}>{line}</div>)
|
.map((line: string, index: number) => (
|
||||||
|
<div key={index}>{line}</div>
|
||||||
|
))
|
||||||
: ""}
|
: ""}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
@ -155,20 +159,20 @@ export default function GenericNode({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="generic-node-desc">
|
<div className="generic-node-desc">
|
||||||
<div className="generic-node-desc-text">{data.node.description}</div>
|
<div className="generic-node-desc-text">{data.node?.description}</div>
|
||||||
|
|
||||||
<>
|
<>
|
||||||
{Object.keys(data.node.template)
|
{Object.keys(data.node!.template)
|
||||||
.filter((templateField) => templateField.charAt(0) !== "_")
|
.filter((templateField) => templateField.charAt(0) !== "_")
|
||||||
.map((templateField: string, idx) => (
|
.map((templateField: string, idx) => (
|
||||||
<div key={idx}>
|
<div key={idx}>
|
||||||
{data.node.template[templateField].show &&
|
{data.node!.template[templateField].show &&
|
||||||
!data.node.template[templateField].advanced ? (
|
!data.node!.template[templateField].advanced ? (
|
||||||
<ParameterComponent
|
<ParameterComponent
|
||||||
key={
|
key={
|
||||||
(data.node.template[templateField].input_types?.join(
|
(data.node!.template[templateField].input_types?.join(
|
||||||
";"
|
";"
|
||||||
) ?? data.node.template[templateField].type) +
|
) ?? data.node!.template[templateField].type) +
|
||||||
"|" +
|
"|" +
|
||||||
templateField +
|
templateField +
|
||||||
"|" +
|
"|" +
|
||||||
|
|
@ -178,39 +182,39 @@ export default function GenericNode({
|
||||||
setData={setData}
|
setData={setData}
|
||||||
color={
|
color={
|
||||||
nodeColors[
|
nodeColors[
|
||||||
types[data.node.template[templateField].type]
|
types[data.node?.template[templateField].type!]
|
||||||
] ??
|
] ??
|
||||||
nodeColors[data.node.template[templateField].type] ??
|
nodeColors[data.node?.template[templateField].type!] ??
|
||||||
nodeColors.unknown
|
nodeColors.unknown
|
||||||
}
|
}
|
||||||
title={
|
title={
|
||||||
data.node.template[templateField].display_name
|
data.node?.template[templateField].display_name
|
||||||
? data.node.template[templateField].display_name
|
? data.node.template[templateField].display_name
|
||||||
: data.node.template[templateField].name
|
: data.node?.template[templateField].name
|
||||||
? toTitleCase(data.node.template[templateField].name)
|
? toTitleCase(data.node.template[templateField].name)
|
||||||
: toTitleCase(templateField)
|
: toTitleCase(templateField)
|
||||||
}
|
}
|
||||||
info={data.node.template[templateField].info}
|
info={data.node?.template[templateField].info}
|
||||||
name={templateField}
|
name={templateField}
|
||||||
tooltipTitle={
|
tooltipTitle={
|
||||||
data.node.template[templateField].input_types?.join(
|
data.node?.template[templateField].input_types?.join(
|
||||||
"\n"
|
"\n"
|
||||||
) ?? data.node.template[templateField].type
|
) ?? data.node?.template[templateField].type
|
||||||
}
|
}
|
||||||
required={data.node.template[templateField].required}
|
required={data.node?.template[templateField].required}
|
||||||
id={
|
id={
|
||||||
(data.node.template[templateField].input_types?.join(
|
(data.node?.template[templateField].input_types?.join(
|
||||||
";"
|
";"
|
||||||
) ?? data.node.template[templateField].type) +
|
) ?? data.node?.template[templateField].type) +
|
||||||
"|" +
|
"|" +
|
||||||
templateField +
|
templateField +
|
||||||
"|" +
|
"|" +
|
||||||
data.id
|
data.id
|
||||||
}
|
}
|
||||||
left={true}
|
left={true}
|
||||||
type={data.node.template[templateField].type}
|
type={data.node?.template[templateField].type}
|
||||||
optionalHandle={
|
optionalHandle={
|
||||||
data.node.template[templateField].input_types
|
data.node?.template[templateField].input_types
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -220,25 +224,25 @@ export default function GenericNode({
|
||||||
))}
|
))}
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
Object.keys(data.node.template).length < 1 ? "hidden" : "",
|
Object.keys(data.node!.template).length < 1 ? "hidden" : "",
|
||||||
"flex-max-width justify-center"
|
"flex-max-width justify-center"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{" "}
|
{" "}
|
||||||
</div>
|
</div>
|
||||||
<ParameterComponent
|
<ParameterComponent
|
||||||
key={[data.type, data.id, ...data.node.base_classes].join("|")}
|
key={[data.type, data.id, ...data.node!.base_classes].join("|")}
|
||||||
data={data}
|
data={data}
|
||||||
setData={setData}
|
setData={setData}
|
||||||
color={nodeColors[types[data.type]] ?? nodeColors.unknown}
|
color={nodeColors[types[data.type]] ?? nodeColors.unknown}
|
||||||
title={
|
title={
|
||||||
data.node.output_types && data.node.output_types.length > 0
|
data.node?.output_types && data.node.output_types.length > 0
|
||||||
? data.node.output_types.join("|")
|
? data.node.output_types.join("|")
|
||||||
: data.type
|
: data.type
|
||||||
}
|
}
|
||||||
tooltipTitle={data.node.base_classes.join("\n")}
|
tooltipTitle={data.node?.base_classes.join("\n")}
|
||||||
id={[data.type, data.id, ...data.node.base_classes].join("|")}
|
id={[data.type, data.id, ...data.node!.base_classes].join("|")}
|
||||||
type={data.node.base_classes.join("|")}
|
type={data.node?.base_classes.join("|")}
|
||||||
left={false}
|
left={false}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { SingleAlertComponentType } from "../../../../types/alerts";
|
||||||
export default function SingleAlert({
|
export default function SingleAlert({
|
||||||
dropItem,
|
dropItem,
|
||||||
removeAlert,
|
removeAlert,
|
||||||
}: SingleAlertComponentType) {
|
}: SingleAlertComponentType): JSX.Element {
|
||||||
const [show, setShow] = useState(true);
|
const [show, setShow] = useState(true);
|
||||||
const type = dropItem.type;
|
const type = dropItem.type;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,9 @@ import { alertContext } from "../../contexts/alertContext";
|
||||||
import { AlertDropdownType } from "../../types/alerts";
|
import { AlertDropdownType } from "../../types/alerts";
|
||||||
import SingleAlert from "./components/singleAlertComponent";
|
import SingleAlert from "./components/singleAlertComponent";
|
||||||
|
|
||||||
export default function AlertDropdown({ children }: AlertDropdownType) {
|
export default function AlertDropdown({
|
||||||
|
children,
|
||||||
|
}: AlertDropdownType): JSX.Element {
|
||||||
const {
|
const {
|
||||||
notificationList,
|
notificationList,
|
||||||
clearNotificationList,
|
clearNotificationList,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ export default function ErrorAlert({
|
||||||
list = [],
|
list = [],
|
||||||
id,
|
id,
|
||||||
removeAlert,
|
removeAlert,
|
||||||
}: ErrorAlertType) {
|
}: ErrorAlertType): JSX.Element {
|
||||||
const [show, setShow] = useState(true);
|
const [show, setShow] = useState(true);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (show) {
|
if (show) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { useEffect } from "react";
|
||||||
|
|
||||||
export function useOnClickOutside(ref, handler) {
|
export function useOnClickOutside(ref, handler) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const listener = (event) => {
|
const listener = (event: Event) => {
|
||||||
// Do nothing if clicking ref's element or its children
|
// Do nothing if clicking ref's element or its children
|
||||||
if (!ref.current || ref.current.contains(event.target)) {
|
if (!ref.current || ref.current.contains(event.target)) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ export default function NoticeAlert({
|
||||||
link = "",
|
link = "",
|
||||||
id,
|
id,
|
||||||
removeAlert,
|
removeAlert,
|
||||||
}: NoticeAlertType) {
|
}: NoticeAlertType): JSX.Element {
|
||||||
const [show, setShow] = useState(true);
|
const [show, setShow] = useState(true);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (show) {
|
if (show) {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ export default function SuccessAlert({
|
||||||
title,
|
title,
|
||||||
id,
|
id,
|
||||||
removeAlert,
|
removeAlert,
|
||||||
}: SuccessAlertType) {
|
}: SuccessAlertType): JSX.Element {
|
||||||
const [show, setShow] = useState(true);
|
const [show, setShow] = useState(true);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (show) {
|
if (show) {
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,12 @@ export default function AccordionComponent({
|
||||||
children,
|
children,
|
||||||
open = [],
|
open = [],
|
||||||
keyValue,
|
keyValue,
|
||||||
}: AccordionComponentType) {
|
}: AccordionComponentType): JSX.Element {
|
||||||
const [value, setValue] = useState(
|
const [value, setValue] = useState(
|
||||||
open.length === 0 ? "" : getOpenAccordion()
|
open.length === 0 ? "" : getOpenAccordion()
|
||||||
);
|
);
|
||||||
|
|
||||||
function getOpenAccordion() {
|
function getOpenAccordion(): string {
|
||||||
let value = "";
|
let value = "";
|
||||||
open.forEach((el) => {
|
open.forEach((el) => {
|
||||||
if (el == trigger) {
|
if (el == trigger) {
|
||||||
|
|
@ -28,10 +28,17 @@ export default function AccordionComponent({
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleClick() {
|
function handleClick(): void {
|
||||||
value === "" ? setValue(keyValue) : setValue("");
|
value === "" ? setValue(keyValue!) : setValue("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleKeyDown = (event) => {
|
||||||
|
if (event.key === "Backspace") {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Accordion
|
<Accordion
|
||||||
|
|
@ -39,8 +46,9 @@ export default function AccordionComponent({
|
||||||
className="w-full"
|
className="w-full"
|
||||||
value={value}
|
value={value}
|
||||||
onValueChange={setValue}
|
onValueChange={setValue}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
>
|
>
|
||||||
<AccordionItem value={keyValue} className="border-b">
|
<AccordionItem value={keyValue!} className="border-b">
|
||||||
<AccordionTrigger
|
<AccordionTrigger
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleClick();
|
handleClick();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,9 @@
|
||||||
export default function CrashErrorComponent({ error, resetErrorBoundary }) {
|
import { crashComponentPropsType } from "../../types/components";
|
||||||
|
|
||||||
|
export default function CrashErrorComponent({
|
||||||
|
error,
|
||||||
|
resetErrorBoundary,
|
||||||
|
}: crashComponentPropsType): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className="fixed left-0 top-0 z-50 flex h-full w-full items-center justify-center bg-foreground bg-opacity-50">
|
<div className="fixed left-0 top-0 z-50 flex h-full w-full items-center justify-center bg-foreground bg-opacity-50">
|
||||||
<div className="flex h-1/3 min-h-fit max-w-4xl flex-col justify-evenly rounded-lg bg-background p-8 text-start shadow-lg">
|
<div className="flex h-1/3 min-h-fit max-w-4xl flex-col justify-evenly rounded-lg bg-background p-8 text-start shadow-lg">
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,8 @@ import { Input } from "../../components/ui/input";
|
||||||
import { Label } from "../../components/ui/label";
|
import { Label } from "../../components/ui/label";
|
||||||
import { Textarea } from "../../components/ui/textarea";
|
import { Textarea } from "../../components/ui/textarea";
|
||||||
import { readFlowsFromDatabase } from "../../controllers/API";
|
import { readFlowsFromDatabase } from "../../controllers/API";
|
||||||
|
import { InputProps } from "../../types/components";
|
||||||
type InputProps = {
|
import { FlowType } from "../../types/flow";
|
||||||
name: string | null;
|
|
||||||
description: string | null;
|
|
||||||
maxLength?: number;
|
|
||||||
flows: Array<{ id: string; name: string; description: string }>;
|
|
||||||
tabId: string;
|
|
||||||
invalidName: boolean;
|
|
||||||
setInvalidName: (invalidName: boolean) => void;
|
|
||||||
setName: (name: string) => void;
|
|
||||||
setDescription: (description: string) => void;
|
|
||||||
updateFlow: (flow: { id: string; name: string }) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const EditFlowSettings: React.FC<InputProps> = ({
|
export const EditFlowSettings: React.FC<InputProps> = ({
|
||||||
name,
|
name,
|
||||||
|
|
@ -27,13 +16,12 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
||||||
tabId,
|
tabId,
|
||||||
setName,
|
setName,
|
||||||
setDescription,
|
setDescription,
|
||||||
updateFlow,
|
}: InputProps): JSX.Element => {
|
||||||
}) => {
|
|
||||||
const [isMaxLength, setIsMaxLength] = useState(false);
|
const [isMaxLength, setIsMaxLength] = useState(false);
|
||||||
const nameLists = useRef([]);
|
const nameLists = useRef<string[]>([]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
readFlowsFromDatabase().then((flows) => {
|
readFlowsFromDatabase().then((flows) => {
|
||||||
flows.forEach((flow) => {
|
flows.forEach((flow: FlowType) => {
|
||||||
nameLists.current.push(flow.name);
|
nameLists.current.push(flow.name);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -46,21 +34,34 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
||||||
} else {
|
} else {
|
||||||
setIsMaxLength(false);
|
setIsMaxLength(false);
|
||||||
}
|
}
|
||||||
|
if (invalidName !== undefined) {
|
||||||
|
if (!nameLists.current.includes(value)) {
|
||||||
|
setInvalidName(false);
|
||||||
|
} else {
|
||||||
|
setInvalidName(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!nameLists.current.includes(value)) {
|
if (!nameLists.current.includes(value)) {
|
||||||
setInvalidName(false);
|
setInvalidName!(false);
|
||||||
} else {
|
} else {
|
||||||
setInvalidName(true);
|
setInvalidName!(true);
|
||||||
}
|
}
|
||||||
setName(value);
|
setName(value);
|
||||||
|
setCurrentName(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const [desc, setDesc] = useState(
|
const [currentName, setCurrentName] = useState(name);
|
||||||
flows.find((flow) => flow.id === tabId).description
|
|
||||||
);
|
const [currentDescription, setCurrentDescription] = useState(description);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCurrentName(name);
|
||||||
|
setCurrentDescription(description);
|
||||||
|
}, [name, description]);
|
||||||
|
|
||||||
const handleDescriptionChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
|
const handleDescriptionChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
flows.find((flow) => flow.id === tabId).description = event.target.value;
|
flows.find((f) => f.id === tabId).description = event.target.value;
|
||||||
setDesc(flows.find((flow) => flow.id === tabId).description);
|
setCurrentDescription(flows.find((f) => f.id === tabId).description);
|
||||||
setDescription(event.target.value);
|
setDescription(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -81,7 +82,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
||||||
onChange={handleNameChange}
|
onChange={handleNameChange}
|
||||||
type="text"
|
type="text"
|
||||||
name="name"
|
name="name"
|
||||||
value={name ?? ""}
|
value={currentName ?? ""}
|
||||||
placeholder="File name"
|
placeholder="File name"
|
||||||
id="name"
|
id="name"
|
||||||
maxLength={maxLength}
|
maxLength={maxLength}
|
||||||
|
|
@ -96,7 +97,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
||||||
name="description"
|
name="description"
|
||||||
id="description"
|
id="description"
|
||||||
onChange={handleDescriptionChange}
|
onChange={handleDescriptionChange}
|
||||||
value={desc}
|
value={currentDescription}
|
||||||
placeholder="Flow description"
|
placeholder="Flow description"
|
||||||
className="mt-2 max-h-[100px] font-normal"
|
className="mt-2 max-h-[100px] font-normal"
|
||||||
rows={3}
|
rows={3}
|
||||||
|
|
|
||||||
|
|
@ -12,17 +12,17 @@ import { Button } from "../ui/button";
|
||||||
|
|
||||||
export default function PaginatorComponent({
|
export default function PaginatorComponent({
|
||||||
pageSize = 10,
|
pageSize = 10,
|
||||||
pageIndex = 1,
|
pageIndex = 0,
|
||||||
rowsCount = [10, 20, 30],
|
rowsCount = [10, 20, 50, 100],
|
||||||
totalRowsCount = 0,
|
totalRowsCount = 0,
|
||||||
paginate,
|
paginate,
|
||||||
}: PaginatorComponentType) {
|
}: PaginatorComponentType) {
|
||||||
const [size, setPageSize] = useState(pageSize);
|
const [size, setPageSize] = useState(pageSize);
|
||||||
const [index, setPageIndex] = useState(pageIndex);
|
const [index, setPageIndex] = useState(pageIndex);
|
||||||
|
|
||||||
const [maxIndex, setMaxPageIndex] = useState(
|
const [maxIndex, setMaxPageIndex] = useState(
|
||||||
Math.ceil(totalRowsCount / pageSize)
|
Math.ceil(totalRowsCount / pageSize)
|
||||||
);
|
);
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -35,7 +35,7 @@ export default function PaginatorComponent({
|
||||||
onValueChange={(pageSize: string) => {
|
onValueChange={(pageSize: string) => {
|
||||||
setPageSize(Number(pageSize));
|
setPageSize(Number(pageSize));
|
||||||
setMaxPageIndex(Math.ceil(totalRowsCount / Number(pageSize)));
|
setMaxPageIndex(Math.ceil(totalRowsCount / Number(pageSize)));
|
||||||
paginate(Number(pageSize), index);
|
paginate(Number(pageSize), 0);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-[100px]">
|
<SelectTrigger className="w-[100px]">
|
||||||
|
|
@ -51,30 +51,30 @@ export default function PaginatorComponent({
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-[100px] items-center justify-center text-sm font-medium">
|
<div className="flex w-[100px] items-center justify-center text-sm font-medium">
|
||||||
Page {index} of {maxIndex}
|
Page {currentPage} of {maxIndex}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Button
|
<Button
|
||||||
|
disabled={index <= 0}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="hidden h-8 w-8 p-0 lg:flex"
|
className="hidden h-8 w-8 p-0 lg:flex"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPageIndex(1);
|
setPageIndex(0);
|
||||||
paginate(size, 1);
|
setCurrentPage(1);
|
||||||
|
paginate(size, 0);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Go to first page</span>
|
<span className="sr-only">Go to first page</span>
|
||||||
<IconComponent name="ChevronsLeft" className="h-4 w-4" />
|
<IconComponent name="ChevronsLeft" className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
disabled={index <= 0}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (index <= 1) {
|
if (index > 0) {
|
||||||
setPageIndex(1);
|
const pgIndex = size - index;
|
||||||
paginate(size, 1);
|
setCurrentPage(currentPage - 1);
|
||||||
} else {
|
setPageIndex(pgIndex);
|
||||||
{
|
paginate(size, pgIndex);
|
||||||
setPageIndex(index - 1);
|
|
||||||
paginate(size, index - 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|
@ -84,14 +84,12 @@ export default function PaginatorComponent({
|
||||||
<IconComponent name="ChevronLeft" className="h-4 w-4" />
|
<IconComponent name="ChevronLeft" className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
disabled={currentPage === maxIndex}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (index >= maxIndex) {
|
const pgIndex = size + index;
|
||||||
setPageIndex(maxIndex);
|
setPageIndex(pgIndex);
|
||||||
paginate(size, maxIndex);
|
setCurrentPage(currentPage + 1);
|
||||||
} else {
|
paginate(size, pgIndex);
|
||||||
setPageIndex(index + 1);
|
|
||||||
paginate(size, index + 1);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="h-8 w-8 p-0"
|
className="h-8 w-8 p-0"
|
||||||
|
|
@ -100,11 +98,13 @@ export default function PaginatorComponent({
|
||||||
<IconComponent name="ChevronRight" className="h-4 w-4" />
|
<IconComponent name="ChevronRight" className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
disabled={currentPage === maxIndex}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="hidden h-8 w-8 p-0 lg:flex"
|
className="hidden h-8 w-8 p-0 lg:flex"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPageIndex(maxIndex);
|
setPageIndex(maxIndex - 1);
|
||||||
paginate(size, maxIndex);
|
setCurrentPage(maxIndex);
|
||||||
|
paginate(size, size);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Go to last page</span>
|
<span className="sr-only">Go to last page</span>
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,16 @@ import { RadialProgressType } from "../../types/components";
|
||||||
export default function RadialProgressComponent({
|
export default function RadialProgressComponent({
|
||||||
value,
|
value,
|
||||||
color,
|
color,
|
||||||
}: RadialProgressType) {
|
}: RadialProgressType): JSX.Element {
|
||||||
const style = {
|
const style = {
|
||||||
"--value": value * 100,
|
"--value": value! * 100,
|
||||||
"--size": "1.5rem",
|
"--size": "1.5rem",
|
||||||
"--thickness": "2px",
|
"--thickness": "2px",
|
||||||
} as React.CSSProperties;
|
} as React.CSSProperties;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={"radial-progress " + color} style={style}>
|
<div className={"radial-progress " + color} style={style}>
|
||||||
<strong className="text-[8px]">{Math.trunc(value * 100)}%</strong>
|
<strong className="text-[8px]">{Math.trunc(value! * 100)}%</strong>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,20 +3,9 @@ import type { FC } from "react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Tooltip as ReactTooltip } from "react-tooltip";
|
import { Tooltip as ReactTooltip } from "react-tooltip";
|
||||||
import "react-tooltip/dist/react-tooltip.css";
|
import "react-tooltip/dist/react-tooltip.css";
|
||||||
|
import { TooltipProps } from "../../types/components";
|
||||||
import { classNames } from "../../utils/utils";
|
import { classNames } from "../../utils/utils";
|
||||||
|
|
||||||
type TooltipProps = {
|
|
||||||
selector: string;
|
|
||||||
content?: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
htmlContent?: React.ReactNode;
|
|
||||||
className?: string; // This should use !impornant to override the default styles eg: '!bg-white'
|
|
||||||
position?: "top" | "right" | "bottom" | "left";
|
|
||||||
clickable?: boolean;
|
|
||||||
children: React.ReactNode;
|
|
||||||
delayShow?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const TooltipReact: FC<TooltipProps> = ({
|
const TooltipReact: FC<TooltipProps> = ({
|
||||||
selector,
|
selector,
|
||||||
content,
|
content,
|
||||||
|
|
@ -27,7 +16,7 @@ const TooltipReact: FC<TooltipProps> = ({
|
||||||
className,
|
className,
|
||||||
clickable,
|
clickable,
|
||||||
delayShow,
|
delayShow,
|
||||||
}) => {
|
}: TooltipProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<div className="tooltip-container">
|
<div className="tooltip-container">
|
||||||
{React.cloneElement(children as React.ReactElement, {
|
{React.cloneElement(children as React.ReactElement, {
|
||||||
|
|
@ -38,7 +27,7 @@ const TooltipReact: FC<TooltipProps> = ({
|
||||||
content={content}
|
content={content}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"z-[9999] !bg-white !text-xs !font-normal !text-foreground !opacity-100 !shadow-md",
|
"z-[9999] !bg-white !text-xs !font-normal !text-foreground !opacity-100 !shadow-md",
|
||||||
className
|
className!
|
||||||
)}
|
)}
|
||||||
place={position}
|
place={position}
|
||||||
clickable={clickable}
|
clickable={clickable}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
import DOMPurify from "dompurify";
|
import DOMPurify from "dompurify";
|
||||||
|
import { SanitizedHTMLWrapperType } from "../../types/components";
|
||||||
|
|
||||||
const SanitizedHTMLWrapper = ({
|
const SanitizedHTMLWrapper = ({
|
||||||
className,
|
className,
|
||||||
content,
|
content,
|
||||||
onClick,
|
onClick,
|
||||||
suppressWarning = false,
|
suppressWarning = false,
|
||||||
}) => {
|
}: SanitizedHTMLWrapperType): JSX.Element => {
|
||||||
const sanitizedHTML = DOMPurify.sanitize(content);
|
const sanitizedHTML = DOMPurify.sanitize(content);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ export default function ShadTooltip({
|
||||||
children,
|
children,
|
||||||
styleClasses,
|
styleClasses,
|
||||||
delayDuration = 500,
|
delayDuration = 500,
|
||||||
}: ShadToolTipType) {
|
}: ShadToolTipType): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Tooltip delayDuration={delayDuration}>
|
<Tooltip delayDuration={delayDuration}>
|
||||||
<TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>
|
<TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ export default function Tooltip({
|
||||||
children,
|
children,
|
||||||
title,
|
title,
|
||||||
placement,
|
placement,
|
||||||
}: TooltipComponentType) {
|
}: TooltipComponentType): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<LightTooltip placement={placement} title={title} arrow>
|
<LightTooltip placement={placement} title={title} arrow>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
||||||
24
src/frontend/src/components/authAdminGuard/index.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { useContext, useEffect } from "react";
|
||||||
|
import { Navigate } from "react-router-dom";
|
||||||
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
|
|
||||||
|
export const ProtectedAdminRoute = ({ children }) => {
|
||||||
|
const { isAdmin, isAuthenticated, logout, getAuthentication, userData, autoLogin } =
|
||||||
|
useContext(AuthContext);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isAuthenticated && !getAuthentication()) {
|
||||||
|
window.location.replace("/login");
|
||||||
|
logout();
|
||||||
|
}
|
||||||
|
}, [isAuthenticated, getAuthentication, logout, userData]);
|
||||||
|
|
||||||
|
if (!isAuthenticated && !getAuthentication()) {
|
||||||
|
return <Navigate to="/login" replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userData && !isAdmin || autoLogin) {
|
||||||
|
return <Navigate to="/" replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return children;
|
||||||
|
};
|
||||||
14
src/frontend/src/components/authGuard/index.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { useContext } from "react";
|
||||||
|
import { Navigate } from "react-router-dom";
|
||||||
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
|
|
||||||
|
export const ProtectedRoute = ({ children }) => {
|
||||||
|
const { isAuthenticated, logout, getAuthentication } =
|
||||||
|
useContext(AuthContext);
|
||||||
|
if (!isAuthenticated && !getAuthentication()) {
|
||||||
|
logout();
|
||||||
|
return <Navigate to="/login" replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return children;
|
||||||
|
};
|
||||||
19
src/frontend/src/components/authLoginGuard/index.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { useContext } from "react";
|
||||||
|
import { Navigate } from "react-router-dom";
|
||||||
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
|
|
||||||
|
export const ProtectedLoginRoute = ({ children }) => {
|
||||||
|
const { getAuthentication, autoLogin } = useContext(AuthContext);
|
||||||
|
|
||||||
|
if (autoLogin === true) {
|
||||||
|
window.location.replace("/");
|
||||||
|
return <Navigate to="/" replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getAuthentication()) {
|
||||||
|
window.location.replace("/");
|
||||||
|
return <Navigate to="/" replace />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return children;
|
||||||
|
};
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { TabsContext } from "../../contexts/tabsContext";
|
import { TabsContext } from "../../contexts/tabsContext";
|
||||||
import { FlowType } from "../../types/flow";
|
import { cardComponentPropsType } from "../../types/components";
|
||||||
import { gradients } from "../../utils/styleUtils";
|
import { gradients } from "../../utils/styleUtils";
|
||||||
import IconComponent from "../genericIconComponent";
|
import IconComponent from "../genericIconComponent";
|
||||||
import {
|
import {
|
||||||
|
|
@ -16,12 +16,7 @@ export const CardComponent = ({
|
||||||
id,
|
id,
|
||||||
onDelete,
|
onDelete,
|
||||||
button,
|
button,
|
||||||
}: {
|
}: cardComponentPropsType): JSX.Element => {
|
||||||
flow: FlowType;
|
|
||||||
id: string;
|
|
||||||
onDelete?: () => void;
|
|
||||||
button?: JSX.Element;
|
|
||||||
}) => {
|
|
||||||
const { removeFlow } = useContext(TabsContext);
|
const { removeFlow } = useContext(TabsContext);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
13
src/frontend/src/components/catchAllRoutes/index.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
export const CatchAllRoute = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
// Redirect to the root ("/") when the catch-all route is matched
|
||||||
|
useEffect(() => {
|
||||||
|
navigate("/");
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
@ -8,6 +8,8 @@ import { postBuildInit } from "../../../controllers/API";
|
||||||
import { FlowType } from "../../../types/flow";
|
import { FlowType } from "../../../types/flow";
|
||||||
|
|
||||||
import { TabsContext } from "../../../contexts/tabsContext";
|
import { TabsContext } from "../../../contexts/tabsContext";
|
||||||
|
import { parsedDataType } from "../../../types/components";
|
||||||
|
import { TabsState } from "../../../types/tabs";
|
||||||
import { validateNodes } from "../../../utils/reactflowUtils";
|
import { validateNodes } from "../../../utils/reactflowUtils";
|
||||||
import RadialProgressComponent from "../../RadialProgress";
|
import RadialProgressComponent from "../../RadialProgress";
|
||||||
import IconComponent from "../../genericIconComponent";
|
import IconComponent from "../../genericIconComponent";
|
||||||
|
|
@ -21,7 +23,7 @@ export default function BuildTrigger({
|
||||||
flow: FlowType;
|
flow: FlowType;
|
||||||
setIsBuilt: any;
|
setIsBuilt: any;
|
||||||
isBuilt: boolean;
|
isBuilt: boolean;
|
||||||
}) {
|
}): JSX.Element {
|
||||||
const { updateSSEData, isBuilding, setIsBuilding, sseData } = useSSE();
|
const { updateSSEData, isBuilding, setIsBuilding, sseData } = useSSE();
|
||||||
const { reactFlowInstance } = useContext(typesContext);
|
const { reactFlowInstance } = useContext(typesContext);
|
||||||
const { setTabsState } = useContext(TabsContext);
|
const { setTabsState } = useContext(TabsContext);
|
||||||
|
|
@ -30,12 +32,12 @@ export default function BuildTrigger({
|
||||||
const eventClick = isBuilding ? "pointer-events-none" : "";
|
const eventClick = isBuilding ? "pointer-events-none" : "";
|
||||||
const [progress, setProgress] = useState(0);
|
const [progress, setProgress] = useState(0);
|
||||||
|
|
||||||
async function handleBuild(flow: FlowType) {
|
async function handleBuild(flow: FlowType): Promise<void> {
|
||||||
try {
|
try {
|
||||||
if (isBuilding) {
|
if (isBuilding) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const errors = validateNodes(reactFlowInstance);
|
const errors = validateNodes(reactFlowInstance!);
|
||||||
if (errors.length > 0) {
|
if (errors.length > 0) {
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title: "Oops! Looks like you missed something",
|
title: "Oops! Looks like you missed something",
|
||||||
|
|
@ -69,7 +71,7 @@ export default function BuildTrigger({
|
||||||
const response = await postBuildInit(flow);
|
const response = await postBuildInit(flow);
|
||||||
const { flowId } = response.data;
|
const { flowId } = response.data;
|
||||||
// Step 2: Use the session ID to establish an SSE connection using EventSource
|
// Step 2: Use the session ID to establish an SSE connection using EventSource
|
||||||
let validationResults = [];
|
let validationResults: boolean[] = [];
|
||||||
let finished = false;
|
let finished = false;
|
||||||
const apiUrl = `/api/v1/build/stream/${flowId}`;
|
const apiUrl = `/api/v1/build/stream/${flowId}`;
|
||||||
const eventSource = new EventSource(apiUrl);
|
const eventSource = new EventSource(apiUrl);
|
||||||
|
|
@ -91,7 +93,8 @@ export default function BuildTrigger({
|
||||||
// If the event is a log, log it
|
// If the event is a log, log it
|
||||||
setSuccessData({ title: parsedData.log });
|
setSuccessData({ title: parsedData.log });
|
||||||
} else if (parsedData.input_keys !== undefined) {
|
} else if (parsedData.input_keys !== undefined) {
|
||||||
setTabsState((old) => {
|
//@ts-ignore
|
||||||
|
setTabsState((old: TabsState) => {
|
||||||
return {
|
return {
|
||||||
...old,
|
...old,
|
||||||
[flowId]: {
|
[flowId]: {
|
||||||
|
|
@ -120,13 +123,13 @@ export default function BuildTrigger({
|
||||||
// Step 3: Wait for the stream to finish
|
// Step 3: Wait for the stream to finish
|
||||||
while (!finished) {
|
while (!finished) {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
finished = validationResults.length === flow.data.nodes.length;
|
finished = validationResults.length === flow.data!.nodes.length;
|
||||||
}
|
}
|
||||||
// Step 4: Return true if all nodes are valid, false otherwise
|
// Step 4: Return true if all nodes are valid, false otherwise
|
||||||
return validationResults.every((result) => result);
|
return validationResults.every((result) => result);
|
||||||
}
|
}
|
||||||
|
|
||||||
function processStreamResult(parsedData) {
|
function processStreamResult(parsedData: parsedDataType) {
|
||||||
// Process each chunk of data here
|
// Process each chunk of data here
|
||||||
// Parse the chunk and update the context
|
// Parse the chunk and update the context
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,18 @@ import {
|
||||||
FLOW_NOT_BUILT_TITLE,
|
FLOW_NOT_BUILT_TITLE,
|
||||||
} from "../../../constants/constants";
|
} from "../../../constants/constants";
|
||||||
import { alertContext } from "../../../contexts/alertContext";
|
import { alertContext } from "../../../contexts/alertContext";
|
||||||
|
import { chatTriggerPropType } from "../../../types/components";
|
||||||
import IconComponent from "../../genericIconComponent";
|
import IconComponent from "../../genericIconComponent";
|
||||||
|
|
||||||
export default function ChatTrigger({ open, setOpen, isBuilt, canOpen }) {
|
export default function ChatTrigger({
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
isBuilt,
|
||||||
|
canOpen,
|
||||||
|
}: chatTriggerPropType): JSX.Element {
|
||||||
const { setErrorData } = useContext(alertContext);
|
const { setErrorData } = useContext(alertContext);
|
||||||
|
|
||||||
function handleClick() {
|
function handleClick(): void {
|
||||||
if (isBuilt) {
|
if (isBuilt) {
|
||||||
if (canOpen) {
|
if (canOpen) {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { getBuildStatus } from "../../controllers/API";
|
||||||
import FormModal from "../../modals/formModal";
|
import FormModal from "../../modals/formModal";
|
||||||
import { NodeType } from "../../types/flow";
|
import { NodeType } from "../../types/flow";
|
||||||
|
|
||||||
export default function Chat({ flow }: ChatType) {
|
export default function Chat({ flow }: ChatType): JSX.Element {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [canOpen, setCanOpen] = useState(false);
|
const [canOpen, setCanOpen] = useState(false);
|
||||||
const { tabsState, isBuilt, setIsBuilt } = useContext(TabsContext);
|
const { tabsState, isBuilt, setIsBuilt } = useContext(TabsContext);
|
||||||
|
|
@ -44,11 +44,11 @@ export default function Chat({ flow }: ChatType) {
|
||||||
}, [flow]);
|
}, [flow]);
|
||||||
|
|
||||||
const prevNodesRef = useRef<any[] | undefined>();
|
const prevNodesRef = useRef<any[] | undefined>();
|
||||||
const nodes = useNodes();
|
const nodes: NodeType[] = useNodes();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const prevNodes = prevNodesRef.current;
|
const prevNodes = prevNodesRef.current;
|
||||||
const currentNodes = nodes.map((node: NodeType) =>
|
const currentNodes = nodes.map((node: NodeType) =>
|
||||||
_.cloneDeep(node.data.node.template)
|
_.cloneDeep(node.data.node?.template)
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
tabsState &&
|
tabsState &&
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ export default function CodeAreaComponent({
|
||||||
dynamic={dynamic}
|
dynamic={dynamic}
|
||||||
value={myValue}
|
value={myValue}
|
||||||
nodeClass={nodeClass}
|
nodeClass={nodeClass}
|
||||||
setNodeClass={setNodeClass}
|
setNodeClass={setNodeClass!}
|
||||||
setValue={(value: string) => {
|
setValue={(value: string) => {
|
||||||
setMyValue(value);
|
setMyValue(value);
|
||||||
onChange(value);
|
onChange(value);
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import {
|
||||||
TabsTrigger,
|
TabsTrigger,
|
||||||
} from "../../components/ui/tabs";
|
} from "../../components/ui/tabs";
|
||||||
import { darkContext } from "../../contexts/darkContext";
|
import { darkContext } from "../../contexts/darkContext";
|
||||||
import { FlowType } from "../../types/flow/index";
|
import { codeTabsPropsType } from "../../types/components";
|
||||||
import { classNames } from "../../utils/utils";
|
import { classNames } from "../../utils/utils";
|
||||||
import IconComponent from "../genericIconComponent";
|
import IconComponent from "../genericIconComponent";
|
||||||
|
|
||||||
|
|
@ -40,28 +40,15 @@ export default function CodeTabsComponent({
|
||||||
setActiveTab,
|
setActiveTab,
|
||||||
isMessage,
|
isMessage,
|
||||||
tweaks,
|
tweaks,
|
||||||
}: {
|
}: codeTabsPropsType) {
|
||||||
flow?: FlowType;
|
|
||||||
tabs: any;
|
|
||||||
activeTab: string;
|
|
||||||
setActiveTab: any;
|
|
||||||
isMessage?: boolean;
|
|
||||||
tweaks?: {
|
|
||||||
tweak?: any;
|
|
||||||
tweaksList?: any;
|
|
||||||
buildContent?: any;
|
|
||||||
getValue?: any;
|
|
||||||
buildTweakObject?: any;
|
|
||||||
};
|
|
||||||
}) {
|
|
||||||
const [isCopied, setIsCopied] = useState<Boolean>(false);
|
const [isCopied, setIsCopied] = useState<Boolean>(false);
|
||||||
const [data, setData] = useState(flow ? flow["data"]["nodes"] : null);
|
const [data, setData] = useState(flow ? flow["data"]!["nodes"] : null);
|
||||||
const [openAccordion, setOpenAccordion] = useState([]);
|
const [openAccordion, setOpenAccordion] = useState<string[]>([]);
|
||||||
const { dark } = useContext(darkContext);
|
const { dark } = useContext(darkContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (flow && flow["data"]["nodes"]) {
|
if (flow && flow["data"]!["nodes"]) {
|
||||||
setData(flow["data"]["nodes"]);
|
setData(flow["data"]!["nodes"]);
|
||||||
}
|
}
|
||||||
}, [flow]);
|
}, [flow]);
|
||||||
|
|
||||||
|
|
@ -102,8 +89,8 @@ export default function CodeTabsComponent({
|
||||||
};
|
};
|
||||||
|
|
||||||
function openAccordions() {
|
function openAccordions() {
|
||||||
let accordionsToOpen = [];
|
let accordionsToOpen: string[] = [];
|
||||||
tweaks.tweak.current.forEach((el) => {
|
tweaks?.tweak!.current.forEach((el) => {
|
||||||
Object.keys(el).forEach((key) => {
|
Object.keys(el).forEach((key) => {
|
||||||
if (Object.keys(el[key]).length > 0) {
|
if (Object.keys(el[key]).length > 0) {
|
||||||
accordionsToOpen.push(key);
|
accordionsToOpen.push(key);
|
||||||
|
|
@ -205,9 +192,9 @@ export default function CodeTabsComponent({
|
||||||
: "overflow-hidden"
|
: "overflow-hidden"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{data.map((node: any, index) => (
|
{data?.map((node: any, index) => (
|
||||||
<div className="px-3" key={index}>
|
<div className="px-3" key={index}>
|
||||||
{tweaks.tweaksList.current.includes(
|
{tweaks?.tweaksList!.current.includes(
|
||||||
node["data"]["id"]
|
node["data"]["id"]
|
||||||
) && (
|
) && (
|
||||||
<AccordionComponent
|
<AccordionComponent
|
||||||
|
|
@ -290,14 +277,14 @@ export default function CodeTabsComponent({
|
||||||
setData((old) => {
|
setData((old) => {
|
||||||
let newInputList =
|
let newInputList =
|
||||||
cloneDeep(old);
|
cloneDeep(old);
|
||||||
newInputList[
|
newInputList![
|
||||||
index
|
index
|
||||||
].data.node.template[
|
].data.node.template[
|
||||||
templateField
|
templateField
|
||||||
].value = target;
|
].value = target;
|
||||||
return newInputList;
|
return newInputList;
|
||||||
});
|
});
|
||||||
tweaks.buildTweakObject(
|
tweaks.buildTweakObject!(
|
||||||
node["data"]["id"],
|
node["data"]["id"],
|
||||||
target,
|
target,
|
||||||
node.data.node.template[
|
node.data.node.template[
|
||||||
|
|
@ -310,7 +297,7 @@ export default function CodeTabsComponent({
|
||||||
templateField
|
templateField
|
||||||
].multiline ? (
|
].multiline ? (
|
||||||
<ShadTooltip
|
<ShadTooltip
|
||||||
content={tweaks.buildContent(
|
content={tweaks.buildContent!(
|
||||||
node.data.node.template[
|
node.data.node.template[
|
||||||
templateField
|
templateField
|
||||||
].value
|
].value
|
||||||
|
|
@ -339,14 +326,14 @@ export default function CodeTabsComponent({
|
||||||
setData((old) => {
|
setData((old) => {
|
||||||
let newInputList =
|
let newInputList =
|
||||||
cloneDeep(old);
|
cloneDeep(old);
|
||||||
newInputList[
|
newInputList![
|
||||||
index
|
index
|
||||||
].data.node.template[
|
].data.node.template[
|
||||||
templateField
|
templateField
|
||||||
].value = target;
|
].value = target;
|
||||||
return newInputList;
|
return newInputList;
|
||||||
});
|
});
|
||||||
tweaks.buildTweakObject(
|
tweaks.buildTweakObject!(
|
||||||
node["data"]["id"],
|
node["data"]["id"],
|
||||||
target,
|
target,
|
||||||
node.data.node
|
node.data.node
|
||||||
|
|
@ -384,14 +371,14 @@ export default function CodeTabsComponent({
|
||||||
setData((old) => {
|
setData((old) => {
|
||||||
let newInputList =
|
let newInputList =
|
||||||
cloneDeep(old);
|
cloneDeep(old);
|
||||||
newInputList[
|
newInputList![
|
||||||
index
|
index
|
||||||
].data.node.template[
|
].data.node.template[
|
||||||
templateField
|
templateField
|
||||||
].value = target;
|
].value = target;
|
||||||
return newInputList;
|
return newInputList;
|
||||||
});
|
});
|
||||||
tweaks.buildTweakObject(
|
tweaks.buildTweakObject!(
|
||||||
node["data"]["id"],
|
node["data"]["id"],
|
||||||
target,
|
target,
|
||||||
node.data.node.template[
|
node.data.node.template[
|
||||||
|
|
@ -417,14 +404,14 @@ export default function CodeTabsComponent({
|
||||||
setData((old) => {
|
setData((old) => {
|
||||||
let newInputList =
|
let newInputList =
|
||||||
cloneDeep(old);
|
cloneDeep(old);
|
||||||
newInputList[
|
newInputList![
|
||||||
index
|
index
|
||||||
].data.node.template[
|
].data.node.template[
|
||||||
templateField
|
templateField
|
||||||
].value = e;
|
].value = e;
|
||||||
return newInputList;
|
return newInputList;
|
||||||
});
|
});
|
||||||
tweaks.buildTweakObject(
|
tweaks.buildTweakObject!(
|
||||||
node["data"]["id"],
|
node["data"]["id"],
|
||||||
e,
|
e,
|
||||||
node.data.node.template[
|
node.data.node.template[
|
||||||
|
|
@ -440,7 +427,7 @@ export default function CodeTabsComponent({
|
||||||
templateField
|
templateField
|
||||||
].type === "file" ? (
|
].type === "file" ? (
|
||||||
<ShadTooltip
|
<ShadTooltip
|
||||||
content={tweaks.buildContent(
|
content={tweaks.buildContent!(
|
||||||
!node.data.node.template[
|
!node.data.node.template[
|
||||||
templateField
|
templateField
|
||||||
].value ||
|
].value ||
|
||||||
|
|
@ -508,14 +495,14 @@ export default function CodeTabsComponent({
|
||||||
setData((old) => {
|
setData((old) => {
|
||||||
let newInputList =
|
let newInputList =
|
||||||
cloneDeep(old);
|
cloneDeep(old);
|
||||||
newInputList[
|
newInputList![
|
||||||
index
|
index
|
||||||
].data.node.template[
|
].data.node.template[
|
||||||
templateField
|
templateField
|
||||||
].value = target;
|
].value = target;
|
||||||
return newInputList;
|
return newInputList;
|
||||||
});
|
});
|
||||||
tweaks.buildTweakObject(
|
tweaks.buildTweakObject!(
|
||||||
node["data"]["id"],
|
node["data"]["id"],
|
||||||
target,
|
target,
|
||||||
node.data.node.template[
|
node.data.node.template[
|
||||||
|
|
@ -544,14 +531,14 @@ export default function CodeTabsComponent({
|
||||||
setData((old) => {
|
setData((old) => {
|
||||||
let newInputList =
|
let newInputList =
|
||||||
cloneDeep(old);
|
cloneDeep(old);
|
||||||
newInputList[
|
newInputList![
|
||||||
index
|
index
|
||||||
].data.node.template[
|
].data.node.template[
|
||||||
templateField
|
templateField
|
||||||
].value = target;
|
].value = target;
|
||||||
return newInputList;
|
return newInputList;
|
||||||
});
|
});
|
||||||
tweaks.buildTweakObject(
|
tweaks.buildTweakObject!(
|
||||||
node["data"]["id"],
|
node["data"]["id"],
|
||||||
target,
|
target,
|
||||||
node.data.node.template[
|
node.data.node.template[
|
||||||
|
|
@ -596,14 +583,14 @@ export default function CodeTabsComponent({
|
||||||
setData((old) => {
|
setData((old) => {
|
||||||
let newInputList =
|
let newInputList =
|
||||||
cloneDeep(old);
|
cloneDeep(old);
|
||||||
newInputList[
|
newInputList![
|
||||||
index
|
index
|
||||||
].data.node.template[
|
].data.node.template[
|
||||||
templateField
|
templateField
|
||||||
].value = target;
|
].value = target;
|
||||||
return newInputList;
|
return newInputList;
|
||||||
});
|
});
|
||||||
tweaks.buildTweakObject(
|
tweaks.buildTweakObject!(
|
||||||
node["data"]["id"],
|
node["data"]["id"],
|
||||||
target,
|
target,
|
||||||
node.data.node.template[
|
node.data.node.template[
|
||||||
|
|
@ -617,7 +604,7 @@ export default function CodeTabsComponent({
|
||||||
templateField
|
templateField
|
||||||
].type === "prompt" ? (
|
].type === "prompt" ? (
|
||||||
<ShadTooltip
|
<ShadTooltip
|
||||||
content={tweaks.buildContent(
|
content={tweaks.buildContent!(
|
||||||
!node.data.node.template[
|
!node.data.node.template[
|
||||||
templateField
|
templateField
|
||||||
].value ||
|
].value ||
|
||||||
|
|
@ -651,14 +638,14 @@ export default function CodeTabsComponent({
|
||||||
setData((old) => {
|
setData((old) => {
|
||||||
let newInputList =
|
let newInputList =
|
||||||
cloneDeep(old);
|
cloneDeep(old);
|
||||||
newInputList[
|
newInputList![
|
||||||
index
|
index
|
||||||
].data.node.template[
|
].data.node.template[
|
||||||
templateField
|
templateField
|
||||||
].value = target;
|
].value = target;
|
||||||
return newInputList;
|
return newInputList;
|
||||||
});
|
});
|
||||||
tweaks.buildTweakObject(
|
tweaks.buildTweakObject!(
|
||||||
node["data"]["id"],
|
node["data"]["id"],
|
||||||
target,
|
target,
|
||||||
node.data.node.template[
|
node.data.node.template[
|
||||||
|
|
@ -673,8 +660,8 @@ export default function CodeTabsComponent({
|
||||||
templateField
|
templateField
|
||||||
].type === "code" ? (
|
].type === "code" ? (
|
||||||
<ShadTooltip
|
<ShadTooltip
|
||||||
content={tweaks.buildContent(
|
content={tweaks.buildContent!(
|
||||||
tweaks.getValue(
|
tweaks.getValue!(
|
||||||
node.data.node.template[
|
node.data.node.template[
|
||||||
templateField
|
templateField
|
||||||
].value,
|
].value,
|
||||||
|
|
@ -706,14 +693,14 @@ export default function CodeTabsComponent({
|
||||||
setData((old) => {
|
setData((old) => {
|
||||||
let newInputList =
|
let newInputList =
|
||||||
cloneDeep(old);
|
cloneDeep(old);
|
||||||
newInputList[
|
newInputList![
|
||||||
index
|
index
|
||||||
].data.node.template[
|
].data.node.template[
|
||||||
templateField
|
templateField
|
||||||
].value = target;
|
].value = target;
|
||||||
return newInputList;
|
return newInputList;
|
||||||
});
|
});
|
||||||
tweaks.buildTweakObject(
|
tweaks.buildTweakObject!(
|
||||||
node["data"]["id"],
|
node["data"]["id"],
|
||||||
target,
|
target,
|
||||||
node.data.node.template[
|
node.data.node.template[
|
||||||
|
|
@ -742,7 +729,7 @@ export default function CodeTabsComponent({
|
||||||
</AccordionComponent>
|
</AccordionComponent>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{tweaks.tweaksList.current.length === 0 && (
|
{tweaks?.tweaksList!.current.length === 0 && (
|
||||||
<>
|
<>
|
||||||
<div className="pt-3">
|
<div className="pt-3">
|
||||||
No tweaks are available for this flow.
|
No tweaks are available for this flow.
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export default function Dropdown({
|
||||||
editNode = false,
|
editNode = false,
|
||||||
numberOfOptions = 0,
|
numberOfOptions = 0,
|
||||||
apiModal = false,
|
apiModal = false,
|
||||||
}: DropDownComponentType) {
|
}: DropDownComponentType): JSX.Element {
|
||||||
let [internalValue, setInternalValue] = useState(
|
let [internalValue, setInternalValue] = useState(
|
||||||
value === "" || !value ? "Choose an option" : value
|
value === "" || !value ? "Choose an option" : value
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { FloatComponentType } from "../../types/components";
|
import { FloatComponentType } from "../../types/components";
|
||||||
|
import { handleKeyDown } from "../../utils/reactflowUtils";
|
||||||
import { Input } from "../ui/input";
|
import { Input } from "../ui/input";
|
||||||
|
|
||||||
export default function FloatComponent({
|
export default function FloatComponent({
|
||||||
|
|
@ -7,7 +8,7 @@ export default function FloatComponent({
|
||||||
onChange,
|
onChange,
|
||||||
disabled,
|
disabled,
|
||||||
editNode = false,
|
editNode = false,
|
||||||
}: FloatComponentType) {
|
}: FloatComponentType): JSX.Element {
|
||||||
const step = 0.1;
|
const step = 0.1;
|
||||||
const min = 0;
|
const min = 0;
|
||||||
const max = 1;
|
const max = 1;
|
||||||
|
|
@ -43,6 +44,9 @@ export default function FloatComponent({
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
onChange(event.target.value);
|
onChange(event.target.value);
|
||||||
}}
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
handleKeyDown(e, value, "0");
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
|
import { forwardRef } from "react";
|
||||||
import { IconComponentProps } from "../../types/components";
|
import { IconComponentProps } from "../../types/components";
|
||||||
import { nodeIconsLucide } from "../../utils/styleUtils";
|
import { nodeIconsLucide } from "../../utils/styleUtils";
|
||||||
|
|
||||||
export default function IconComponent({
|
const ForwardedIconComponent = forwardRef(
|
||||||
name,
|
({ name, className, iconColor }: IconComponentProps, ref) => {
|
||||||
className,
|
const TargetIcon = nodeIconsLucide[name] ?? nodeIconsLucide["unknown"];
|
||||||
iconColor,
|
return (
|
||||||
}: IconComponentProps): JSX.Element {
|
<TargetIcon
|
||||||
const TargetIcon = nodeIconsLucide[name] ?? nodeIconsLucide["unknown"];
|
strokeWidth={1.5}
|
||||||
return (
|
className={className}
|
||||||
<TargetIcon
|
style={iconColor ? { color: iconColor } : {}}
|
||||||
strokeWidth={1.5}
|
ref={ref}
|
||||||
className={className}
|
/>
|
||||||
style={{ color: iconColor }}
|
);
|
||||||
/>
|
}
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
export default ForwardedIconComponent;
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,11 @@ import { Link, useNavigate } from "react-router-dom";
|
||||||
import { alertContext } from "../../../../contexts/alertContext";
|
import { alertContext } from "../../../../contexts/alertContext";
|
||||||
import { undoRedoContext } from "../../../../contexts/undoRedoContext";
|
import { undoRedoContext } from "../../../../contexts/undoRedoContext";
|
||||||
import FlowSettingsModal from "../../../../modals/flowSettingsModal";
|
import FlowSettingsModal from "../../../../modals/flowSettingsModal";
|
||||||
|
import { menuBarPropsType } from "../../../../types/components";
|
||||||
import IconComponent from "../../../genericIconComponent";
|
import IconComponent from "../../../genericIconComponent";
|
||||||
import { Button } from "../../../ui/button";
|
import { Button } from "../../../ui/button";
|
||||||
|
|
||||||
export const MenuBar = ({ flows, tabId }) => {
|
export const MenuBar = ({ flows, tabId }: menuBarPropsType): JSX.Element => {
|
||||||
const { addFlow } = useContext(TabsContext);
|
const { addFlow } = useContext(TabsContext);
|
||||||
const { setErrorData } = useContext(alertContext);
|
const { setErrorData } = useContext(alertContext);
|
||||||
const { undo, redo } = useContext(undoRedoContext);
|
const { undo, redo } = useContext(undoRedoContext);
|
||||||
|
|
@ -25,12 +26,12 @@ export const MenuBar = ({ flows, tabId }) => {
|
||||||
|
|
||||||
function handleAddFlow() {
|
function handleAddFlow() {
|
||||||
try {
|
try {
|
||||||
addFlow(null, true).then((id) => {
|
addFlow(undefined, true).then((id) => {
|
||||||
navigate("/flow/" + id);
|
navigate("/flow/" + id);
|
||||||
});
|
});
|
||||||
// saveFlowStyleInDataBase();
|
// saveFlowStyleInDataBase();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setErrorData(err);
|
setErrorData(err as { title: string; list?: Array<string> });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let current_flow = flows.find((flow) => flow.id === tabId);
|
let current_flow = flows.find((flow) => flow.id === tabId);
|
||||||
|
|
@ -45,7 +46,9 @@ export const MenuBar = ({ flows, tabId }) => {
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button asChild variant="primary" size="sm">
|
<Button asChild variant="primary" size="sm">
|
||||||
<div className="header-menu-bar-display">
|
<div className="header-menu-bar-display">
|
||||||
<div className="header-menu-flow-name">{current_flow.name}</div>
|
<div className="header-menu-flow-name">
|
||||||
|
{current_flow!.name}
|
||||||
|
</div>
|
||||||
<IconComponent name="ChevronDown" className="h-4 w-4" />
|
<IconComponent name="ChevronDown" className="h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { FaDiscord, FaGithub, FaTwitter } from "react-icons/fa";
|
import { FaDiscord, FaGithub, FaTwitter } from "react-icons/fa";
|
||||||
import { Link, useLocation } from "react-router-dom";
|
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||||
import AlertDropdown from "../../alerts/alertDropDown";
|
import AlertDropdown from "../../alerts/alertDropDown";
|
||||||
import { USER_PROJECTS_HEADER } from "../../constants/constants";
|
import { USER_PROJECTS_HEADER } from "../../constants/constants";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
import { darkContext } from "../../contexts/darkContext";
|
import { darkContext } from "../../contexts/darkContext";
|
||||||
import { TabsContext } from "../../contexts/tabsContext";
|
import { TabsContext } from "../../contexts/tabsContext";
|
||||||
import { getRepoStars } from "../../controllers/API";
|
import { getRepoStars } from "../../controllers/API";
|
||||||
|
|
@ -12,31 +13,50 @@ import { Button } from "../ui/button";
|
||||||
import { Separator } from "../ui/separator";
|
import { Separator } from "../ui/separator";
|
||||||
import MenuBar from "./components/menuBar";
|
import MenuBar from "./components/menuBar";
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header(): JSX.Element {
|
||||||
const { flows, tabId } = useContext(TabsContext);
|
const { flows, tabId } = useContext(TabsContext);
|
||||||
const { dark, setDark } = useContext(darkContext);
|
const { dark, setDark } = useContext(darkContext);
|
||||||
const { notificationCenter } = useContext(alertContext);
|
const { notificationCenter } = useContext(alertContext);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const { logout, autoLogin, isAdmin, stars } = useContext(AuthContext);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [stars, setStars] = useState(null);
|
|
||||||
|
|
||||||
// Get and set numbers of stars on header
|
|
||||||
useEffect(() => {
|
|
||||||
async function fetchStars() {
|
|
||||||
const starsCount = await getRepoStars("logspace-ai", "langflow");
|
|
||||||
setStars(starsCount);
|
|
||||||
}
|
|
||||||
fetchStars();
|
|
||||||
}, []);
|
|
||||||
return (
|
return (
|
||||||
<div className="header-arrangement">
|
<div className="header-arrangement">
|
||||||
<div className="header-start-display">
|
<div className="header-start-display">
|
||||||
<Link to="/">
|
<Link to="/">
|
||||||
<span className="ml-4 text-2xl">⛓️</span>
|
<span className="ml-4 text-2xl">⛓️</span>
|
||||||
</Link>
|
</Link>
|
||||||
<Button variant="outline" className="">
|
{location.pathname === "/admin" && (
|
||||||
Sign out
|
<Button
|
||||||
</Button>
|
onClick={() => {
|
||||||
|
navigate("/");
|
||||||
|
}}
|
||||||
|
variant="outline"
|
||||||
|
className=""
|
||||||
|
>
|
||||||
|
Main page
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{autoLogin === false && (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
logout();
|
||||||
|
navigate("/login");
|
||||||
|
}}
|
||||||
|
variant="outline"
|
||||||
|
className=""
|
||||||
|
>
|
||||||
|
Sign out
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isAdmin && !autoLogin && location.pathname !== "/admin" && (
|
||||||
|
<Button variant="outline" onClick={() => navigate("/admin")}>
|
||||||
|
Admin page
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
{flows.findIndex((f) => tabId === f.id) !== -1 && tabId !== "" && (
|
{flows.findIndex((f) => tabId === f.id) !== -1 && tabId !== "" && (
|
||||||
<MenuBar flows={flows} tabId={tabId} />
|
<MenuBar flows={flows} tabId={tabId} />
|
||||||
)}
|
)}
|
||||||
|
|
@ -119,6 +139,18 @@ export default function Header() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</AlertDropdown>
|
</AlertDropdown>
|
||||||
|
{!autoLogin && (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
navigate("/account/api-keys");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconComponent
|
||||||
|
name="Key"
|
||||||
|
className="side-bar-button-size text-muted-foreground hover:text-accent-foreground"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import * as Form from "@radix-ui/react-form";
|
import * as Form from "@radix-ui/react-form";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { InputComponentType } from "../../types/components";
|
import { InputComponentType } from "../../types/components";
|
||||||
|
import { handleKeyDown } from "../../utils/reactflowUtils";
|
||||||
import { classNames } from "../../utils/utils";
|
import { classNames } from "../../utils/utils";
|
||||||
import { Input } from "../ui/input";
|
import { Input } from "../ui/input";
|
||||||
|
|
||||||
|
|
@ -14,7 +15,7 @@ export default function InputComponent({
|
||||||
editNode = false,
|
editNode = false,
|
||||||
placeholder = "Type something...",
|
placeholder = "Type something...",
|
||||||
className,
|
className,
|
||||||
}: InputComponentType) {
|
}: InputComponentType): JSX.Element {
|
||||||
const [pwdVisible, setPwdVisible] = useState(false);
|
const [pwdVisible, setPwdVisible] = useState(false);
|
||||||
|
|
||||||
// Clear component state
|
// Clear component state
|
||||||
|
|
@ -39,12 +40,15 @@ export default function InputComponent({
|
||||||
editNode ? " input-edit-node " : "",
|
editNode ? " input-edit-node " : "",
|
||||||
password && editNode ? "pr-8" : "",
|
password && editNode ? "pr-8" : "",
|
||||||
password && !editNode ? "pr-10" : "",
|
password && !editNode ? "pr-10" : "",
|
||||||
className
|
className!
|
||||||
)}
|
)}
|
||||||
placeholder={password && editNode ? "Key" : placeholder}
|
placeholder={password && editNode ? "Key" : placeholder}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
onChange(e.target.value);
|
onChange(e.target.value);
|
||||||
}}
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
handleKeyDown(e, value, "");
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -59,12 +63,15 @@ export default function InputComponent({
|
||||||
editNode ? " input-edit-node " : "",
|
editNode ? " input-edit-node " : "",
|
||||||
password && editNode ? "pr-8" : "",
|
password && editNode ? "pr-8" : "",
|
||||||
password && !editNode ? "pr-10" : "",
|
password && !editNode ? "pr-10" : "",
|
||||||
className
|
className!
|
||||||
)}
|
)}
|
||||||
placeholder={password && editNode ? "Key" : placeholder}
|
placeholder={password && editNode ? "Key" : placeholder}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
onChange(e.target.value);
|
onChange(e.target.value);
|
||||||
}}
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
handleKeyDown(e, value, "");
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{password && (
|
{password && (
|
||||||
|
|
@ -74,7 +81,8 @@ export default function InputComponent({
|
||||||
? "input-component-true-button"
|
? "input-component-true-button"
|
||||||
: "input-component-false-button"
|
: "input-component-false-button"
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
setPwdVisible(!pwdVisible);
|
setPwdVisible(!pwdVisible);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ export default function InputFileComponent({
|
||||||
fileTypes,
|
fileTypes,
|
||||||
onFileChange,
|
onFileChange,
|
||||||
editNode = false,
|
editNode = false,
|
||||||
}: FileComponentType) {
|
}: FileComponentType): JSX.Element {
|
||||||
const [myValue, setMyValue] = useState(value);
|
const [myValue, setMyValue] = useState(value);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { setErrorData } = useContext(alertContext);
|
const { setErrorData } = useContext(alertContext);
|
||||||
|
|
@ -41,7 +41,7 @@ export default function InputFileComponent({
|
||||||
setMyValue(value);
|
setMyValue(value);
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
const handleButtonClick = () => {
|
const handleButtonClick = (): void => {
|
||||||
// Create a file input element
|
// Create a file input element
|
||||||
const input = document.createElement("input");
|
const input = document.createElement("input");
|
||||||
input.type = "file";
|
input.type = "file";
|
||||||
|
|
@ -49,7 +49,7 @@ export default function InputFileComponent({
|
||||||
input.style.display = "none"; // Hidden from view
|
input.style.display = "none"; // Hidden from view
|
||||||
input.multiple = false; // Allow only one file selection
|
input.multiple = false; // Allow only one file selection
|
||||||
|
|
||||||
input.onchange = (event: Event) => {
|
input.onchange = (event: Event): void => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
// Get the selected file
|
// Get the selected file
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export default function InputListComponent({
|
||||||
onChange,
|
onChange,
|
||||||
disabled,
|
disabled,
|
||||||
editNode = false,
|
editNode = false,
|
||||||
}: InputListComponentType) {
|
}: InputListComponentType): JSX.Element {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
onChange([""]);
|
onChange([""]);
|
||||||
|
|
@ -39,6 +39,12 @@ export default function InputListComponent({
|
||||||
newInputList[idx] = event.target.value;
|
newInputList[idx] = event.target.value;
|
||||||
onChange(newInputList);
|
onChange(newInputList);
|
||||||
}}
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.ctrlKey && e.key === "Backspace") {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{idx === value.length - 1 ? (
|
{idx === value.length - 1 ? (
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { FloatComponentType } from "../../types/components";
|
import { FloatComponentType } from "../../types/components";
|
||||||
|
import { handleKeyDown } from "../../utils/reactflowUtils";
|
||||||
import { Input } from "../ui/input";
|
import { Input } from "../ui/input";
|
||||||
|
|
||||||
export default function IntComponent({
|
export default function IntComponent({
|
||||||
|
|
@ -7,7 +8,7 @@ export default function IntComponent({
|
||||||
onChange,
|
onChange,
|
||||||
disabled,
|
disabled,
|
||||||
editNode = false,
|
editNode = false,
|
||||||
}: FloatComponentType) {
|
}: FloatComponentType): JSX.Element {
|
||||||
const min = 0;
|
const min = 0;
|
||||||
|
|
||||||
// Clear component state
|
// Clear component state
|
||||||
|
|
@ -37,6 +38,7 @@ export default function IntComponent({
|
||||||
) {
|
) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
handleKeyDown(event, value, "0");
|
||||||
}}
|
}}
|
||||||
type="number"
|
type="number"
|
||||||
step="1"
|
step="1"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
type LoadingComponentProps = {
|
import { LoadingComponentProps } from "../../types/components";
|
||||||
remSize: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function LoadingComponent({ remSize }: LoadingComponentProps) {
|
export default function LoadingComponent({
|
||||||
|
remSize,
|
||||||
|
}: LoadingComponentProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div role="status" className="m-auto w-min">
|
<div role="status" className="m-auto w-min">
|
||||||
<svg
|
<svg
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export default function PromptAreaComponent({
|
||||||
onChange,
|
onChange,
|
||||||
disabled,
|
disabled,
|
||||||
editNode = false,
|
editNode = false,
|
||||||
}: TextAreaComponentType) {
|
}: TextAreaComponentType): JSX.Element {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
onChange("");
|
onChange("");
|
||||||
|
|
@ -23,9 +23,9 @@ export default function PromptAreaComponent({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (value !== "" && !editNode) {
|
if (value !== "" && !editNode) {
|
||||||
postValidatePrompt(field_name, value, nodeClass).then((apiReturn) => {
|
postValidatePrompt(field_name!, value, nodeClass!).then((apiReturn) => {
|
||||||
if (apiReturn.data) {
|
if (apiReturn.data) {
|
||||||
setNodeClass(apiReturn.data.frontend_node);
|
setNodeClass!(apiReturn.data.frontend_node);
|
||||||
// need to update reactFlowInstance to re-render the nodes.
|
// need to update reactFlowInstance to re-render the nodes.
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ export default function TextAreaComponent({
|
||||||
onChange,
|
onChange,
|
||||||
disabled,
|
disabled,
|
||||||
editNode = false,
|
editNode = false,
|
||||||
}: TextAreaComponentType) {
|
}: TextAreaComponentType): JSX.Element {
|
||||||
// Clear text area
|
// Clear text area
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ export default function ToggleComponent({
|
||||||
enabled,
|
enabled,
|
||||||
setEnabled,
|
setEnabled,
|
||||||
disabled,
|
disabled,
|
||||||
}: ToggleComponentType) {
|
}: ToggleComponentType): JSX.Element {
|
||||||
// set component state as disabled
|
// set component state as disabled
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ export default function ToggleShadComponent({
|
||||||
setEnabled,
|
setEnabled,
|
||||||
disabled,
|
disabled,
|
||||||
size,
|
size,
|
||||||
}: ToggleComponentType) {
|
}: ToggleComponentType): JSX.Element {
|
||||||
let scaleX, scaleY;
|
let scaleX, scaleY;
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case "small":
|
case "small":
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,14 @@ export default function RenameLabel(props) {
|
||||||
});
|
});
|
||||||
if (inputRef.current) {
|
if (inputRef.current) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
inputRef.current.focus();
|
inputRef.current?.focus();
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resizeInput();
|
resizeInput();
|
||||||
}, [isRename]);
|
}, [isRename]);
|
||||||
|
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
const resizeInput = () => {
|
const resizeInput = () => {
|
||||||
const input = inputRef.current;
|
const input = inputRef.current;
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ export const INVALID_CHARACTERS = [
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const regexHighlight = /\{([^}]+)\}/g;
|
export const regexHighlight = /\{([^}]+)\}/g;
|
||||||
|
export const specialCharsRegex = /[!@#$%^&*()\-_=+[\]{}|;:'",.<>/?\\`´]/;
|
||||||
|
|
||||||
export const programmingLanguages: languageMap = {
|
export const programmingLanguages: languageMap = {
|
||||||
javascript: ".js",
|
javascript: ".js",
|
||||||
|
|
@ -507,7 +508,11 @@ export const URL_EXCLUDED_FROM_ERROR_RETRIES = [
|
||||||
"/api/v1/validate/code",
|
"/api/v1/validate/code",
|
||||||
"/api/v1/custom_component",
|
"/api/v1/custom_component",
|
||||||
"/api/v1/validate/prompt",
|
"/api/v1/validate/prompt",
|
||||||
|
"http://localhost:7860/login",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const skipNodeUpdate = ["CustomComponent"];
|
||||||
|
|
||||||
export const CONTROL_INPUT_STATE = {
|
export const CONTROL_INPUT_STATE = {
|
||||||
password: "",
|
password: "",
|
||||||
cnfPassword: "",
|
cnfPassword: "",
|
||||||
|
|
@ -518,6 +523,18 @@ export const CONTROL_LOGIN_STATE = {
|
||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const CONTROL_NEW_USER = {
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
is_active: false,
|
||||||
|
is_superuser: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CONTROL_NEW_API_KEY = {
|
||||||
|
apikeyname: "",
|
||||||
|
};
|
||||||
|
|
||||||
export const tabsCode = [];
|
export const tabsCode = [];
|
||||||
|
|
||||||
export function tabsArray(codes: string[], method: number) {
|
export function tabsArray(codes: string[], method: number) {
|
||||||
|
|
@ -598,3 +615,8 @@ export function tabsArray(codes: string[], method: number) {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const BASE_URL_API = "http://localhost:7860/";
|
||||||
|
|
||||||
|
export const SIGN_UP_SUCCESS =
|
||||||
|
"Congratulations on signing up! Your account is currently awaiting activation by our admin team.";
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,15 @@
|
||||||
import { createContext, ReactNode, useState } from "react";
|
import { createContext, ReactNode, useState } from "react";
|
||||||
import { AlertItemType } from "../types/alerts";
|
import { AlertItemType } from "../types/alerts";
|
||||||
|
import { alertContextType } from "../types/typesContext";
|
||||||
|
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
//types for alertContextType
|
|
||||||
type alertContextType = {
|
|
||||||
errorData: { title: string; list?: Array<string> };
|
|
||||||
setErrorData: (newState: { title: string; list?: Array<string> }) => void;
|
|
||||||
errorOpen: boolean;
|
|
||||||
setErrorOpen: (newState: boolean) => void;
|
|
||||||
noticeData: { title: string; link?: string };
|
|
||||||
setNoticeData: (newState: { title: string; link?: string }) => void;
|
|
||||||
noticeOpen: boolean;
|
|
||||||
setNoticeOpen: (newState: boolean) => void;
|
|
||||||
successData: { title: string };
|
|
||||||
setSuccessData: (newState: { title: string }) => void;
|
|
||||||
successOpen: boolean;
|
|
||||||
setSuccessOpen: (newState: boolean) => void;
|
|
||||||
notificationCenter: boolean;
|
|
||||||
setNotificationCenter: (newState: boolean) => void;
|
|
||||||
notificationList: Array<AlertItemType>;
|
|
||||||
pushNotificationList: (Object: AlertItemType) => void;
|
|
||||||
clearNotificationList: () => void;
|
|
||||||
removeFromNotificationList: (index: string) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
//initial values to alertContextType
|
//initial values to alertContextType
|
||||||
const initialValue: alertContextType = {
|
const initialValue: alertContextType = {
|
||||||
errorData: { title: "", list: [] },
|
errorData: { title: "", list: [] },
|
||||||
setErrorData: () => {},
|
setErrorData: () => {},
|
||||||
|
loading: true,
|
||||||
|
setLoading: () => {},
|
||||||
errorOpen: false,
|
errorOpen: false,
|
||||||
setErrorOpen: () => {},
|
setErrorOpen: () => {},
|
||||||
noticeData: { title: "", link: "" },
|
noticeData: { title: "", link: "" },
|
||||||
|
|
@ -55,6 +36,7 @@ export function AlertProvider({ children }: { children: ReactNode }) {
|
||||||
list?: Array<string>;
|
list?: Array<string>;
|
||||||
}>({ title: "", list: [] });
|
}>({ title: "", list: [] });
|
||||||
const [errorOpen, setErrorOpen] = useState(false);
|
const [errorOpen, setErrorOpen] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
const [noticeData, setNoticeDataState] = useState<{
|
const [noticeData, setNoticeDataState] = useState<{
|
||||||
title: string;
|
title: string;
|
||||||
link?: string;
|
link?: string;
|
||||||
|
|
@ -65,7 +47,7 @@ export function AlertProvider({ children }: { children: ReactNode }) {
|
||||||
});
|
});
|
||||||
const [successOpen, setSuccessOpen] = useState(false);
|
const [successOpen, setSuccessOpen] = useState(false);
|
||||||
const [notificationCenter, setNotificationCenter] = useState(false);
|
const [notificationCenter, setNotificationCenter] = useState(false);
|
||||||
const [notificationList, setNotificationList] = useState([]);
|
const [notificationList, setNotificationList] = useState<AlertItemType[]>([]);
|
||||||
const pushNotificationList = (notification: AlertItemType) => {
|
const pushNotificationList = (notification: AlertItemType) => {
|
||||||
setNotificationList((old) => {
|
setNotificationList((old) => {
|
||||||
let newNotificationList = _.cloneDeep(old);
|
let newNotificationList = _.cloneDeep(old);
|
||||||
|
|
@ -141,6 +123,8 @@ export function AlertProvider({ children }: { children: ReactNode }) {
|
||||||
removeFromNotificationList,
|
removeFromNotificationList,
|
||||||
clearNotificationList,
|
clearNotificationList,
|
||||||
notificationList,
|
notificationList,
|
||||||
|
loading,
|
||||||
|
setLoading,
|
||||||
pushNotificationList,
|
pushNotificationList,
|
||||||
setNotificationCenter,
|
setNotificationCenter,
|
||||||
notificationCenter,
|
notificationCenter,
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,87 @@
|
||||||
import { createContext, useEffect, useState } from "react";
|
import { createContext, useEffect, useState } from "react";
|
||||||
import { AuthContextType, userData } from "../types/contexts/auth";
|
import Cookies from "universal-cookie";
|
||||||
|
import { getLoggedUser, getRepoStars } from "../controllers/API";
|
||||||
|
import { Users } from "../types/api";
|
||||||
|
import { AuthContextType } from "../types/contexts/auth";
|
||||||
|
|
||||||
const initialValue: AuthContextType = {
|
const initialValue: AuthContextType = {
|
||||||
|
isAdmin: false,
|
||||||
|
setIsAdmin: () => false,
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
accessToken: null,
|
accessToken: null,
|
||||||
|
refreshToken: null,
|
||||||
login: () => {},
|
login: () => {},
|
||||||
logout: () => {},
|
logout: () => {},
|
||||||
refreshAccessToken: () => Promise.resolve(),
|
refreshAccessToken: () => Promise.resolve(),
|
||||||
userData: null,
|
userData: null,
|
||||||
setUserData: () => {},
|
setUserData: () => {},
|
||||||
|
getAuthentication: () => false,
|
||||||
|
authenticationErrorCount: 0,
|
||||||
|
autoLogin: false,
|
||||||
|
setAutoLogin: () => {},
|
||||||
|
stars: 0,
|
||||||
|
setStars: (stars) => 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const AuthContext = createContext<AuthContextType>(initialValue);
|
export const AuthContext = createContext<AuthContextType>(initialValue);
|
||||||
|
|
||||||
export function AuthProvider({ children }): React.ReactElement {
|
export function AuthProvider({ children }): React.ReactElement {
|
||||||
const [accessToken, setAccessToken] = useState<string | null>(null);
|
const [accessToken, setAccessToken] = useState<string | null>(null);
|
||||||
const [userData, setUserData] = useState<userData>(null);
|
const [refreshToken, setRefreshToken] = useState<string | null>(null);
|
||||||
|
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
|
||||||
|
const [isAdmin, setIsAdmin] = useState<boolean>(false);
|
||||||
|
const [userData, setUserData] = useState<Users | null>(null);
|
||||||
|
const [autoLogin, setAutoLogin] = useState<boolean>(false);
|
||||||
|
const [stars, setStars] = useState<number>(0);
|
||||||
|
const cookies = new Cookies();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const storedAccessToken = localStorage.getItem("access_token");
|
const storedAccessToken = cookies.get("access_token");
|
||||||
if (storedAccessToken) {
|
if (storedAccessToken) {
|
||||||
setAccessToken(storedAccessToken);
|
setAccessToken(storedAccessToken);
|
||||||
}
|
}
|
||||||
|
async function fetchStars() {
|
||||||
|
const starsCount = await getRepoStars("logspace-ai", "langflow");
|
||||||
|
setStars(starsCount);
|
||||||
|
}
|
||||||
|
fetchStars();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (accessToken) {
|
||||||
|
getLoggedUser().then((user) => {
|
||||||
|
const isSuperUser = user.is_superuser;
|
||||||
|
setIsAdmin(isSuperUser);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [accessToken, isAdmin]);
|
||||||
|
|
||||||
|
function getAuthentication() {
|
||||||
|
const storedRefreshToken = cookies.get("refresh_token");
|
||||||
|
const storedAccess = cookies.get("access_token");
|
||||||
|
const auth = storedAccess && storedRefreshToken ? true : false;
|
||||||
|
return auth;
|
||||||
|
}
|
||||||
|
|
||||||
function login(newAccessToken: string, refreshToken: string) {
|
function login(newAccessToken: string, refreshToken: string) {
|
||||||
localStorage.setItem("access_token", newAccessToken);
|
cookies.set("access_token", newAccessToken, { path: "/" });
|
||||||
|
cookies.set("refresh_token", refreshToken, { path: "/" });
|
||||||
setAccessToken(newAccessToken);
|
setAccessToken(newAccessToken);
|
||||||
// Store refreshToken if needed
|
setRefreshToken(refreshToken);
|
||||||
|
setIsAuthenticated(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
localStorage.removeItem("access_token");
|
cookies.remove("access_token", { path: "/" });
|
||||||
// Clear refreshToken if used
|
cookies.remove("refresh_token", { path: "/" });
|
||||||
|
setUserData(null);
|
||||||
setAccessToken(null);
|
setAccessToken(null);
|
||||||
|
setRefreshToken(null);
|
||||||
|
setIsAuthenticated(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshAccessToken(refreshToken: string) {
|
async function refreshAccessToken(refreshToken: string) {
|
||||||
try {
|
try {
|
||||||
// Call your API to refresh the access token using the refresh token
|
|
||||||
const response = await fetch("/api/refresh-token", {
|
const response = await fetch("/api/refresh-token", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -50,6 +93,9 @@ export function AuthProvider({ children }): React.ReactElement {
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
login(data.accessToken, refreshToken);
|
login(data.accessToken, refreshToken);
|
||||||
|
getLoggedUser().then((user) => {
|
||||||
|
console.log("oi");
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
logout();
|
logout();
|
||||||
}
|
}
|
||||||
|
|
@ -62,13 +108,22 @@ export function AuthProvider({ children }): React.ReactElement {
|
||||||
// !! to convert string to boolean
|
// !! to convert string to boolean
|
||||||
<AuthContext.Provider
|
<AuthContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
stars,
|
||||||
|
setStars,
|
||||||
|
isAdmin,
|
||||||
|
setIsAdmin,
|
||||||
isAuthenticated: !!accessToken,
|
isAuthenticated: !!accessToken,
|
||||||
accessToken,
|
accessToken,
|
||||||
|
refreshToken,
|
||||||
login,
|
login,
|
||||||
logout,
|
logout,
|
||||||
refreshAccessToken,
|
refreshAccessToken,
|
||||||
setUserData,
|
setUserData,
|
||||||
userData,
|
userData,
|
||||||
|
getAuthentication,
|
||||||
|
authenticationErrorCount: 0,
|
||||||
|
setAutoLogin,
|
||||||
|
autoLogin,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
import { createContext, useEffect, useState } from "react";
|
import { createContext, useEffect, useState } from "react";
|
||||||
|
import { darkContextType } from "../types/typesContext";
|
||||||
type darkContextType = {
|
|
||||||
dark: {};
|
|
||||||
setDark: (newState: {}) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
const initialValue = {
|
const initialValue = {
|
||||||
dark: {},
|
dark: {},
|
||||||
|
|
@ -14,13 +10,13 @@ export const darkContext = createContext<darkContextType>(initialValue);
|
||||||
|
|
||||||
export function DarkProvider({ children }) {
|
export function DarkProvider({ children }) {
|
||||||
const [dark, setDark] = useState(
|
const [dark, setDark] = useState(
|
||||||
JSON.parse(window.localStorage.getItem("isDark")) ?? false
|
JSON.parse(window.localStorage.getItem("isDark")!) ?? false
|
||||||
);
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dark) {
|
if (dark) {
|
||||||
document.getElementById("body").classList.add("dark");
|
document.getElementById("body")!.classList.add("dark");
|
||||||
} else {
|
} else {
|
||||||
document.getElementById("body").classList.remove("dark");
|
document.getElementById("body")!.classList.remove("dark");
|
||||||
}
|
}
|
||||||
window.localStorage.setItem("isDark", dark.toString());
|
window.localStorage.setItem("isDark", dark.toString());
|
||||||
}, [dark]);
|
}, [dark]);
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { ReactFlowProvider } from "reactflow";
|
||||||
import { TooltipProvider } from "../components/ui/tooltip";
|
import { TooltipProvider } from "../components/ui/tooltip";
|
||||||
import { SSEProvider } from "./SSEContext";
|
import { SSEProvider } from "./SSEContext";
|
||||||
import { AlertProvider } from "./alertContext";
|
import { AlertProvider } from "./alertContext";
|
||||||
|
import { AuthProvider } from "./authContext";
|
||||||
import { DarkProvider } from "./darkContext";
|
import { DarkProvider } from "./darkContext";
|
||||||
import { LocationProvider } from "./locationContext";
|
import { LocationProvider } from "./locationContext";
|
||||||
import { TabsProvider } from "./tabsContext";
|
import { TabsProvider } from "./tabsContext";
|
||||||
|
|
@ -13,23 +14,25 @@ export default function ContextWrapper({ children }: { children: ReactNode }) {
|
||||||
//element to wrap all context
|
//element to wrap all context
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TooltipProvider>
|
<AuthProvider>
|
||||||
<ReactFlowProvider>
|
<TooltipProvider>
|
||||||
<DarkProvider>
|
<ReactFlowProvider>
|
||||||
<TypesProvider>
|
<DarkProvider>
|
||||||
<LocationProvider>
|
<TypesProvider>
|
||||||
<AlertProvider>
|
<LocationProvider>
|
||||||
<SSEProvider>
|
<AlertProvider>
|
||||||
<TabsProvider>
|
<SSEProvider>
|
||||||
<UndoRedoProvider>{children}</UndoRedoProvider>
|
<TabsProvider>
|
||||||
</TabsProvider>
|
<UndoRedoProvider>{children}</UndoRedoProvider>
|
||||||
</SSEProvider>
|
</TabsProvider>
|
||||||
</AlertProvider>
|
</SSEProvider>
|
||||||
</LocationProvider>
|
</AlertProvider>
|
||||||
</TypesProvider>
|
</LocationProvider>
|
||||||
</DarkProvider>
|
</TypesProvider>
|
||||||
</ReactFlowProvider>
|
</DarkProvider>
|
||||||
</TooltipProvider>
|
</ReactFlowProvider>
|
||||||
|
</TooltipProvider>
|
||||||
|
</AuthProvider>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,5 @@
|
||||||
import { createContext, ReactNode, useState } from "react";
|
import { createContext, ReactNode, useState } from "react";
|
||||||
|
import { locationContextType } from "../types/typesContext";
|
||||||
//types for location context
|
|
||||||
type locationContextType = {
|
|
||||||
current: Array<string>;
|
|
||||||
setCurrent: (newState: Array<string>) => void;
|
|
||||||
isStackedOpen: boolean;
|
|
||||||
setIsStackedOpen: (newState: boolean) => void;
|
|
||||||
showSideBar: boolean;
|
|
||||||
setShowSideBar: (newState: boolean) => void;
|
|
||||||
extraNavigation: {
|
|
||||||
title: string;
|
|
||||||
options?: Array<{
|
|
||||||
name: string;
|
|
||||||
href: string;
|
|
||||||
icon: any;
|
|
||||||
children?: Array<any>;
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
setExtraNavigation: (newState: {
|
|
||||||
title: string;
|
|
||||||
options?: Array<{
|
|
||||||
name: string;
|
|
||||||
href: string;
|
|
||||||
icon: any;
|
|
||||||
children?: Array<any>;
|
|
||||||
}>;
|
|
||||||
}) => void;
|
|
||||||
extraComponent: any;
|
|
||||||
setExtraComponent: (newState: any) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
//initial value for location context
|
//initial value for location context
|
||||||
const initialValue = {
|
const initialValue = {
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@ import {
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { addEdge } from "reactflow";
|
import { Edge, Node, ReactFlowJsonObject, addEdge } from "reactflow";
|
||||||
import ShortUniqueId from "short-unique-id";
|
import ShortUniqueId from "short-unique-id";
|
||||||
|
import { skipNodeUpdate } from "../constants/constants";
|
||||||
import {
|
import {
|
||||||
deleteFlowFromDatabase,
|
deleteFlowFromDatabase,
|
||||||
downloadFlowsFromDatabase,
|
downloadFlowsFromDatabase,
|
||||||
|
|
@ -18,8 +19,9 @@ import {
|
||||||
uploadFlowsToDatabase,
|
uploadFlowsToDatabase,
|
||||||
} from "../controllers/API";
|
} from "../controllers/API";
|
||||||
import { APIClassType, APITemplateType } from "../types/api";
|
import { APIClassType, APITemplateType } from "../types/api";
|
||||||
import { FlowType, NodeType } from "../types/flow";
|
import { tweakType } from "../types/components";
|
||||||
import { TabsContextType, TabsState } from "../types/tabs";
|
import { FlowType, NodeDataType, NodeType } from "../types/flow";
|
||||||
|
import { TabsContextType, TabsState, errorsVarType } from "../types/tabs";
|
||||||
import {
|
import {
|
||||||
addVersionToDuplicates,
|
addVersionToDuplicates,
|
||||||
updateIds,
|
updateIds,
|
||||||
|
|
@ -73,9 +75,12 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
||||||
const [id, setId] = useState(uid());
|
const [id, setId] = useState(uid());
|
||||||
const { templates, reactFlowInstance } = useContext(typesContext);
|
const { templates, reactFlowInstance } = useContext(typesContext);
|
||||||
const [lastCopiedSelection, setLastCopiedSelection] = useState(null);
|
const [lastCopiedSelection, setLastCopiedSelection] = useState<{
|
||||||
|
nodes: any;
|
||||||
|
edges: any;
|
||||||
|
} | null>(null);
|
||||||
const [tabsState, setTabsState] = useState<TabsState>({});
|
const [tabsState, setTabsState] = useState<TabsState>({});
|
||||||
const [getTweak, setTweak] = useState([]);
|
const [getTweak, setTweak] = useState<tweakType>([]);
|
||||||
|
|
||||||
const newNodeId = useRef(uid());
|
const newNodeId = useRef(uid());
|
||||||
function incrementNodeId() {
|
function incrementNodeId() {
|
||||||
|
|
@ -130,8 +135,8 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
//get tabs from db
|
//get tabs from db
|
||||||
return readFlowsFromDatabase();
|
return readFlowsFromDatabase();
|
||||||
}
|
}
|
||||||
function processDBData(DbData) {
|
function processDBData(DbData: FlowType[]) {
|
||||||
DbData.forEach((flow) => {
|
DbData.forEach((flow: FlowType) => {
|
||||||
try {
|
try {
|
||||||
if (!flow.data) {
|
if (!flow.data) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -144,7 +149,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function processFlowEdges(flow) {
|
function processFlowEdges(flow: FlowType) {
|
||||||
if (!flow.data || !flow.data.edges) return;
|
if (!flow.data || !flow.data.edges) return;
|
||||||
flow.data.edges.forEach((edge) => {
|
flow.data.edges.forEach((edge) => {
|
||||||
edge.className = "";
|
edge.className = "";
|
||||||
|
|
@ -153,16 +158,17 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateDisplay_name(node: NodeType, template: APIClassType) {
|
function updateDisplay_name(node: NodeType, template: APIClassType) {
|
||||||
node.data.node.display_name = template["display_name"] || node.data.type;
|
node.data.node!.display_name = template["display_name"] || node.data.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateNodeDocumentation(node: NodeType, template: APIClassType) {
|
function updateNodeDocumentation(node: NodeType, template: APIClassType) {
|
||||||
node.data.node.documentation = template["documentation"];
|
node.data.node!.documentation = template["documentation"];
|
||||||
}
|
}
|
||||||
|
|
||||||
function processFlowNodes(flow) {
|
function processFlowNodes(flow: FlowType) {
|
||||||
if (!flow.data || !flow.data.nodes) return;
|
if (!flow.data || !flow.data.nodes) return;
|
||||||
flow.data.nodes.forEach((node: NodeType) => {
|
flow.data.nodes.forEach((node: NodeType) => {
|
||||||
|
if (skipNodeUpdate.includes(node.data.type)) return;
|
||||||
const template = templates[node.data.type];
|
const template = templates[node.data.type];
|
||||||
if (!template) {
|
if (!template) {
|
||||||
setErrorData({ title: `Unknown node type: ${node.data.type}` });
|
setErrorData({ title: `Unknown node type: ${node.data.type}` });
|
||||||
|
|
@ -180,7 +186,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateNodeBaseClasses(node: NodeType, template: APIClassType) {
|
function updateNodeBaseClasses(node: NodeType, template: APIClassType) {
|
||||||
node.data.node.base_classes = template["base_classes"];
|
node.data.node!.base_classes = template["base_classes"];
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateNodeEdges(
|
function updateNodeEdges(
|
||||||
|
|
@ -188,10 +194,10 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
node: NodeType,
|
node: NodeType,
|
||||||
template: APIClassType
|
template: APIClassType
|
||||||
) {
|
) {
|
||||||
flow.data.edges.forEach((edge) => {
|
flow.data!.edges.forEach((edge) => {
|
||||||
if (edge.source === node.id) {
|
if (edge.source === node.id) {
|
||||||
edge.sourceHandle = edge.sourceHandle
|
edge.sourceHandle = edge.sourceHandle
|
||||||
.split("|")
|
?.split("|")
|
||||||
.slice(0, 2)
|
.slice(0, 2)
|
||||||
.concat(template["base_classes"])
|
.concat(template["base_classes"])
|
||||||
.join("|");
|
.join("|");
|
||||||
|
|
@ -200,17 +206,17 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateNodeDescription(node: NodeType, template: APIClassType) {
|
function updateNodeDescription(node: NodeType, template: APIClassType) {
|
||||||
node.data.node.description = template["description"];
|
node.data.node!.description = template["description"];
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateNodeTemplate(node: NodeType, template: APIClassType) {
|
function updateNodeTemplate(node: NodeType, template: APIClassType) {
|
||||||
node.data.node.template = updateTemplate(
|
node.data.node!.template = updateTemplate(
|
||||||
template["template"] as unknown as APITemplateType,
|
template["template"] as unknown as APITemplateType,
|
||||||
node.data.node.template as APITemplateType
|
node.data.node!.template as APITemplateType
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateStateWithDbData(tabsData) {
|
function updateStateWithDbData(tabsData: FlowType[]) {
|
||||||
setFlows(tabsData);
|
setFlows(tabsData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -241,7 +247,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
link.download = `${
|
link.download = `${
|
||||||
flowName && flowName != ""
|
flowName && flowName != ""
|
||||||
? flowName
|
? flowName
|
||||||
: flows.find((f) => f.id === tabId).name
|
: flows.find((f) => f.id === tabId)!.name
|
||||||
}.json`;
|
}.json`;
|
||||||
|
|
||||||
// simulate a click on the link element to trigger the download
|
// simulate a click on the link element to trigger the download
|
||||||
|
|
@ -293,10 +299,10 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
input.onchange = (e: Event) => {
|
input.onchange = (e: Event) => {
|
||||||
// check if the file type is application/json
|
// check if the file type is application/json
|
||||||
if (
|
if (
|
||||||
(e.target as HTMLInputElement).files[0].type === "application/json"
|
(e.target as HTMLInputElement).files![0].type === "application/json"
|
||||||
) {
|
) {
|
||||||
// get the file from the file input
|
// get the file from the file input
|
||||||
const currentfile = (e.target as HTMLInputElement).files[0];
|
const currentfile = (e.target as HTMLInputElement).files![0];
|
||||||
// read the file as text
|
// read the file as text
|
||||||
currentfile.text().then((text) => {
|
currentfile.text().then((text) => {
|
||||||
// parse the text into a JSON object
|
// parse the text into a JSON object
|
||||||
|
|
@ -319,10 +325,10 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
input.onchange = (event: Event) => {
|
input.onchange = (event: Event) => {
|
||||||
// check if the file type is application/json
|
// check if the file type is application/json
|
||||||
if (
|
if (
|
||||||
(event.target as HTMLInputElement).files[0].type === "application/json"
|
(event.target as HTMLInputElement).files![0].type === "application/json"
|
||||||
) {
|
) {
|
||||||
// get the file from the file input
|
// get the file from the file input
|
||||||
const file = (event.target as HTMLInputElement).files[0];
|
const file = (event.target as HTMLInputElement).files![0];
|
||||||
// read the file as text
|
// read the file as text
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", file);
|
formData.append("file", file);
|
||||||
|
|
@ -353,15 +359,15 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function paste(
|
function paste(
|
||||||
selectionInstance,
|
selectionInstance: { nodes: Node[]; edges: Edge[] },
|
||||||
position: { x: number; y: number; paneX?: number; paneY?: number }
|
position: { x: number; y: number; paneX?: number; paneY?: number }
|
||||||
) {
|
) {
|
||||||
let minimumX = Infinity;
|
let minimumX = Infinity;
|
||||||
let minimumY = Infinity;
|
let minimumY = Infinity;
|
||||||
let idsMap = {};
|
let idsMap = {};
|
||||||
let nodes = reactFlowInstance.getNodes();
|
let nodes: Node<NodeDataType>[] = reactFlowInstance!.getNodes();
|
||||||
let edges = reactFlowInstance.getEdges();
|
let edges = reactFlowInstance!.getEdges();
|
||||||
selectionInstance.nodes.forEach((node) => {
|
selectionInstance.nodes.forEach((node: Node) => {
|
||||||
if (node.position.y < minimumY) {
|
if (node.position.y < minimumY) {
|
||||||
minimumY = node.position.y;
|
minimumY = node.position.y;
|
||||||
}
|
}
|
||||||
|
|
@ -371,8 +377,8 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
});
|
});
|
||||||
|
|
||||||
const insidePosition = position.paneX
|
const insidePosition = position.paneX
|
||||||
? { x: position.paneX + position.x, y: position.paneY + position.y }
|
? { x: position.paneX + position.x, y: position.paneY! + position.y }
|
||||||
: reactFlowInstance.project({ x: position.x, y: position.y });
|
: reactFlowInstance!.project({ x: position.x, y: position.y });
|
||||||
|
|
||||||
selectionInstance.nodes.forEach((node: NodeType) => {
|
selectionInstance.nodes.forEach((node: NodeType) => {
|
||||||
// Generate a unique node ID
|
// Generate a unique node ID
|
||||||
|
|
@ -384,8 +390,8 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
id: newId,
|
id: newId,
|
||||||
type: "genericNode",
|
type: "genericNode",
|
||||||
position: {
|
position: {
|
||||||
x: insidePosition.x + node.position.x - minimumX,
|
x: insidePosition.x + node.position!.x - minimumX,
|
||||||
y: insidePosition.y + node.position.y - minimumY,
|
y: insidePosition.y + node.position!.y - minimumY,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
..._.cloneDeep(node.data),
|
..._.cloneDeep(node.data),
|
||||||
|
|
@ -398,19 +404,19 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
.map((node) => ({ ...node, selected: false }))
|
.map((node) => ({ ...node, selected: false }))
|
||||||
.concat({ ...newNode, selected: false });
|
.concat({ ...newNode, selected: false });
|
||||||
});
|
});
|
||||||
reactFlowInstance.setNodes(nodes);
|
reactFlowInstance!.setNodes(nodes);
|
||||||
|
|
||||||
selectionInstance.edges.forEach((edge) => {
|
selectionInstance.edges.forEach((edge) => {
|
||||||
let source = idsMap[edge.source];
|
let source = idsMap[edge.source];
|
||||||
let target = idsMap[edge.target];
|
let target = idsMap[edge.target];
|
||||||
let sourceHandleSplitted = edge.sourceHandle.split("|");
|
let sourceHandleSplitted = edge.sourceHandle!.split("|");
|
||||||
let sourceHandle =
|
let sourceHandle =
|
||||||
sourceHandleSplitted[0] +
|
sourceHandleSplitted[0] +
|
||||||
"|" +
|
"|" +
|
||||||
source +
|
source +
|
||||||
"|" +
|
"|" +
|
||||||
sourceHandleSplitted.slice(2).join("|");
|
sourceHandleSplitted.slice(2).join("|");
|
||||||
let targetHandleSplitted = edge.targetHandle.split("|");
|
let targetHandleSplitted = edge.targetHandle!.split("|");
|
||||||
let targetHandle =
|
let targetHandle =
|
||||||
targetHandleSplitted.slice(0, -1).join("|") + "|" + target;
|
targetHandleSplitted.slice(0, -1).join("|") + "|" + target;
|
||||||
let id =
|
let id =
|
||||||
|
|
@ -438,21 +444,21 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
edges.map((edge) => ({ ...edge, selected: false }))
|
edges.map((edge) => ({ ...edge, selected: false }))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
reactFlowInstance.setEdges(edges);
|
reactFlowInstance!.setEdges(edges);
|
||||||
}
|
}
|
||||||
|
|
||||||
const addFlow = async (
|
const addFlow = async (
|
||||||
flow?: FlowType,
|
flow?: FlowType,
|
||||||
newProject?: Boolean
|
newProject?: Boolean
|
||||||
): Promise<String> => {
|
): Promise<String | undefined> => {
|
||||||
if (newProject) {
|
if (newProject) {
|
||||||
let flowData = extractDataFromFlow(flow);
|
let flowData = extractDataFromFlow(flow!);
|
||||||
if (flowData.description == "") {
|
if (flowData.description == "") {
|
||||||
flowData.description = getRandomDescription();
|
flowData.description = getRandomDescription();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
const newFlow = createNewFlow(flowData, flow);
|
const newFlow = createNewFlow(flowData, flow!);
|
||||||
processFlowEdges(newFlow);
|
processFlowEdges(newFlow);
|
||||||
processFlowNodes(newFlow);
|
processFlowNodes(newFlow);
|
||||||
|
|
||||||
|
|
@ -477,13 +483,13 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
paste(
|
paste(
|
||||||
{ nodes: flow.data.nodes, edges: flow.data.edges },
|
{ nodes: flow!.data!.nodes, edges: flow!.data!.edges },
|
||||||
{ x: 10, y: 10 }
|
{ x: 10, y: 10 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const extractDataFromFlow = (flow) => {
|
const extractDataFromFlow = (flow: FlowType) => {
|
||||||
let data = flow?.data ? flow.data : null;
|
let data = flow?.data ? flow.data : null;
|
||||||
const description = flow?.description ? flow.description : "";
|
const description = flow?.description ? flow.description : "";
|
||||||
|
|
||||||
|
|
@ -496,18 +502,19 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
return { data, description };
|
return { data, description };
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateEdges = (edges) => {
|
const updateEdges = (edges: Edge[]) => {
|
||||||
edges.forEach((edge) => {
|
edges.forEach((edge) => {
|
||||||
edge.className =
|
edge.className =
|
||||||
(edge.targetHandle.split("|")[0] === "Text"
|
(edge.targetHandle!.split("|")[0] === "Text"
|
||||||
? "stroke-gray-800 "
|
? "stroke-gray-800 "
|
||||||
: "stroke-gray-900 ") + " stroke-connection";
|
: "stroke-gray-900 ") + " stroke-connection";
|
||||||
edge.animated = edge.targetHandle.split("|")[0] === "Text";
|
edge.animated = edge.targetHandle!.split("|")[0] === "Text";
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateNodes = (nodes, edges) => {
|
const updateNodes = (nodes: Node[], edges: Edge[]) => {
|
||||||
nodes.forEach((node) => {
|
nodes.forEach((node) => {
|
||||||
|
if (skipNodeUpdate.includes(node.data.type)) return;
|
||||||
const template = templates[node.data.type];
|
const template = templates[node.data.type];
|
||||||
if (!template) {
|
if (!template) {
|
||||||
setErrorData({ title: `Unknown node type: ${node.data.type}` });
|
setErrorData({ title: `Unknown node type: ${node.data.type}` });
|
||||||
|
|
@ -517,8 +524,8 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
node.data.node.base_classes = template["base_classes"];
|
node.data.node.base_classes = template["base_classes"];
|
||||||
edges.forEach((edge) => {
|
edges.forEach((edge) => {
|
||||||
if (edge.source === node.id) {
|
if (edge.source === node.id) {
|
||||||
edge.sourceHandle = edge.sourceHandle
|
edge.sourceHandle = edge
|
||||||
.split("|")
|
.sourceHandle!.split("|")
|
||||||
.slice(0, 2)
|
.slice(0, 2)
|
||||||
.concat(template["base_classes"])
|
.concat(template["base_classes"])
|
||||||
.join("|");
|
.join("|");
|
||||||
|
|
@ -533,14 +540,17 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const createNewFlow = (flowData, flow) => ({
|
const createNewFlow = (
|
||||||
|
flowData: { data: ReactFlowJsonObject | null; description: string },
|
||||||
|
flow: FlowType
|
||||||
|
) => ({
|
||||||
description: flowData.description,
|
description: flowData.description,
|
||||||
name: flow?.name ?? getRandomName(),
|
name: flow?.name ?? getRandomName(),
|
||||||
data: flowData.data,
|
data: flowData.data,
|
||||||
id: "",
|
id: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const addFlowToLocalState = (newFlow) => {
|
const addFlowToLocalState = (newFlow: FlowType) => {
|
||||||
setFlows((prevState) => {
|
setFlows((prevState) => {
|
||||||
return [...prevState, newFlow];
|
return [...prevState, newFlow];
|
||||||
});
|
});
|
||||||
|
|
@ -591,7 +601,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setErrorData(err);
|
setErrorData(err as errorsVarType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,21 @@
|
||||||
import { createContext, ReactNode, useEffect, useState } from "react";
|
import {
|
||||||
import { Node } from "reactflow";
|
createContext,
|
||||||
|
ReactNode,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import { Node, ReactFlowInstance } from "reactflow";
|
||||||
import { getAll } from "../controllers/API";
|
import { getAll } from "../controllers/API";
|
||||||
import { APIKindType } from "../types/api";
|
import { APIKindType } from "../types/api";
|
||||||
import { typesContextType } from "../types/typesContext";
|
import { typesContextType } from "../types/typesContext";
|
||||||
|
import { alertContext } from "./alertContext";
|
||||||
|
|
||||||
//context to share types adn functions from nodes to flow
|
//context to share types adn functions from nodes to flow
|
||||||
|
|
||||||
const initialValue: typesContextType = {
|
const initialValue: typesContextType = {
|
||||||
reactFlowInstance: null,
|
reactFlowInstance: null,
|
||||||
setReactFlowInstance: () => {},
|
setReactFlowInstance: (newState: ReactFlowInstance) => {},
|
||||||
deleteNode: () => {},
|
deleteNode: () => {},
|
||||||
types: {},
|
types: {},
|
||||||
setTypes: () => {},
|
setTypes: () => {},
|
||||||
|
|
@ -22,13 +29,15 @@ 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<ReactFlowInstance | null>(null);
|
||||||
const [templates, setTemplates] = useState({});
|
const [templates, setTemplates] = useState({});
|
||||||
const [data, setData] = useState({});
|
const [data, setData] = useState({});
|
||||||
|
const { setLoading } = useContext(alertContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let delay = 1000; // Start delay of 1 second
|
let delay = 1000; // Start delay of 1 second
|
||||||
let intervalId = null;
|
let intervalId: NodeJS.Timer;
|
||||||
let retryCount = 0; // Count of retry attempts
|
let retryCount = 0; // Count of retry attempts
|
||||||
const maxRetryCount = 5; // Max retry attempts
|
const maxRetryCount = 5; // Max retry attempts
|
||||||
|
|
||||||
|
|
@ -40,6 +49,7 @@ export function TypesProvider({ children }: { children: ReactNode }) {
|
||||||
const result = await getAll();
|
const result = await getAll();
|
||||||
// Make sure to only update the state if the component is still mounted.
|
// Make sure to only update the state if the component is still mounted.
|
||||||
if (isMounted) {
|
if (isMounted) {
|
||||||
|
setLoading(false);
|
||||||
setData(result.data);
|
setData(result.data);
|
||||||
setTemplates(
|
setTemplates(
|
||||||
Object.keys(result.data).reduce((acc, curr) => {
|
Object.keys(result.data).reduce((acc, curr) => {
|
||||||
|
|
@ -69,7 +79,7 @@ export function TypesProvider({ children }: { children: ReactNode }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Clear the interval if successful.
|
// Clear the interval if successful.
|
||||||
clearInterval(intervalId);
|
clearInterval(intervalId!);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("An error has occurred while fetching types.");
|
console.error("An error has occurred while fetching types.");
|
||||||
}
|
}
|
||||||
|
|
@ -77,21 +87,20 @@ export function TypesProvider({ children }: { children: ReactNode }) {
|
||||||
|
|
||||||
// Start the initial interval.
|
// Start the initial interval.
|
||||||
intervalId = setInterval(getTypes, delay);
|
intervalId = setInterval(getTypes, delay);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// This will clear the interval when the component unmounts, or when the dependencies of the useEffect hook change.
|
// This will clear the interval when the component unmounts, or when the dependencies of the useEffect hook change.
|
||||||
clearInterval(intervalId);
|
clearInterval(intervalId!);
|
||||||
// Indicate that the component has been unmounted.
|
// Indicate that the component has been unmounted.
|
||||||
isMounted = false;
|
isMounted = false;
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
function deleteNode(idx: string) {
|
function deleteNode(idx: string) {
|
||||||
reactFlowInstance.setNodes(
|
reactFlowInstance!.setNodes(
|
||||||
reactFlowInstance.getNodes().filter((node: Node) => node.id !== idx)
|
reactFlowInstance!.getNodes().filter((node: Node) => node.id !== idx)
|
||||||
);
|
);
|
||||||
reactFlowInstance.setEdges(
|
reactFlowInstance!.setEdges(
|
||||||
reactFlowInstance
|
reactFlowInstance!
|
||||||
.getEdges()
|
.getEdges()
|
||||||
.filter((edge) => edge.source !== idx && edge.target !== idx)
|
.filter((edge) => edge.source !== idx && edge.target !== idx)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -6,26 +6,15 @@ import {
|
||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { Edge, Node, useReactFlow } from "reactflow";
|
import { useReactFlow } from "reactflow";
|
||||||
|
import {
|
||||||
|
HistoryItem,
|
||||||
|
UseUndoRedoOptions,
|
||||||
|
undoRedoContextType,
|
||||||
|
} from "../types/typesContext";
|
||||||
import { isWrappedWithClass } from "../utils/utils";
|
import { isWrappedWithClass } from "../utils/utils";
|
||||||
import { TabsContext } from "./tabsContext";
|
import { TabsContext } from "./tabsContext";
|
||||||
|
|
||||||
type undoRedoContextType = {
|
|
||||||
undo: () => void;
|
|
||||||
redo: () => void;
|
|
||||||
takeSnapshot: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
type UseUndoRedoOptions = {
|
|
||||||
maxHistorySize: number;
|
|
||||||
enableShortcuts: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type HistoryItem = {
|
|
||||||
nodes: Node[];
|
|
||||||
edges: Edge[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const initialValue = {
|
const initialValue = {
|
||||||
undo: () => {},
|
undo: () => {},
|
||||||
redo: () => {},
|
redo: () => {},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
import axios, { AxiosError, AxiosInstance } from "axios";
|
import axios, { AxiosError, AxiosInstance } from "axios";
|
||||||
import { useContext, useEffect, useRef } from "react";
|
import { useContext, useEffect } from "react";
|
||||||
|
import { Cookies } from "react-cookie";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { renewAccessToken } from ".";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
|
import { AuthContext } from "../../contexts/authContext";
|
||||||
|
|
||||||
// Create a new Axios instance
|
// Create a new Axios instance
|
||||||
const api: AxiosInstance = axios.create({
|
const api: AxiosInstance = axios.create({
|
||||||
|
|
@ -8,52 +12,99 @@ const api: AxiosInstance = axios.create({
|
||||||
});
|
});
|
||||||
|
|
||||||
function ApiInterceptor() {
|
function ApiInterceptor() {
|
||||||
const retryCounts = useRef([]);
|
|
||||||
const { setErrorData } = useContext(alertContext);
|
const { setErrorData } = useContext(alertContext);
|
||||||
|
let { accessToken, login, logout, authenticationErrorCount } =
|
||||||
|
useContext(AuthContext);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const cookies = new Cookies();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const interceptor = api.interceptors.response.use(
|
const interceptor = api.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
async (error: AxiosError) => {
|
async (error: AxiosError) => {
|
||||||
// if (URL_EXCLUDED_FROM_ERROR_RETRIES.includes(error.config?.url)) {
|
if (error.response?.status === 401) {
|
||||||
// return Promise.reject(error);
|
const refreshToken = cookies.get("refresh_token");
|
||||||
// }
|
|
||||||
// let retryCount = 0;
|
if (refreshToken) {
|
||||||
// while (retryCount < 4) {
|
authenticationErrorCount = authenticationErrorCount + 1;
|
||||||
// await sleep(5000); // Sleep for 5 seconds
|
if (authenticationErrorCount > 3) {
|
||||||
// retryCount++;
|
authenticationErrorCount = 0;
|
||||||
// try {
|
logout();
|
||||||
// const response = await axios.request(error.config);
|
navigate("/login");
|
||||||
// return response;
|
}
|
||||||
// } catch (error) {
|
|
||||||
// if (retryCount === 3) {
|
const res = await renewAccessToken(refreshToken);
|
||||||
// setErrorData({
|
login(res.data.access_token, res.data.refresh_token);
|
||||||
// title: "There was an error on web connection, please: ",
|
try {
|
||||||
// list: [
|
if (error?.config?.headers) {
|
||||||
// "Refresh the page",
|
delete error.config.headers["Authorization"];
|
||||||
// "Use a new flow tab",
|
error.config.headers["Authorization"] = `Bearer ${accessToken}`;
|
||||||
// "Check if the backend is up",
|
const response = await axios.request(error.config);
|
||||||
// "Endpoint: " + error.config?.url,
|
return response;
|
||||||
// ],
|
}
|
||||||
// });
|
} catch (error) {
|
||||||
// return Promise.reject(error);
|
if (axios.isAxiosError(error) && error.response?.status === 401) {
|
||||||
// }
|
logout();
|
||||||
// }
|
navigate("/login");
|
||||||
// }
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!refreshToken && error?.config?.url?.includes("login")) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
} else {
|
||||||
|
logout();
|
||||||
|
navigate("/login");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if (URL_EXCLUDED_FROM_ERROR_RETRIES.includes(error.config?.url)) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Request interceptor to add access token to every request
|
||||||
|
const requestInterceptor = api.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
if (accessToken) {
|
||||||
|
config.headers["Authorization"] = `Bearer ${accessToken}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
config?.url?.includes(
|
||||||
|
"https://raw.githubusercontent.com/logspace-ai/langflow_examples/main/examples"
|
||||||
|
) ||
|
||||||
|
config?.url?.includes(
|
||||||
|
"https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples"
|
||||||
|
) ||
|
||||||
|
config?.url?.includes(
|
||||||
|
"https://api.github.com/repos/logspace-ai/langflow"
|
||||||
|
) ||
|
||||||
|
config?.url?.includes("auto_login")
|
||||||
|
) {
|
||||||
|
delete config.headers["Authorization"];
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// Clean up the interceptor when the component unmounts
|
// Clean up the interceptors when the component unmounts
|
||||||
api.interceptors.response.eject(interceptor);
|
api.interceptors.response.eject(interceptor);
|
||||||
|
api.interceptors.request.eject(requestInterceptor);
|
||||||
};
|
};
|
||||||
}, [retryCounts]);
|
}, [accessToken, setErrorData]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to sleep for a given duration in milliseconds
|
// Function to sleep for a given duration in milliseconds
|
||||||
function sleep(ms) {
|
function sleep(ms: number) {
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,14 @@
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { ReactFlowJsonObject } from "reactflow";
|
import { ReactFlowJsonObject } from "reactflow";
|
||||||
|
import { BASE_URL_API } from "../../constants/constants";
|
||||||
import { api } from "../../controllers/API/api";
|
import { api } from "../../controllers/API/api";
|
||||||
import { APIObjectType, sendAllProps } from "../../types/api/index";
|
import {
|
||||||
|
APIObjectType,
|
||||||
|
LoginType,
|
||||||
|
Users,
|
||||||
|
sendAllProps,
|
||||||
|
} from "../../types/api/index";
|
||||||
|
import { UserInputType } from "../../types/components";
|
||||||
import { FlowStyleType, FlowType } from "../../types/flow";
|
import { FlowStyleType, FlowType } from "../../types/flow";
|
||||||
import {
|
import {
|
||||||
APIClassType,
|
APIClassType,
|
||||||
|
|
@ -23,7 +30,7 @@ export async function getAll(): Promise<AxiosResponse<APIObjectType>> {
|
||||||
|
|
||||||
const GITHUB_API_URL = "https://api.github.com";
|
const GITHUB_API_URL = "https://api.github.com";
|
||||||
|
|
||||||
export async function getRepoStars(owner, repo) {
|
export async function getRepoStars(owner: string, repo: string) {
|
||||||
try {
|
try {
|
||||||
const response = await api.get(`${GITHUB_API_URL}/repos/${owner}/${repo}`);
|
const response = await api.get(`${GITHUB_API_URL}/repos/${owner}/${repo}`);
|
||||||
return response.data.stargazers_count;
|
return response.data.stargazers_count;
|
||||||
|
|
@ -100,7 +107,7 @@ export async function getExamples(): Promise<FlowType[]> {
|
||||||
export async function saveFlowToDatabase(newFlow: {
|
export async function saveFlowToDatabase(newFlow: {
|
||||||
name: string;
|
name: string;
|
||||||
id: string;
|
id: string;
|
||||||
data: ReactFlowJsonObject;
|
data: ReactFlowJsonObject | null;
|
||||||
description: string;
|
description: string;
|
||||||
style?: FlowStyleType;
|
style?: FlowStyleType;
|
||||||
}): Promise<FlowType> {
|
}): Promise<FlowType> {
|
||||||
|
|
@ -179,7 +186,7 @@ export async function downloadFlowsFromDatabase() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function uploadFlowsToDatabase(flows) {
|
export async function uploadFlowsToDatabase(flows: FormData) {
|
||||||
try {
|
try {
|
||||||
const response = await api.post(`/api/v1/flows/upload/`, flows);
|
const response = await api.post(`/api/v1/flows/upload/`, flows);
|
||||||
|
|
||||||
|
|
@ -346,3 +353,153 @@ export async function postCustomComponent(
|
||||||
): Promise<AxiosResponse<APIClassType>> {
|
): Promise<AxiosResponse<APIClassType>> {
|
||||||
return await api.post(`/api/v1/custom_component`, { code });
|
return await api.post(`/api/v1/custom_component`, { code });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function onLogin(user: LoginType) {
|
||||||
|
try {
|
||||||
|
const response = await api.post(
|
||||||
|
`${BASE_URL_API}login`,
|
||||||
|
new URLSearchParams({
|
||||||
|
username: user.username,
|
||||||
|
password: user.password,
|
||||||
|
}).toString(),
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
const data = response.data;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function autoLogin() {
|
||||||
|
try {
|
||||||
|
const response = await api.get(`${BASE_URL_API}auto_login`);
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
const data = response.data;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function renewAccessToken(token: string) {
|
||||||
|
try {
|
||||||
|
return await api.post(`${BASE_URL_API}refresh?token=${token}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getLoggedUser(): Promise<Users> {
|
||||||
|
try {
|
||||||
|
const res = await api.get(`${BASE_URL_API}user`);
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addUser(user: UserInputType): Promise<Users> {
|
||||||
|
try {
|
||||||
|
const res = await api.post(`${BASE_URL_API}user`, user);
|
||||||
|
if (res.status === 200) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUsersPage(
|
||||||
|
skip: number,
|
||||||
|
limit: number
|
||||||
|
): Promise<[Users]> {
|
||||||
|
try {
|
||||||
|
const res = await api.get(
|
||||||
|
`${BASE_URL_API}users?skip=${skip}&limit=${limit}`
|
||||||
|
);
|
||||||
|
if (res.status === 200) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteUser(user_id: string) {
|
||||||
|
try {
|
||||||
|
const res = await api.delete(`${BASE_URL_API}user/${user_id}`);
|
||||||
|
if (res.status === 200) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateUser(user_id: string, user: Users) {
|
||||||
|
try {
|
||||||
|
const res = await api.patch(`${BASE_URL_API}user/${user_id}`, user);
|
||||||
|
if (res.status === 200) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getApiKey(user_id: String) {
|
||||||
|
try {
|
||||||
|
const res = await api.get(`${BASE_URL_API}api_key/${user_id}`);
|
||||||
|
if (res.status === 200) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createApiKey(user_id: string) {
|
||||||
|
try {
|
||||||
|
const res = await api.post(`${BASE_URL_API}api_key/${user_id}`);
|
||||||
|
if (res.status === 200) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function deleteApiKey(user_id: string) {
|
||||||
|
try {
|
||||||
|
const res = await api.delete(`${BASE_URL_API}api_key/${user_id}`);
|
||||||
|
if (res.status === 200) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,22 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
<svg version="1.0" id="katman_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg version="1.0" id="katman_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
viewBox="0 0 841.89 595.28" style="enable-background:new 0 0 841.89 595.28;" xml:space="preserve">
|
viewBox="0 0 841.89 595.28" style="enable-background:new 0 0 841.89 595.28;" xml:space="preserve">
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#505AA5;}
|
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#505AA5;}
|
||||||
</style>
|
</style>
|
||||||
<path class="st0" d="M349.6,124.45c48.94-54.99,129.98-71.12,196.61-39.38c88.52,42.17,120.82,149.6,72.62,232.48L510.41,503.76
|
<path class="st0" d="M349.6,124.45c48.94-54.99,129.98-71.12,196.61-39.38c88.52,42.17,120.82,149.6,72.62,232.48L510.41,503.76
|
||||||
c-6.06,10.41-16.03,18-27.72,21.11c-11.69,3.11-24.15,1.49-34.64-4.51l131.26-225.49c34.97-60.15,11.58-138.11-52.6-168.8
|
c-6.06,10.41-16.03,18-27.72,21.11c-11.69,3.11-24.15,1.49-34.64-4.51l131.26-225.49c34.97-60.15,11.58-138.11-52.6-168.8
|
||||||
c-48.16-23.03-107.02-11.53-142.6,28.08c-19.62,21.74-30.64,49.82-31.01,79.01c-0.37,29.2,9.94,57.53,29.01,79.76
|
c-48.16-23.03-107.02-11.53-142.6,28.08c-19.62,21.74-30.64,49.82-31.01,79.01c-0.37,29.2,9.94,57.53,29.01,79.76
|
||||||
c3.43,3.99,7.12,7.75,11.04,11.25l-76.63,131.88c-3,5.16-6.99,9.67-11.74,13.3c-4.76,3.62-10.18,6.28-15.97,7.82
|
c3.43,3.99,7.12,7.75,11.04,11.25l-76.63,131.88c-3,5.16-6.99,9.67-11.74,13.3c-4.76,3.62-10.18,6.28-15.97,7.82
|
||||||
c-5.79,1.54-11.83,1.93-17.77,1.16c-5.94-0.78-11.67-2.71-16.87-5.68l83.19-143.17c-11.95-17.11-20.53-36.31-25.29-56.58
|
c-5.79,1.54-11.83,1.93-17.77,1.16c-5.94-0.78-11.67-2.71-16.87-5.68l83.19-143.17c-11.95-17.11-20.53-36.31-25.29-56.58
|
||||||
L261.1,360.8c-6.06,10.41-16.03,18-27.72,21.11c-11.69,3.11-24.15,1.49-34.64-4.51l131.83-226.76
|
L261.1,360.8c-6.06,10.41-16.03,18-27.72,21.11c-11.69,3.11-24.15,1.49-34.64-4.51l131.83-226.76
|
||||||
C336.06,141.32,342.43,132.55,349.6,124.45z M501.76,196.63c31.75,18.21,42.71,58.7,24.34,90.22L399.69,503.74
|
C336.06,141.32,342.43,132.55,349.6,124.45z M501.76,196.63c31.75,18.21,42.71,58.7,24.34,90.22L399.69,503.74
|
||||||
c-6.06,10.41-16.03,18-27.72,21.11c-11.69,3.11-24.15,1.49-34.64-4.51l117.38-201.93c-9.42-1.97-18.29-5.94-26.01-11.65
|
c-6.06,10.41-16.03,18-27.72,21.11c-11.69,3.11-24.15,1.49-34.64-4.51l117.38-201.93c-9.42-1.97-18.29-5.94-26.01-11.65
|
||||||
c-7.72-5.71-14.1-13.01-18.7-21.4c-4.6-8.4-7.31-17.68-7.95-27.22c-0.64-9.54,0.82-19.1,4.27-28.02c3.45-8.92,8.8-17,15.7-23.67
|
c-7.72-5.71-14.1-13.01-18.7-21.4c-4.6-8.4-7.31-17.68-7.95-27.22c-0.64-9.54,0.82-19.1,4.27-28.02c3.45-8.92,8.8-17,15.7-23.67
|
||||||
c6.9-6.67,15.17-11.77,24.24-14.95c9.08-3.18,18.74-4.37,28.32-3.49S493.43,191.83,501.76,196.63z M455.78,237.39
|
c6.9-6.67,15.17-11.77,24.24-14.95c9.08-3.18,18.74-4.37,28.32-3.49S493.43,191.83,501.76,196.63z M455.78,237.39
|
||||||
c-2.17,1.66-4,3.72-5.36,6.08h-0.01c-2.06,3.55-3.02,7.62-2.75,11.71c0.27,4.09,1.76,8,4.27,11.25c2.51,3.25,5.94,5.69,9.84,7
|
c-2.17,1.66-4,3.72-5.36,6.08h-0.01c-2.06,3.55-3.02,7.62-2.75,11.71c0.27,4.09,1.76,8,4.27,11.25c2.51,3.25,5.94,5.69,9.84,7
|
||||||
c3.91,1.32,8.12,1.45,12.1,0.39c3.99-1.06,7.56-3.27,10.28-6.35c2.72-3.08,4.46-6.89,5-10.95c0.54-4.06-0.15-8.19-1.97-11.87
|
c3.91,1.32,8.12,1.45,12.1,0.39c3.99-1.06,7.56-3.27,10.28-6.35c2.72-3.08,4.46-6.89,5-10.95c0.54-4.06-0.15-8.19-1.97-11.87
|
||||||
c-1.82-3.67-4.71-6.73-8.28-8.78c-2.37-1.36-4.99-2.24-7.71-2.6c-2.72-0.35-5.48-0.18-8.12,0.53
|
c-1.82-3.67-4.71-6.73-8.28-8.78c-2.37-1.36-4.99-2.24-7.71-2.6c-2.72-0.35-5.48-0.18-8.12,0.53
|
||||||
C460.43,234.52,457.95,235.73,455.78,237.39z"/>
|
C460.43,234.52,457.95,235.73,455.78,237.39z"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 1.9 KiB |