Merge remote-tracking branch 'origin/dev' into celery
This commit is contained in:
commit
97ad9889be
123 changed files with 6957 additions and 3229 deletions
1
.dockerignore
Normal file
1
.dockerignore
Normal file
|
|
@ -0,0 +1 @@
|
|||
.venv/
|
||||
9
Makefile
9
Makefile
|
|
@ -32,7 +32,10 @@ lint:
|
|||
poetry run ruff . --fix
|
||||
|
||||
install_frontend:
|
||||
cd src/frontend && npm install
|
||||
cd src/frontend && npm install;
|
||||
|
||||
install_frontendc:
|
||||
cd src/frontend && npm ci;
|
||||
|
||||
run_frontend:
|
||||
cd src/frontend && npm start
|
||||
|
|
@ -40,6 +43,10 @@ run_frontend:
|
|||
frontend:
|
||||
make install_frontend
|
||||
make run_frontend
|
||||
|
||||
frontendc:
|
||||
make install_frontendc
|
||||
make run_frontend
|
||||
|
||||
install_backend:
|
||||
poetry install --extras deploy
|
||||
|
|
|
|||
|
|
@ -15,4 +15,4 @@ COPY ./ ./
|
|||
# Install dependencies
|
||||
RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi
|
||||
|
||||
CMD ["uvicorn","--factory", "langflow.main:create_app", "--host", "0.0.0.0", "--port", "5003", "--reload", "log-level", "debug"]
|
||||
CMD ["uvicorn", "--factory", "src.backend.langflow.main:create_app", "--host", "0.0.0.0", "--port", "7860", "--reload", "--log-level", "debug"]
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ services:
|
|||
[
|
||||
"sh",
|
||||
"-c",
|
||||
"pip install debugpy -t /tmp && python /tmp/debugpy --wait-for-client --listen 0.0.0.0:5678 -m uvicorn --factory 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:
|
||||
- 7860:7860
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ services:
|
|||
- "7860:7860"
|
||||
volumes:
|
||||
- ./:/app
|
||||
command: bash -c "uvicorn --factory langflow.main:create_app --host 0.0.0.0 --port 7860 --reload"
|
||||
command: bash -c "uvicorn --factory src.backend.langflow.main:create_app --host 0.0.0.0 --port 7860 --reload"
|
||||
|
||||
frontend:
|
||||
build:
|
||||
|
|
|
|||
|
|
@ -73,3 +73,25 @@ Used to load [OpenAI’s](https://openai.com/) embedding models.
|
|||
- **request_timeout:** Used to specify the maximum amount of time, in milliseconds, to wait for a response from the OpenAI API when generating embeddings for a given text.
|
||||
|
||||
- **tiktoken_model_name:** Used to count the number of tokens in documents to constrain them to be under a certain limit. By default, when set to None, this will be the same as the embedding model name.
|
||||
|
||||
---
|
||||
|
||||
### VertexAIEmbeddings
|
||||
|
||||
Wrapper around [Google Vertex AI](https://cloud.google.com/vertex-ai) [Embeddings API](https://cloud.google.com/vertex-ai/docs/generative-ai/embeddings/get-text-embeddings).
|
||||
|
||||
:::info
|
||||
Vertex AI is a cloud computing platform offered by Google Cloud Platform (GCP). It provides access, management, and development of applications and services through global data centers. To use Vertex AI PaLM, you need to have the [google-cloud-aiplatform](https://pypi.org/project/google-cloud-aiplatform/) Python package installed and credentials configured for your environment.
|
||||
:::
|
||||
|
||||
- **credentials:** The default custom credentials (google.auth.credentials.Credentials) to use.
|
||||
- **location:** The default location to use when making API calls – defaults to `us-central1`.
|
||||
- **max_output_tokens:** Token limit determines the maximum amount of text output from one prompt – defaults to `128`.
|
||||
- **model_name:** The name of the Vertex AI large language model – defaults to `text-bison`.
|
||||
- **project:** The default GCP project to use when making Vertex API calls.
|
||||
- **request_parallelism:** The amount of parallelism allowed for requests issued to VertexAI models – defaults to `5`.
|
||||
- **temperature:** Tunes the degree of randomness in text generations. Should be a non-negative value – defaults to `0`.
|
||||
- **top_k:** How the model selects tokens for output, the next token is selected from – defaults to `40`.
|
||||
- **top_p:** Tokens are selected from most probable to least until the sum of their – defaults to `0.95`.
|
||||
- **tuned_model_name:** The name of a tuned model. If provided, model_name is ignored.
|
||||
- **verbose:** This parameter is used to control the level of detail in the output of the chain. When set to True, it will print out some internal states of the chain while it is being run, which can help debug and understand the chain's behavior. If set to False, it will suppress the verbose output – defaults to `False`.
|
||||
|
|
@ -185,6 +185,28 @@ Wrapper around [Google Vertex AI](https://cloud.google.com/vertex-ai) large lang
|
|||
Vertex AI is a cloud computing platform offered by Google Cloud Platform (GCP). It provides access, management, and development of applications and services through global data centers. To use Vertex AI PaLM, you need to have the [google-cloud-aiplatform](https://pypi.org/project/google-cloud-aiplatform/) Python package installed and credentials configured for your environment.
|
||||
:::
|
||||
|
||||
- **credentials:** The default custom credentials (google.auth.credentials.Credentials) to use.
|
||||
- **location:** The default location to use when making API calls – defaults to `us-central1`.
|
||||
- **max_output_tokens:** Token limit determines the maximum amount of text output from one prompt – defaults to `128`.
|
||||
- **model_name:** The name of the Vertex AI large language model – defaults to `text-bison`.
|
||||
- **project:** The default GCP project to use when making Vertex API calls.
|
||||
- **request_parallelism:** The amount of parallelism allowed for requests issued to VertexAI models – defaults to `5`.
|
||||
- **temperature:** Tunes the degree of randomness in text generations. Should be a non-negative value – defaults to `0`.
|
||||
- **top_k:** How the model selects tokens for output, the next token is selected from – defaults to `40`.
|
||||
- **top_p:** Tokens are selected from most probable to least until the sum of their – defaults to `0.95`.
|
||||
- **tuned_model_name:** The name of a tuned model. If provided, model_name is ignored.
|
||||
- **verbose:** This parameter is used to control the level of detail in the output of the chain. When set to True, it will print out some internal states of the chain while it is being run, which can help debug and understand the chain's behavior. If set to False, it will suppress the verbose output – defaults to `False`.
|
||||
|
||||
---
|
||||
|
||||
### ChatVertexAI
|
||||
|
||||
Wrapper around [Google Vertex AI](https://cloud.google.com/vertex-ai) large language models.
|
||||
|
||||
:::info
|
||||
Vertex AI is a cloud computing platform offered by Google Cloud Platform (GCP). It provides access, management, and development of applications and services through global data centers. To use Vertex AI PaLM, you need to have the [google-cloud-aiplatform](https://pypi.org/project/google-cloud-aiplatform/) Python package installed and credentials configured for your environment.
|
||||
:::
|
||||
|
||||
- **credentials:** The default custom credentials (google.auth.credentials.Credentials) to use.
|
||||
- **location:** The default location to use when making API calls – defaults to `us-central1`.
|
||||
- **max_output_tokens:** Token limit determines the maximum amount of text output from one prompt – defaults to `128`.
|
||||
|
|
|
|||
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "langflow",
|
||||
"name": "reactFlow",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
|
|
|||
141
poetry.lock
generated
141
poetry.lock
generated
|
|
@ -146,13 +146,13 @@ files = [
|
|||
|
||||
[[package]]
|
||||
name = "alembic"
|
||||
version = "1.11.2"
|
||||
version = "1.11.3"
|
||||
description = "A database migration tool for SQLAlchemy."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "alembic-1.11.2-py3-none-any.whl", hash = "sha256:7981ab0c4fad4fe1be0cf183aae17689fe394ff874fd2464adb774396faf0796"},
|
||||
{file = "alembic-1.11.2.tar.gz", hash = "sha256:678f662130dc540dac12de0ea73de9f89caea9dbea138f60ef6263149bf84657"},
|
||||
{file = "alembic-1.11.3-py3-none-any.whl", hash = "sha256:d6c96c2482740592777c400550a523bc7a9aada4e210cae2e733354ddae6f6f8"},
|
||||
{file = "alembic-1.11.3.tar.gz", hash = "sha256:3db4ce81a9072e1b5aa44c2d202add24553182672a12daf21608d6f62a8f9cf9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -179,20 +179,20 @@ vine = ">=5.0.0"
|
|||
|
||||
[[package]]
|
||||
name = "anthropic"
|
||||
version = "0.3.9"
|
||||
version = "0.3.10"
|
||||
description = "Client library for the anthropic API"
|
||||
optional = false
|
||||
python-versions = ">=3.7,<4.0"
|
||||
files = [
|
||||
{file = "anthropic-0.3.9-py3-none-any.whl", hash = "sha256:23e8daf266c707faa0b85328ada03dcf885e62c2f236eb0159352e29b9c4e2e3"},
|
||||
{file = "anthropic-0.3.9.tar.gz", hash = "sha256:c19b75308c07cb1ecbea03ffcdde9ba68e5ca22a22479217712396385678bde3"},
|
||||
{file = "anthropic-0.3.10-py3-none-any.whl", hash = "sha256:95cd73168296a4f91a5899a28660991e044322cf94442d07b99901d4ca74acd6"},
|
||||
{file = "anthropic-0.3.10.tar.gz", hash = "sha256:d1f66efc541fbff0ecfd37fd4d3690f9daaa748fc42d9ded5863a10815a5d97b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
anyio = ">=3.5.0,<4"
|
||||
distro = ">=1.7.0,<2"
|
||||
httpx = ">=0.23.0,<1"
|
||||
pydantic = ">=1.9.0,<2.0.0"
|
||||
pydantic = ">=1.9.0,<3"
|
||||
tokenizers = ">=0.13.0"
|
||||
typing-extensions = ">=4.5,<5"
|
||||
|
||||
|
|
@ -349,6 +349,40 @@ files = [
|
|||
{file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bcrypt"
|
||||
version = "4.0.1"
|
||||
description = "Modern password hashing for your software and your servers"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "bcrypt-4.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f"},
|
||||
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0"},
|
||||
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410"},
|
||||
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344"},
|
||||
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a"},
|
||||
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3"},
|
||||
{file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2"},
|
||||
{file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535"},
|
||||
{file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e"},
|
||||
{file = "bcrypt-4.0.1-cp36-abi3-win32.whl", hash = "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab"},
|
||||
{file = "bcrypt-4.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9"},
|
||||
{file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf4fa8b2ca74381bb5442c089350f09a3f17797829d958fad058d6e44d9eb83c"},
|
||||
{file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:67a97e1c405b24f19d08890e7ae0c4f7ce1e56a712a016746c8b2d7732d65d4b"},
|
||||
{file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b3b85202d95dd568efcb35b53936c5e3b3600c7cdcc6115ba461df3a8e89f38d"},
|
||||
{file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb03eec97496166b704ed663a53680ab57c5084b2fc98ef23291987b525cb7d"},
|
||||
{file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:5ad4d32a28b80c5fa6671ccfb43676e8c1cc232887759d1cd7b6f56ea4355215"},
|
||||
{file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b57adba8a1444faf784394de3436233728a1ecaeb6e07e8c22c8848f179b893c"},
|
||||
{file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:705b2cea8a9ed3d55b4491887ceadb0106acf7c6387699fca771af56b1cdeeda"},
|
||||
{file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:2b3ac11cf45161628f1f3733263e63194f22664bf4d0c0f3ab34099c02134665"},
|
||||
{file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3100851841186c25f127731b9fa11909ab7b1df6fc4b9f8353f4f1fd952fbf71"},
|
||||
{file = "bcrypt-4.0.1.tar.gz", hash = "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
tests = ["pytest (>=3.2.1,!=3.3.0)"]
|
||||
typecheck = ["mypy"]
|
||||
|
||||
[[package]]
|
||||
name = "beautifulsoup4"
|
||||
version = "4.12.2"
|
||||
|
|
@ -2552,13 +2586,13 @@ pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_ve
|
|||
|
||||
[[package]]
|
||||
name = "humanize"
|
||||
version = "4.7.0"
|
||||
version = "4.8.0"
|
||||
description = "Python humanize utilities"
|
||||
optional = true
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "humanize-4.7.0-py3-none-any.whl", hash = "sha256:df7c429c2d27372b249d3f26eb53b07b166b661326e0325793e0a988082e3889"},
|
||||
{file = "humanize-4.7.0.tar.gz", hash = "sha256:7ca0e43e870981fa684acb5b062deb307218193bca1a01f2b2676479df849b3a"},
|
||||
{file = "humanize-4.8.0-py3-none-any.whl", hash = "sha256:8bc9e2bb9315e61ec06bf690151ae35aeb65651ab091266941edf97c90836404"},
|
||||
{file = "humanize-4.8.0.tar.gz", hash = "sha256:9783373bf1eec713a770ecaa7c2d7a7902c98398009dfa3d8a2df91eec9311e8"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
|
|
@ -3140,13 +3174,13 @@ test = ["psutil", "pytest", "pytest-asyncio"]
|
|||
|
||||
[[package]]
|
||||
name = "langsmith"
|
||||
version = "0.0.22"
|
||||
version = "0.0.23"
|
||||
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
|
||||
optional = false
|
||||
python-versions = ">=3.8.1,<4.0"
|
||||
files = [
|
||||
{file = "langsmith-0.0.22-py3-none-any.whl", hash = "sha256:1bc94a2e5bfa355ca15d9e658c2c2d04c8cc45c61892a1be08a7c3b40f2fd3f4"},
|
||||
{file = "langsmith-0.0.22.tar.gz", hash = "sha256:5726c7841294db2a9e5863e20718878d16e28722bdaf3169a278ff3bda2f0be7"},
|
||||
{file = "langsmith-0.0.23-py3-none-any.whl", hash = "sha256:68a48ecd5b389d14de55e9b6ac237a280ed3720814907277e25026b18e037103"},
|
||||
{file = "langsmith-0.0.23.tar.gz", hash = "sha256:2d26d78bef5ab928da632d0dfbae8985afa6ffdaec5a895dfa277ed24c6936fd"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
|
@ -3723,33 +3757,38 @@ dill = ">=0.3.7"
|
|||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.5.0"
|
||||
version = "1.5.1"
|
||||
description = "Optional static typing for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mypy-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ad3109bec37cc33654de8db30fe8ff3a1bb57ea65144167d68185e6dced9868d"},
|
||||
{file = "mypy-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b4ea3a0241cb005b0ccdbd318fb99619b21ae51bcf1660b95fc22e0e7d3ba4a1"},
|
||||
{file = "mypy-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fe816e26e676c1311b9e04fd576543b873576d39439f7c24c8e5c7728391ecf"},
|
||||
{file = "mypy-1.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:42170e68adb1603ccdc55a30068f72bcfcde2ce650188e4c1b2a93018b826735"},
|
||||
{file = "mypy-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:d145b81a8214687cfc1f85c03663a5bbe736777410e5580e54d526e7e904f564"},
|
||||
{file = "mypy-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c36011320e452eb30bec38b9fd3ba20569dc9545d7d4540d967f3ea1fab9c374"},
|
||||
{file = "mypy-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f3940cf5845b2512b3ab95463198b0cdf87975dfd17fdcc6ce9709a9abe09e69"},
|
||||
{file = "mypy-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9166186c498170e1ff478a7f540846b2169243feb95bc228d39a67a1a450cdc6"},
|
||||
{file = "mypy-1.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:725b57a19b7408ef66a0fd9db59b5d3e528922250fb56e50bded27fea9ff28f0"},
|
||||
{file = "mypy-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:eec5c927aa4b3e8b4781840f1550079969926d0a22ce38075f6cfcf4b13e3eb4"},
|
||||
{file = "mypy-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:79c520aa24f21852206b5ff2cf746dc13020113aa73fa55af504635a96e62718"},
|
||||
{file = "mypy-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:769ddb6bfe55c2bd9c7d6d7020885a5ea14289619db7ee650e06b1ef0852c6f4"},
|
||||
{file = "mypy-1.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbf18f8db7e5f060d61c91e334d3b96d6bb624ddc9ee8a1cde407b737acbca2c"},
|
||||
{file = "mypy-1.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a2500ad063413bc873ae102cf655bf49889e0763b260a3a7cf544a0cbbf7e70a"},
|
||||
{file = "mypy-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:84cf9f7d8a8a22bb6a36444480f4cbf089c917a4179fbf7eea003ea931944a7f"},
|
||||
{file = "mypy-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a551ed0fc02455fe2c1fb0145160df8336b90ab80224739627b15ebe2b45e9dc"},
|
||||
{file = "mypy-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:372fd97293ed0076d52695849f59acbbb8461c4ab447858cdaeaf734a396d823"},
|
||||
{file = "mypy-1.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8a7444d6fcac7e2585b10abb91ad900a576da7af8f5cffffbff6065d9115813"},
|
||||
{file = "mypy-1.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:35b13335c6c46a386577a51f3d38b2b5d14aa619e9633bb756bd77205e4bd09f"},
|
||||
{file = "mypy-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:2c9d570f53908cbea326ad8f96028a673b814d9dca7515bf71d95fa662c3eb6f"},
|
||||
{file = "mypy-1.5.0-py3-none-any.whl", hash = "sha256:69b32d0dedd211b80f1b7435644e1ef83033a2af2ac65adcdc87c38db68a86be"},
|
||||
{file = "mypy-1.5.0.tar.gz", hash = "sha256:f3460f34b3839b9bc84ee3ed65076eb827cd99ed13ed08d723f9083cada4a212"},
|
||||
{file = "mypy-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f33592ddf9655a4894aef22d134de7393e95fcbdc2d15c1ab65828eee5c66c70"},
|
||||
{file = "mypy-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:258b22210a4a258ccd077426c7a181d789d1121aca6db73a83f79372f5569ae0"},
|
||||
{file = "mypy-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9ec1f695f0c25986e6f7f8778e5ce61659063268836a38c951200c57479cc12"},
|
||||
{file = "mypy-1.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:abed92d9c8f08643c7d831300b739562b0a6c9fcb028d211134fc9ab20ccad5d"},
|
||||
{file = "mypy-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a156e6390944c265eb56afa67c74c0636f10283429171018446b732f1a05af25"},
|
||||
{file = "mypy-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ac9c21bfe7bc9f7f1b6fae441746e6a106e48fc9de530dea29e8cd37a2c0cc4"},
|
||||
{file = "mypy-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51cb1323064b1099e177098cb939eab2da42fea5d818d40113957ec954fc85f4"},
|
||||
{file = "mypy-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:596fae69f2bfcb7305808c75c00f81fe2829b6236eadda536f00610ac5ec2243"},
|
||||
{file = "mypy-1.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:32cb59609b0534f0bd67faebb6e022fe534bdb0e2ecab4290d683d248be1b275"},
|
||||
{file = "mypy-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:159aa9acb16086b79bbb0016145034a1a05360626046a929f84579ce1666b315"},
|
||||
{file = "mypy-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6b0e77db9ff4fda74de7df13f30016a0a663928d669c9f2c057048ba44f09bb"},
|
||||
{file = "mypy-1.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26f71b535dfc158a71264e6dc805a9f8d2e60b67215ca0bfa26e2e1aa4d4d373"},
|
||||
{file = "mypy-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc3a600f749b1008cc75e02b6fb3d4db8dbcca2d733030fe7a3b3502902f161"},
|
||||
{file = "mypy-1.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:26fb32e4d4afa205b24bf645eddfbb36a1e17e995c5c99d6d00edb24b693406a"},
|
||||
{file = "mypy-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:82cb6193de9bbb3844bab4c7cf80e6227d5225cc7625b068a06d005d861ad5f1"},
|
||||
{file = "mypy-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4a465ea2ca12804d5b34bb056be3a29dc47aea5973b892d0417c6a10a40b2d65"},
|
||||
{file = "mypy-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9fece120dbb041771a63eb95e4896791386fe287fefb2837258925b8326d6160"},
|
||||
{file = "mypy-1.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d28ddc3e3dfeab553e743e532fb95b4e6afad51d4706dd22f28e1e5e664828d2"},
|
||||
{file = "mypy-1.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:57b10c56016adce71fba6bc6e9fd45d8083f74361f629390c556738565af8eeb"},
|
||||
{file = "mypy-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:ff0cedc84184115202475bbb46dd99f8dcb87fe24d5d0ddfc0fe6b8575c88d2f"},
|
||||
{file = "mypy-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8f772942d372c8cbac575be99f9cc9d9fb3bd95c8bc2de6c01411e2c84ebca8a"},
|
||||
{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]
|
||||
|
|
@ -4451,6 +4490,23 @@ files = [
|
|||
qa = ["flake8 (==3.8.3)", "mypy (==0.782)"]
|
||||
testing = ["docopt", "pytest (<6.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "passlib"
|
||||
version = "1.7.4"
|
||||
description = "comprehensive password hashing framework supporting over 30 schemes"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"},
|
||||
{file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
argon2 = ["argon2-cffi (>=18.2.0)"]
|
||||
bcrypt = ["bcrypt (>=3.1.0)"]
|
||||
build-docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)"]
|
||||
totp = ["cryptography"]
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.11.2"
|
||||
|
|
@ -7351,6 +7407,17 @@ files = [
|
|||
{file = "validators-0.21.0.tar.gz", hash = "sha256:245b98ab778ed9352a7269c6a8f6c2a839bed5b2a7e3e60273ce399d247dd4b3"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vine"
|
||||
version = "5.0.0"
|
||||
description = "Promises, promises, promises."
|
||||
optional = true
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "vine-5.0.0-py2.py3-none-any.whl", hash = "sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30"},
|
||||
{file = "vine-5.0.0.tar.gz", hash = "sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "watchfiles"
|
||||
version = "0.19.0"
|
||||
|
|
@ -7842,4 +7909,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"]
|
|||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = ">=3.9,<3.11"
|
||||
content-hash = "00a3de3cc8916619f8434289b23a51e768b7b7c74f33e29c0ea3c61688ea3874"
|
||||
content-hash = "c1581fd2c650cc1365793ca33278917dd86bb4527cc956106a82a5c513ecc483"
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ unstructured = "^0.7.0"
|
|||
pypdf = "^3.11.0"
|
||||
lxml = "^4.9.2"
|
||||
pysrt = "^1.1.2"
|
||||
fake-useragent = "^1.1.3"
|
||||
fake-useragent = "^1.2.1"
|
||||
docstring-parser = "^0.15"
|
||||
psycopg2-binary = "^2.9.6"
|
||||
pyarrow = "^12.0.0"
|
||||
|
|
@ -81,6 +81,9 @@ celery = { extras = ["redis"], version = "^5.3.1", optional = true }
|
|||
redis = { version = "^4.6.0", optional = true }
|
||||
flower = { version = "^2.0.0", optional = true }
|
||||
alembic = "^1.11.2"
|
||||
passlib = "^1.7.4"
|
||||
bcrypt = "^4.0.1"
|
||||
python-jose = "^3.3.0"
|
||||
metaphor-python = "^0.1.11"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -11,4 +11,4 @@ RUN rm *.whl
|
|||
|
||||
EXPOSE 80
|
||||
|
||||
CMD [ "uvicorn", "--host", "0.0.0.0", "--port", "80", "langflow.backend.app:app" ]
|
||||
CMD [ "uvicorn", "--host", "0.0.0.0", "--port", "7860", "--factory", "langflow.main:create_app" ]
|
||||
|
|
|
|||
0
src/backend/langflow/auth/__init__.py
Normal file
0
src/backend/langflow/auth/__init__.py
Normal file
177
src/backend/langflow/auth/auth.py
Normal file
177
src/backend/langflow/auth/auth.py
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
from uuid import UUID
|
||||
from typing import Annotated
|
||||
from jose import JWTError, jwt
|
||||
from sqlalchemy.orm import Session
|
||||
from passlib.context import CryptContext
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from langflow.services.utils import get_settings_manager
|
||||
|
||||
from langflow.services.utils import get_session
|
||||
from langflow.database.models.user import (
|
||||
User,
|
||||
get_user_by_id,
|
||||
get_user_by_username,
|
||||
update_user_last_login_at,
|
||||
)
|
||||
|
||||
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login")
|
||||
|
||||
|
||||
async def get_current_user(
|
||||
token: Annotated[str, Depends(oauth2_scheme)], db: Session = Depends(get_session)
|
||||
) -> User:
|
||||
settings_manager = get_settings_manager()
|
||||
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
token,
|
||||
settings_manager.settings.SECRET_KEY,
|
||||
algorithms=[settings_manager.settings.ALGORITHM],
|
||||
)
|
||||
user_id: UUID = payload.get("sub") # type: ignore
|
||||
token_type: str = payload.get("type") # type: ignore
|
||||
|
||||
if user_id is None or token_type:
|
||||
raise credentials_exception
|
||||
except JWTError as e:
|
||||
raise credentials_exception from e
|
||||
|
||||
user = get_user_by_id(db, user_id) # type: ignore
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
return user
|
||||
|
||||
|
||||
async def get_current_active_user(
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
if not current_user.is_active:
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
return current_user
|
||||
|
||||
|
||||
def verify_password(plain_password, hashed_password):
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
|
||||
|
||||
def get_password_hash(password):
|
||||
return pwd_context.hash(password)
|
||||
|
||||
|
||||
def create_token(data: dict, expires_delta: timedelta):
|
||||
settings_manager = get_settings_manager()
|
||||
|
||||
to_encode = data.copy()
|
||||
expire = datetime.now(timezone.utc) + expires_delta
|
||||
to_encode["exp"] = expire
|
||||
|
||||
return jwt.encode(
|
||||
to_encode,
|
||||
settings_manager.settings.SECRET_KEY,
|
||||
algorithm=settings_manager.settings.ALGORITHM,
|
||||
)
|
||||
|
||||
|
||||
def create_user_longterm_token(
|
||||
user_id: UUID, db: Session = Depends(get_session), update_last_login: bool = False
|
||||
) -> dict:
|
||||
access_token_expires_longterm = timedelta(days=365)
|
||||
access_token = create_token(
|
||||
data={"sub": str(user_id)},
|
||||
expires_delta=access_token_expires_longterm,
|
||||
)
|
||||
|
||||
# Update: last_login_at
|
||||
if update_last_login:
|
||||
update_user_last_login_at(user_id, db)
|
||||
|
||||
return {
|
||||
"access_token": access_token,
|
||||
"refresh_token": None,
|
||||
"token_type": "bearer",
|
||||
}
|
||||
|
||||
|
||||
def create_user_tokens(
|
||||
user_id: UUID, db: Session = Depends(get_session), update_last_login: bool = False
|
||||
) -> dict:
|
||||
settings_manager = get_settings_manager()
|
||||
|
||||
access_token_expires = timedelta(
|
||||
minutes=settings_manager.settings.ACCESS_TOKEN_EXPIRE_MINUTES
|
||||
)
|
||||
access_token = create_token(
|
||||
data={"sub": str(user_id)},
|
||||
expires_delta=access_token_expires,
|
||||
)
|
||||
|
||||
refresh_token_expires = timedelta(
|
||||
minutes=settings_manager.settings.REFRESH_TOKEN_EXPIRE_MINUTES
|
||||
)
|
||||
refresh_token = create_token(
|
||||
data={"sub": str(user_id), "type": "rf"},
|
||||
expires_delta=refresh_token_expires,
|
||||
)
|
||||
|
||||
# Update: last_login_at
|
||||
if update_last_login:
|
||||
update_user_last_login_at(user_id, db)
|
||||
|
||||
return {
|
||||
"access_token": access_token,
|
||||
"refresh_token": refresh_token,
|
||||
"token_type": "bearer",
|
||||
}
|
||||
|
||||
|
||||
def create_refresh_token(refresh_token: str, db: Session = Depends(get_session)):
|
||||
settings_manager = get_settings_manager()
|
||||
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
refresh_token,
|
||||
settings_manager.settings.SECRET_KEY,
|
||||
algorithms=[settings_manager.settings.ALGORITHM],
|
||||
)
|
||||
user_id: UUID = payload.get("sub") # type: ignore
|
||||
token_type: str = payload.get("type") # type: ignore
|
||||
|
||||
if user_id is None or token_type is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token"
|
||||
)
|
||||
|
||||
return create_user_tokens(user_id, db)
|
||||
|
||||
except JWTError as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid refresh token",
|
||||
) from e
|
||||
|
||||
|
||||
def authenticate_user(
|
||||
username: str, password: str, db: Session = Depends(get_session)
|
||||
) -> User | None:
|
||||
user = get_user_by_username(db, username)
|
||||
|
||||
if not user:
|
||||
return None
|
||||
|
||||
if not user.is_active:
|
||||
if not user.last_login_at:
|
||||
raise HTTPException(status_code=400, detail="Waiting for approval")
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
|
||||
return user if verify_password(password, user.password) else None
|
||||
|
|
@ -104,6 +104,8 @@ embeddings:
|
|||
documentation: "https://python.langchain.com/docs/modules/data_connection/text_embedding/integrations/sentence_transformers"
|
||||
CohereEmbeddings:
|
||||
documentation: "https://python.langchain.com/docs/modules/data_connection/text_embedding/integrations/cohere"
|
||||
VertexAIEmbeddings:
|
||||
documentation: "https://python.langchain.com/docs/modules/data_connection/text_embedding/integrations/google_vertex_ai_palm"
|
||||
llms:
|
||||
OpenAI:
|
||||
documentation: "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/openai"
|
||||
|
|
@ -127,8 +129,8 @@ llms:
|
|||
# There's a bug in this component deactivating until we get it sorted: _language_models.py", line 804, in send_message
|
||||
# is_blocked=safety_attributes.get("blocked", False),
|
||||
# AttributeError: 'list' object has no attribute 'get'
|
||||
# ChatVertexAI:
|
||||
# documentation: "https://python.langchain.com/docs/modules/model_io/models/chat/integrations/google_vertex_ai_palm"
|
||||
ChatVertexAI:
|
||||
documentation: "https://python.langchain.com/docs/modules/model_io/models/chat/integrations/google_vertex_ai_palm"
|
||||
###
|
||||
memories:
|
||||
# https://github.com/supabase-community/supabase-py/issues/482
|
||||
|
|
|
|||
7
src/backend/langflow/database/models/token.py
Normal file
7
src/backend/langflow/database/models/token.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
refresh_token: str
|
||||
token_type: str
|
||||
94
src/backend/langflow/database/models/user.py
Normal file
94
src/backend/langflow/database/models/user.py
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
from sqlmodel import Field
|
||||
from uuid import UUID, uuid4
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List
|
||||
from sqlalchemy.orm import Session
|
||||
from datetime import timezone, datetime
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from fastapi import HTTPException, Depends
|
||||
|
||||
from langflow.services.utils import get_session
|
||||
from langflow.services.database.models.base import SQLModelSerializable, SQLModel
|
||||
|
||||
|
||||
class User(SQLModelSerializable, table=True):
|
||||
id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True)
|
||||
username: str = Field(index=True, unique=True)
|
||||
password: str = Field()
|
||||
is_active: bool = Field(default=False)
|
||||
is_superuser: bool = Field(default=False)
|
||||
create_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
updated_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
last_login_at: Optional[datetime] = Field()
|
||||
|
||||
|
||||
class UserAddModel(SQLModel):
|
||||
username: str = Field()
|
||||
password: str = Field()
|
||||
|
||||
|
||||
class UserListModel(SQLModel):
|
||||
id: UUID = Field(default_factory=uuid4)
|
||||
username: str = Field()
|
||||
is_active: bool = Field()
|
||||
is_superuser: bool = Field()
|
||||
create_at: datetime = Field()
|
||||
updated_at: datetime = Field()
|
||||
last_login_at: Optional[datetime] = Field()
|
||||
|
||||
|
||||
class UserPatchModel(SQLModel):
|
||||
username: Optional[str] = Field()
|
||||
is_active: Optional[bool] = Field()
|
||||
is_superuser: Optional[bool] = Field()
|
||||
last_login_at: Optional[datetime] = Field()
|
||||
|
||||
|
||||
class UsersResponse(BaseModel):
|
||||
total_count: int
|
||||
users: List[UserListModel]
|
||||
|
||||
|
||||
def get_user_by_username(db: Session, username: str) -> User:
|
||||
db_user = db.query(User).filter(User.username == username).first()
|
||||
return User.from_orm(db_user) if db_user else None # type: ignore
|
||||
|
||||
|
||||
def get_user_by_id(db: Session, id: UUID) -> User:
|
||||
db_user = db.query(User).filter(User.id == id).first()
|
||||
return User.from_orm(db_user) if db_user else None # type: ignore
|
||||
|
||||
|
||||
def update_user(
|
||||
user_id: UUID, user: UserPatchModel, db: Session = Depends(get_session)
|
||||
) -> User:
|
||||
user_db = get_user_by_username(db, user.username) # type: ignore
|
||||
if user_db and user_db.id != user_id:
|
||||
raise HTTPException(status_code=409, detail="Username already exists")
|
||||
|
||||
user_db = get_user_by_id(db, user_id)
|
||||
if not user_db:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
try:
|
||||
user_data = user.dict(exclude_unset=True)
|
||||
for key, value in user_data.items():
|
||||
setattr(user_db, key, value)
|
||||
|
||||
user_db.updated_at = datetime.now(timezone.utc)
|
||||
user_db = db.merge(user_db)
|
||||
db.commit()
|
||||
if db.identity_key(instance=user_db) is not None:
|
||||
db.refresh(user_db)
|
||||
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=400, detail=str(e)) from e
|
||||
|
||||
return user_db
|
||||
|
||||
|
||||
def update_user_last_login_at(user_id: UUID, db: Session = Depends(get_session)):
|
||||
user_data = UserPatchModel(last_login_at=datetime.now(timezone.utc)) # type: ignore
|
||||
|
||||
return update_user(user_id, user_data, db)
|
||||
|
|
@ -88,7 +88,7 @@ def instantiate_based_on_type(class_object, base_type, node_type, params):
|
|||
elif base_type == "toolkits":
|
||||
return instantiate_toolkit(node_type, class_object, params)
|
||||
elif base_type == "embeddings":
|
||||
return instantiate_embedding(class_object, params)
|
||||
return instantiate_embedding(node_type, class_object, params)
|
||||
elif base_type == "vectorstores":
|
||||
return instantiate_vectorstore(class_object, params)
|
||||
elif base_type == "documentloaders":
|
||||
|
|
@ -147,7 +147,7 @@ def instantiate_llm(node_type, class_object, params: Dict):
|
|||
# This is a workaround so JinaChat works until streaming is implemented
|
||||
# if "openai_api_base" in params and "jina" in params["openai_api_base"]:
|
||||
# False if condition is True
|
||||
if node_type == "VertexAI":
|
||||
if "VertexAI" in node_type:
|
||||
return initialize_vertexai(class_object=class_object, params=params)
|
||||
# max_tokens sometimes is a string and should be an int
|
||||
if "max_tokens" in params:
|
||||
|
|
@ -261,9 +261,13 @@ def instantiate_toolkit(node_type, class_object: Type[BaseToolkit], params: Dict
|
|||
return loaded_toolkit
|
||||
|
||||
|
||||
def instantiate_embedding(class_object, params: Dict):
|
||||
def instantiate_embedding(node_type, class_object, params: Dict):
|
||||
params.pop("model", None)
|
||||
params.pop("headers", None)
|
||||
|
||||
if "VertexAI" in node_type:
|
||||
return initialize_vertexai(class_object=class_object, params=params)
|
||||
|
||||
try:
|
||||
return class_object(**params)
|
||||
except ValidationError:
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ from fastapi.responses import FileResponse
|
|||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from langflow.api import router
|
||||
from langflow.routers import login, users, health
|
||||
|
||||
from langflow.interface.utils import setup_llm_caching
|
||||
from langflow.services.database.utils import initialize_database
|
||||
from langflow.services.manager import initialize_services
|
||||
|
|
@ -19,13 +21,7 @@ def create_app():
|
|||
|
||||
app = FastAPI()
|
||||
|
||||
origins = [
|
||||
"*",
|
||||
]
|
||||
|
||||
@app.get("/health")
|
||||
def get_health():
|
||||
return {"status": "OK"}
|
||||
origins = ["*"]
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
|
|
@ -34,6 +30,11 @@ def create_app():
|
|||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
app.include_router(login.router)
|
||||
app.include_router(users.router)
|
||||
app.include_router(health.router)
|
||||
|
||||
app.include_router(router)
|
||||
|
||||
app.on_event("startup")(initialize_services)
|
||||
|
|
|
|||
0
src/backend/langflow/routers/__init__.py
Normal file
0
src/backend/langflow/routers/__init__.py
Normal file
8
src/backend/langflow/routers/health.py
Normal file
8
src/backend/langflow/routers/health.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
from fastapi import APIRouter
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/health")
|
||||
def get_health():
|
||||
return {"status": "OK"}
|
||||
62
src/backend/langflow/routers/login.py
Normal file
62
src/backend/langflow/routers/login.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
from uuid import UUID
|
||||
from sqlalchemy.orm import Session
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
|
||||
from langflow.services.utils import get_session
|
||||
from langflow.database.models.token import Token
|
||||
from langflow.auth.auth import (
|
||||
authenticate_user,
|
||||
create_user_tokens,
|
||||
create_refresh_token,
|
||||
create_user_longterm_token,
|
||||
)
|
||||
|
||||
from langflow.services.utils import get_settings_manager
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/login", response_model=Token)
|
||||
async def login_to_get_access_token(
|
||||
form_data: OAuth2PasswordRequestForm = Depends(),
|
||||
db: Session = Depends(get_session),
|
||||
# _: Session = Depends(get_current_active_user)
|
||||
):
|
||||
if user := authenticate_user(form_data.username, form_data.password, db):
|
||||
return create_user_tokens(user_id=user.id, db=db, update_last_login=True)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect username or password",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/auto_login")
|
||||
async def auto_login(db: Session = Depends(get_session)):
|
||||
settings_manager = get_settings_manager()
|
||||
|
||||
if settings_manager.settings.AUTO_LOGIN:
|
||||
user_id = UUID("3fa85f64-5717-4562-b3fc-2c963f66afa6")
|
||||
return create_user_longterm_token(user_id, db)
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail={
|
||||
"message": "Auto login is disabled. Please enable it in the settings",
|
||||
"auto_login": False,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.post("/refresh")
|
||||
async def refresh_token(token: str):
|
||||
if token:
|
||||
return create_refresh_token(token)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid refresh token",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
133
src/backend/langflow/routers/users.py
Normal file
133
src/backend/langflow/routers/users.py
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
from uuid import UUID
|
||||
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from sqlmodel import Session, select
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
|
||||
from langflow.services.utils import get_session
|
||||
from langflow.auth.auth import get_current_active_user, get_password_hash
|
||||
from langflow.database.models.user import (
|
||||
User,
|
||||
UserAddModel,
|
||||
UserListModel,
|
||||
UserPatchModel,
|
||||
UsersResponse,
|
||||
update_user,
|
||||
)
|
||||
|
||||
router = APIRouter(tags=["Login"])
|
||||
|
||||
|
||||
@router.post("/user", response_model=UserListModel)
|
||||
def add_user(
|
||||
user: UserAddModel,
|
||||
db: Session = Depends(get_session),
|
||||
) -> User:
|
||||
"""
|
||||
Add a new user to the database.
|
||||
"""
|
||||
new_user = User(**user.dict())
|
||||
try:
|
||||
new_user.password = get_password_hash(user.password)
|
||||
|
||||
db.add(new_user)
|
||||
db.commit()
|
||||
db.refresh(new_user)
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=400, detail="User exists") from e
|
||||
|
||||
return new_user
|
||||
|
||||
|
||||
@router.get("/user", response_model=UserListModel)
|
||||
def read_current_user(current_user: User = Depends(get_current_active_user)) -> User:
|
||||
"""
|
||||
Retrieve the current user's data.
|
||||
"""
|
||||
return current_user
|
||||
|
||||
|
||||
@router.get("/users", response_model=UsersResponse)
|
||||
def read_all_users(
|
||||
skip: int = 0,
|
||||
limit: int = 10,
|
||||
_: Session = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_session),
|
||||
) -> UsersResponse:
|
||||
"""
|
||||
Retrieve a list of users from the database with pagination.
|
||||
"""
|
||||
query = select(User).offset(skip).limit(limit)
|
||||
users = db.execute(query).fetchall()
|
||||
|
||||
count_query = select(func.count()).select_from(User) # type: ignore
|
||||
total_count = db.execute(count_query).scalar()
|
||||
|
||||
return UsersResponse(
|
||||
total_count=total_count, # type: ignore
|
||||
users=[UserListModel(**dict(user.User)) for user in users],
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/user/{user_id}", response_model=UserListModel)
|
||||
def patch_user(
|
||||
user_id: UUID,
|
||||
user: UserPatchModel,
|
||||
_: Session = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_session),
|
||||
) -> User:
|
||||
"""
|
||||
Update an existing user's data.
|
||||
"""
|
||||
return update_user(user_id, user, db)
|
||||
|
||||
|
||||
@router.delete("/user/{user_id}")
|
||||
def delete_user(
|
||||
user_id: UUID,
|
||||
_: Session = Depends(get_current_active_user),
|
||||
db: Session = Depends(get_session),
|
||||
) -> dict:
|
||||
"""
|
||||
Delete a user from the database.
|
||||
"""
|
||||
user_db = db.query(User).filter(User.id == user_id).first()
|
||||
if not user_db:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
db.delete(user_db)
|
||||
db.commit()
|
||||
|
||||
return {"detail": "User deleted"}
|
||||
|
||||
|
||||
# TODO: REMOVE - Just for testing purposes
|
||||
@router.post("/super_user", response_model=User)
|
||||
def add_super_user_for_testing_purposes_delete_me_before_merge_into_dev(
|
||||
db: Session = Depends(get_session),
|
||||
) -> User:
|
||||
"""
|
||||
Add a superuser for testing purposes.
|
||||
(This should be removed in production)
|
||||
"""
|
||||
new_user = User(
|
||||
username="superuser",
|
||||
password="12345",
|
||||
is_active=True,
|
||||
is_superuser=True,
|
||||
last_login_at=None,
|
||||
)
|
||||
|
||||
try:
|
||||
new_user.password = get_password_hash(new_user.password)
|
||||
db.add(new_user)
|
||||
db.commit()
|
||||
db.refresh(new_user)
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=400, detail="User exists") from e
|
||||
|
||||
return new_user
|
||||
|
|
@ -1,11 +1,16 @@
|
|||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
from langflow.services.base import Service
|
||||
from langflow.services.utils import get_settings_manager
|
||||
from sqlmodel import SQLModel, Session, create_engine
|
||||
from langflow.utils.logger import logger
|
||||
from alembic.config import Config
|
||||
from alembic import command
|
||||
from langflow.services.database import models # noqa
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy.engine import Engine
|
||||
|
||||
|
||||
class DatabaseManager(Service):
|
||||
name = "database_manager"
|
||||
|
|
@ -17,7 +22,19 @@ class DatabaseManager(Service):
|
|||
langflow_dir = Path(__file__).parent.parent.parent
|
||||
self.script_location = langflow_dir / "alembic"
|
||||
self.alembic_cfg_path = langflow_dir / "alembic.ini"
|
||||
self.engine = create_engine(database_url)
|
||||
self.engine = self._create_engine()
|
||||
|
||||
def _create_engine(self) -> "Engine":
|
||||
"""Create the engine for the database."""
|
||||
settings_manager = get_settings_manager()
|
||||
if (
|
||||
settings_manager.settings.DATABASE_URL
|
||||
and settings_manager.settings.DATABASE_URL.startswith("sqlite")
|
||||
):
|
||||
connect_args = {"check_same_thread": False}
|
||||
else:
|
||||
connect_args = {}
|
||||
return create_engine(self.database_url, connect_args=connect_args)
|
||||
|
||||
def __enter__(self):
|
||||
self._session = Session(self.engine)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import contextlib
|
||||
import json
|
||||
import os
|
||||
import secrets
|
||||
from typing import Optional, List
|
||||
from pathlib import Path
|
||||
|
||||
|
|
@ -43,6 +44,14 @@ class Settings(BaseSettings):
|
|||
REDIS_PORT: int = 6379
|
||||
REDIS_DB: int = 0
|
||||
REDIS_CACHE_EXPIRE: int = 3600
|
||||
# Login settings
|
||||
SECRET_KEY: str = secrets.token_hex(32)
|
||||
ALGORITHM: str = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60
|
||||
REFRESH_TOKEN_EXPIRE_MINUTES: int = 70
|
||||
# If AUTO_LOGIN = True
|
||||
# > The application does not request login and logs in automatically as a super user.
|
||||
AUTO_LOGIN: bool = True
|
||||
|
||||
@validator("DATABASE_URL", pre=True)
|
||||
def set_database_url(cls, value):
|
||||
|
|
|
|||
|
|
@ -5,6 +5,47 @@ from langflow.template.frontend_node.base import FrontendNode
|
|||
|
||||
|
||||
class EmbeddingFrontendNode(FrontendNode):
|
||||
def add_extra_fields(self) -> None:
|
||||
if "VertexAI" in self.template.type_name:
|
||||
# Add credentials field which should of type file.
|
||||
self.template.add_field(
|
||||
TemplateField(
|
||||
field_type="file",
|
||||
required=False,
|
||||
show=True,
|
||||
name="credentials",
|
||||
value="",
|
||||
suffixes=[".json"],
|
||||
file_types=["json"],
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def format_vertex_field(field: TemplateField, name: str):
|
||||
if "VertexAI" in name:
|
||||
advanced_fields = [
|
||||
"verbose",
|
||||
"top_p",
|
||||
"top_k",
|
||||
"max_output_tokens",
|
||||
]
|
||||
if field.name in advanced_fields:
|
||||
field.advanced = True
|
||||
show_fields = [
|
||||
"verbose",
|
||||
"project",
|
||||
"location",
|
||||
"credentials",
|
||||
"max_output_tokens",
|
||||
"model_name",
|
||||
"temperature",
|
||||
"top_p",
|
||||
"top_k",
|
||||
]
|
||||
|
||||
if field.name in show_fields:
|
||||
field.show = True
|
||||
|
||||
@staticmethod
|
||||
def format_jina_fields(field: TemplateField):
|
||||
if "jina" in field.name:
|
||||
|
|
@ -41,10 +82,36 @@ class EmbeddingFrontendNode(FrontendNode):
|
|||
@staticmethod
|
||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||
FrontendNode.format_field(field, name)
|
||||
if name and "vertex" in name.lower():
|
||||
EmbeddingFrontendNode.format_vertex_field(field, name)
|
||||
field.advanced = not field.required
|
||||
field.show = True
|
||||
if field.name == "headers":
|
||||
field.show = False
|
||||
if field.name == "model_kwargs":
|
||||
field.field_type = "code"
|
||||
field.advanced = True
|
||||
field.show = True
|
||||
elif field.name in [
|
||||
"model_name",
|
||||
"temperature",
|
||||
"model_file",
|
||||
"model_type",
|
||||
"deployment_name",
|
||||
"credentials",
|
||||
]:
|
||||
field.advanced = False
|
||||
field.show = True
|
||||
if field.name == "credentials":
|
||||
field.field_type = "file"
|
||||
if name == "VertexAI" and field.name not in [
|
||||
"callbacks",
|
||||
"client",
|
||||
"stop",
|
||||
"tags",
|
||||
"cache",
|
||||
]:
|
||||
field.show = True
|
||||
|
||||
# Format Jina fields
|
||||
EmbeddingFrontendNode.format_jina_fields(field)
|
||||
|
|
|
|||
5225
src/frontend/package-lock.json
generated
5225
src/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -12,11 +12,13 @@
|
|||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "^1.0.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||
"@radix-ui/react-form": "^0.0.3",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-menubar": "^1.0.3",
|
||||
"@radix-ui/react-popover": "^1.0.6",
|
||||
"@radix-ui/react-progress": "^1.0.3",
|
||||
"@radix-ui/react-select": "^1.2.2",
|
||||
"@radix-ui/react-separator": "^1.0.3",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
|
|
@ -69,7 +71,8 @@
|
|||
"start": "vite",
|
||||
"build": "vite build",
|
||||
"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": {
|
||||
"extends": [
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import ErrorAlert from "./alerts/error";
|
|||
import NoticeAlert from "./alerts/notice";
|
||||
import SuccessAlert from "./alerts/success";
|
||||
import CrashErrorComponent from "./components/CrashErrorComponent";
|
||||
import LoadingComponent from "./components/loadingComponent";
|
||||
import { alertContext } from "./contexts/alertContext";
|
||||
import { locationContext } from "./contexts/locationContext";
|
||||
import { TabsContext } from "./contexts/tabsContext";
|
||||
|
|
@ -48,6 +47,10 @@ export default function App() {
|
|||
}>
|
||||
>([]);
|
||||
|
||||
const isLoginPage = location.pathname.includes("login");
|
||||
const isAdminPage = location.pathname.includes("admin");
|
||||
const isSignUpPage = location.pathname.includes("signup");
|
||||
|
||||
// Use effect hook to update alertsList when a new alert is added
|
||||
useEffect(() => {
|
||||
// If there is an error alert open with data, add it to the alertsList
|
||||
|
|
@ -135,15 +138,8 @@ export default function App() {
|
|||
}}
|
||||
FallbackComponent={CrashErrorComponent}
|
||||
>
|
||||
{loading ? (
|
||||
<div className="loading-page-panel">
|
||||
<LoadingComponent remSize={50} />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Router />
|
||||
</>
|
||||
)}
|
||||
{!isLoginPage && !isSignUpPage && <Header />}
|
||||
<Router />
|
||||
</ErrorBoundary>
|
||||
<div></div>
|
||||
<div className="app-div" style={{ zIndex: 999 }}>
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ export default function ParameterComponent({
|
|||
let groupedObj = groupByFamily(myData, tooltipTitle!, left, flow!);
|
||||
|
||||
if (groupedObj && groupedObj.length > 0) {
|
||||
//@ts-ignore
|
||||
//@ts-ignore
|
||||
refHtml.current = groupedObj.map((item, index) => {
|
||||
const Icon: any =
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { useSSE } from "../../contexts/SSEContext";
|
|||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import { typesContext } from "../../contexts/typesContext";
|
||||
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
|
||||
import { validationStatusType } from "../../types/components";
|
||||
import { NodeDataType } from "../../types/flow";
|
||||
import { cleanEdges } from "../../utils/reactflowUtils";
|
||||
import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils";
|
||||
|
|
@ -20,13 +21,14 @@ export default function GenericNode({
|
|||
}: {
|
||||
data: NodeDataType;
|
||||
selected: boolean;
|
||||
}) {
|
||||
}): JSX.Element {
|
||||
const [data, setData] = useState(olddata);
|
||||
const { updateFlow, flows, tabId } = useContext(TabsContext);
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
const { types, deleteNode, reactFlowInstance } = useContext(typesContext);
|
||||
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
|
||||
const { sseData, isBuilding } = useSSE();
|
||||
useEffect(() => {
|
||||
|
|
@ -41,7 +43,7 @@ export default function GenericNode({
|
|||
nodes: flow.data.nodes,
|
||||
},
|
||||
updateEdge: (edge) => {
|
||||
flow.data.edges = edge;
|
||||
flow.data!.edges = edge;
|
||||
reactFlowInstance.setEdges(edge);
|
||||
updateNodeInternals(data.id);
|
||||
},
|
||||
|
|
@ -77,7 +79,7 @@ export default function GenericNode({
|
|||
"generic-node-div"
|
||||
)}
|
||||
>
|
||||
{data.node.beta && (
|
||||
{data.node?.beta && (
|
||||
<div className="beta-badge-wrapper">
|
||||
<div className="beta-badge-content">BETA</div>
|
||||
</div>
|
||||
|
|
@ -90,9 +92,9 @@ export default function GenericNode({
|
|||
iconColor={`${nodeColors[types[data.type]]}`}
|
||||
/>
|
||||
<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">
|
||||
{data.node.display_name}
|
||||
{data.node?.display_name}
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
</div>
|
||||
|
|
@ -117,7 +119,9 @@ export default function GenericNode({
|
|||
{typeof validationStatus.params === "string"
|
||||
? validationStatus.params
|
||||
.split("\n")
|
||||
.map((line, index) => <div key={index}>{line}</div>)
|
||||
.map((line: string, index: number) => (
|
||||
<div key={index}>{line}</div>
|
||||
))
|
||||
: ""}
|
||||
</div>
|
||||
)
|
||||
|
|
@ -155,20 +159,20 @@ export default function GenericNode({
|
|||
</div>
|
||||
|
||||
<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) !== "_")
|
||||
.map((templateField: string, idx) => (
|
||||
<div key={idx}>
|
||||
{data.node.template[templateField].show &&
|
||||
!data.node.template[templateField].advanced ? (
|
||||
{data.node!.template[templateField].show &&
|
||||
!data.node!.template[templateField].advanced ? (
|
||||
<ParameterComponent
|
||||
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 +
|
||||
"|" +
|
||||
|
|
@ -178,39 +182,39 @@ export default function GenericNode({
|
|||
setData={setData}
|
||||
color={
|
||||
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
|
||||
}
|
||||
title={
|
||||
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(templateField)
|
||||
}
|
||||
info={data.node.template[templateField].info}
|
||||
info={data.node?.template[templateField].info}
|
||||
name={templateField}
|
||||
tooltipTitle={
|
||||
data.node.template[templateField].input_types?.join(
|
||||
data.node?.template[templateField].input_types?.join(
|
||||
"\n"
|
||||
) ?? data.node.template[templateField].type
|
||||
) ?? data.node?.template[templateField].type
|
||||
}
|
||||
required={data.node.template[templateField].required}
|
||||
required={data.node?.template[templateField].required}
|
||||
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 +
|
||||
"|" +
|
||||
data.id
|
||||
}
|
||||
left={true}
|
||||
type={data.node.template[templateField].type}
|
||||
type={data.node?.template[templateField].type}
|
||||
optionalHandle={
|
||||
data.node.template[templateField].input_types
|
||||
data.node?.template[templateField].input_types
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
|
|
@ -220,25 +224,25 @@ export default function GenericNode({
|
|||
))}
|
||||
<div
|
||||
className={classNames(
|
||||
Object.keys(data.node.template).length < 1 ? "hidden" : "",
|
||||
Object.keys(data.node!.template).length < 1 ? "hidden" : "",
|
||||
"flex-max-width justify-center"
|
||||
)}
|
||||
>
|
||||
{" "}
|
||||
</div>
|
||||
<ParameterComponent
|
||||
key={[data.type, data.id, ...data.node.base_classes].join("|")}
|
||||
key={[data.type, data.id, ...data.node!.base_classes].join("|")}
|
||||
data={data}
|
||||
setData={setData}
|
||||
color={nodeColors[types[data.type]] ?? nodeColors.unknown}
|
||||
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.type
|
||||
}
|
||||
tooltipTitle={data.node.base_classes.join("\n")}
|
||||
id={[data.type, data.id, ...data.node.base_classes].join("|")}
|
||||
type={data.node.base_classes.join("|")}
|
||||
tooltipTitle={data.node?.base_classes.join("\n")}
|
||||
id={[data.type, data.id, ...data.node!.base_classes].join("|")}
|
||||
type={data.node?.base_classes.join("|")}
|
||||
left={false}
|
||||
/>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { SingleAlertComponentType } from "../../../../types/alerts";
|
|||
export default function SingleAlert({
|
||||
dropItem,
|
||||
removeAlert,
|
||||
}: SingleAlertComponentType) {
|
||||
}: SingleAlertComponentType): JSX.Element {
|
||||
const [show, setShow] = useState(true);
|
||||
const type = dropItem.type;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ import { alertContext } from "../../contexts/alertContext";
|
|||
import { AlertDropdownType } from "../../types/alerts";
|
||||
import SingleAlert from "./components/singleAlertComponent";
|
||||
|
||||
export default function AlertDropdown({ children }: AlertDropdownType) {
|
||||
export default function AlertDropdown({
|
||||
children,
|
||||
}: AlertDropdownType): JSX.Element {
|
||||
const {
|
||||
notificationList,
|
||||
clearNotificationList,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export default function ErrorAlert({
|
|||
list = [],
|
||||
id,
|
||||
removeAlert,
|
||||
}: ErrorAlertType) {
|
||||
}: ErrorAlertType): JSX.Element {
|
||||
const [show, setShow] = useState(true);
|
||||
useEffect(() => {
|
||||
if (show) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useEffect } from "react";
|
|||
|
||||
export function useOnClickOutside(ref, handler) {
|
||||
useEffect(() => {
|
||||
const listener = (event) => {
|
||||
const listener = (event: Event) => {
|
||||
// Do nothing if clicking ref's element or its children
|
||||
if (!ref.current || ref.current.contains(event.target)) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export default function NoticeAlert({
|
|||
link = "",
|
||||
id,
|
||||
removeAlert,
|
||||
}: NoticeAlertType) {
|
||||
}: NoticeAlertType): JSX.Element {
|
||||
const [show, setShow] = useState(true);
|
||||
useEffect(() => {
|
||||
if (show) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export default function SuccessAlert({
|
|||
title,
|
||||
id,
|
||||
removeAlert,
|
||||
}: SuccessAlertType) {
|
||||
}: SuccessAlertType): JSX.Element {
|
||||
const [show, setShow] = useState(true);
|
||||
useEffect(() => {
|
||||
if (show) {
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ export default function AccordionComponent({
|
|||
children,
|
||||
open = [],
|
||||
keyValue,
|
||||
}: AccordionComponentType) {
|
||||
}: AccordionComponentType): JSX.Element {
|
||||
const [value, setValue] = useState(
|
||||
open.length === 0 ? "" : getOpenAccordion()
|
||||
);
|
||||
|
||||
function getOpenAccordion() {
|
||||
function getOpenAccordion(): string {
|
||||
let value = "";
|
||||
open.forEach((el) => {
|
||||
if (el == trigger) {
|
||||
|
|
@ -28,8 +28,8 @@ export default function AccordionComponent({
|
|||
return value;
|
||||
}
|
||||
|
||||
function handleClick() {
|
||||
value === "" ? setValue(keyValue) : setValue("");
|
||||
function handleClick(): void {
|
||||
value === "" ? setValue(keyValue!) : setValue("");
|
||||
}
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
|
|
@ -48,7 +48,7 @@ export default function AccordionComponent({
|
|||
onValueChange={setValue}
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
<AccordionItem value={keyValue} className="border-b">
|
||||
<AccordionItem value={keyValue!} className="border-b">
|
||||
<AccordionTrigger
|
||||
onClick={() => {
|
||||
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 (
|
||||
<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">
|
||||
|
|
|
|||
|
|
@ -3,19 +3,8 @@ import { Input } from "../../components/ui/input";
|
|||
import { Label } from "../../components/ui/label";
|
||||
import { Textarea } from "../../components/ui/textarea";
|
||||
import { readFlowsFromDatabase } from "../../controllers/API";
|
||||
|
||||
type InputProps = {
|
||||
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;
|
||||
};
|
||||
import { InputProps } from "../../types/components";
|
||||
import { FlowType } from "../../types/flow";
|
||||
|
||||
export const EditFlowSettings: React.FC<InputProps> = ({
|
||||
name,
|
||||
|
|
@ -27,13 +16,12 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
|||
tabId,
|
||||
setName,
|
||||
setDescription,
|
||||
updateFlow,
|
||||
}) => {
|
||||
}: InputProps): JSX.Element => {
|
||||
const [isMaxLength, setIsMaxLength] = useState(false);
|
||||
const nameLists = useRef([]);
|
||||
const nameLists = useRef<string[]>([]);
|
||||
useEffect(() => {
|
||||
readFlowsFromDatabase().then((flows) => {
|
||||
flows.forEach((flow) => {
|
||||
flows.forEach((flow: FlowType) => {
|
||||
nameLists.current.push(flow.name);
|
||||
});
|
||||
});
|
||||
|
|
@ -46,29 +34,22 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
|||
} else {
|
||||
setIsMaxLength(false);
|
||||
}
|
||||
if (invalidName !== undefined) {
|
||||
if (!nameLists.current.includes(value)) {
|
||||
setInvalidName(false);
|
||||
} else {
|
||||
setInvalidName(true);
|
||||
}
|
||||
if (!nameLists.current.includes(value)) {
|
||||
setInvalidName!(false);
|
||||
} else {
|
||||
setInvalidName!(true);
|
||||
}
|
||||
setName(value);
|
||||
setCurrentName(value);
|
||||
};
|
||||
|
||||
const [currentName, setCurrentName] = useState(name);
|
||||
|
||||
const [currentDescription, setCurrentDescription] = useState(description);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentName(name);
|
||||
setCurrentDescription(description);
|
||||
}, [name, description]);
|
||||
const [desc, setDesc] = useState(
|
||||
flows.find((flow) => flow.id === tabId)?.description
|
||||
);
|
||||
|
||||
const handleDescriptionChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
flows.find((f) => f.id === tabId).description = event.target.value;
|
||||
setCurrentDescription(flows.find((f) => f.id === tabId).description);
|
||||
flows.find((flow) => flow.id === tabId)!.description = event.target.value;
|
||||
setDesc(flows.find((flow) => flow.id === tabId)?.description);
|
||||
setDescription(event.target.value);
|
||||
};
|
||||
|
||||
|
|
|
|||
118
src/frontend/src/components/PaginatorComponent/index.tsx
Normal file
118
src/frontend/src/components/PaginatorComponent/index.tsx
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
import { useState } from "react";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "../../components/ui/select";
|
||||
import { PaginatorComponentType } from "../../types/components";
|
||||
import IconComponent from "../genericIconComponent";
|
||||
import { Button } from "../ui/button";
|
||||
|
||||
export default function PaginatorComponent({
|
||||
pageSize = 10,
|
||||
pageIndex = 1,
|
||||
rowsCount = [10, 20, 30],
|
||||
totalRowsCount = 0,
|
||||
paginate,
|
||||
}: PaginatorComponentType) {
|
||||
const [size, setPageSize] = useState(pageSize);
|
||||
const [index, setPageIndex] = useState(pageIndex);
|
||||
|
||||
const [maxIndex, setMaxPageIndex] = useState(
|
||||
Math.ceil(totalRowsCount / pageSize)
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between px-2">
|
||||
<div className="flex-1 text-sm text-muted-foreground"></div>
|
||||
<div className="flex items-center space-x-6 lg:space-x-8">
|
||||
<div className="flex items-center space-x-2">
|
||||
<p className="text-sm font-medium">Rows per page</p>
|
||||
<Select
|
||||
onValueChange={(pageSize: string) => {
|
||||
setPageSize(Number(pageSize));
|
||||
setMaxPageIndex(Math.ceil(totalRowsCount / Number(pageSize)));
|
||||
paginate(Number(pageSize), index);
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-[100px]">
|
||||
<SelectValue placeholder="10" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{rowsCount.map((item, i) => (
|
||||
<SelectItem key={i} value={item.toString()}>
|
||||
{item}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex w-[100px] items-center justify-center text-sm font-medium">
|
||||
Page {index} of {maxIndex}
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="hidden h-8 w-8 p-0 lg:flex"
|
||||
onClick={() => {
|
||||
setPageIndex(1);
|
||||
paginate(size, 1);
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Go to first page</span>
|
||||
<IconComponent name="ChevronsLeft" className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (index <= 1) {
|
||||
setPageIndex(1);
|
||||
paginate(size, 1);
|
||||
} else {
|
||||
{
|
||||
setPageIndex(index - 1);
|
||||
paginate(size, index - 1);
|
||||
}
|
||||
}
|
||||
}}
|
||||
variant="outline"
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<span className="sr-only">Go to previous page</span>
|
||||
<IconComponent name="ChevronLeft" className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (index >= maxIndex) {
|
||||
setPageIndex(maxIndex);
|
||||
paginate(size, maxIndex);
|
||||
} else {
|
||||
setPageIndex(index + 1);
|
||||
paginate(size, index + 1);
|
||||
}
|
||||
}}
|
||||
variant="outline"
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<span className="sr-only">Go to next page</span>
|
||||
<IconComponent name="ChevronRight" className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="hidden h-8 w-8 p-0 lg:flex"
|
||||
onClick={() => {
|
||||
setPageIndex(maxIndex);
|
||||
paginate(size, maxIndex);
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Go to last page</span>
|
||||
<IconComponent name="ChevronsRight" className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -3,16 +3,16 @@ import { RadialProgressType } from "../../types/components";
|
|||
export default function RadialProgressComponent({
|
||||
value,
|
||||
color,
|
||||
}: RadialProgressType) {
|
||||
}: RadialProgressType): JSX.Element {
|
||||
const style = {
|
||||
"--value": value * 100,
|
||||
"--value": value! * 100,
|
||||
"--size": "1.5rem",
|
||||
"--thickness": "2px",
|
||||
} as React.CSSProperties;
|
||||
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,20 +3,9 @@ import type { FC } from "react";
|
|||
import React from "react";
|
||||
import { Tooltip as ReactTooltip } from "react-tooltip";
|
||||
import "react-tooltip/dist/react-tooltip.css";
|
||||
import { TooltipProps } from "../../types/components";
|
||||
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> = ({
|
||||
selector,
|
||||
content,
|
||||
|
|
@ -27,7 +16,7 @@ const TooltipReact: FC<TooltipProps> = ({
|
|||
className,
|
||||
clickable,
|
||||
delayShow,
|
||||
}) => {
|
||||
}: TooltipProps): JSX.Element => {
|
||||
return (
|
||||
<div className="tooltip-container">
|
||||
{React.cloneElement(children as React.ReactElement, {
|
||||
|
|
@ -38,7 +27,7 @@ const TooltipReact: FC<TooltipProps> = ({
|
|||
content={content}
|
||||
className={classNames(
|
||||
"z-[9999] !bg-white !text-xs !font-normal !text-foreground !opacity-100 !shadow-md",
|
||||
className
|
||||
className!
|
||||
)}
|
||||
place={position}
|
||||
clickable={clickable}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import DOMPurify from "dompurify";
|
||||
import { SanitizedHTMLWrapperType } from "../../types/components";
|
||||
|
||||
const SanitizedHTMLWrapper = ({
|
||||
className,
|
||||
content,
|
||||
onClick,
|
||||
suppressWarning = false,
|
||||
}) => {
|
||||
}: SanitizedHTMLWrapperType): JSX.Element => {
|
||||
const sanitizedHTML = DOMPurify.sanitize(content);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export default function ShadTooltip({
|
|||
children,
|
||||
styleClasses,
|
||||
delayDuration = 500,
|
||||
}: ShadToolTipType) {
|
||||
}: ShadToolTipType): JSX.Element {
|
||||
return (
|
||||
<Tooltip delayDuration={delayDuration}>
|
||||
<TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ export default function Tooltip({
|
|||
children,
|
||||
title,
|
||||
placement,
|
||||
}: TooltipComponentType) {
|
||||
}: TooltipComponentType): JSX.Element {
|
||||
return (
|
||||
<LightTooltip placement={placement} title={title} arrow>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useContext } from "react";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import { FlowType } from "../../types/flow";
|
||||
import { cardComponentPropsType } from "../../types/components";
|
||||
import { gradients } from "../../utils/styleUtils";
|
||||
import IconComponent from "../genericIconComponent";
|
||||
import {
|
||||
|
|
@ -16,12 +16,7 @@ export const CardComponent = ({
|
|||
id,
|
||||
onDelete,
|
||||
button,
|
||||
}: {
|
||||
flow: FlowType;
|
||||
id: string;
|
||||
onDelete?: () => void;
|
||||
button?: JSX.Element;
|
||||
}) => {
|
||||
}: cardComponentPropsType): JSX.Element => {
|
||||
const { removeFlow } = useContext(TabsContext);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import { postBuildInit } from "../../../controllers/API";
|
|||
import { FlowType } from "../../../types/flow";
|
||||
|
||||
import { TabsContext } from "../../../contexts/tabsContext";
|
||||
import { parsedDataType } from "../../../types/components";
|
||||
import { TabsState } from "../../../types/tabs";
|
||||
import { validateNodes } from "../../../utils/reactflowUtils";
|
||||
import RadialProgressComponent from "../../RadialProgress";
|
||||
import IconComponent from "../../genericIconComponent";
|
||||
|
|
@ -21,7 +23,7 @@ export default function BuildTrigger({
|
|||
flow: FlowType;
|
||||
setIsBuilt: any;
|
||||
isBuilt: boolean;
|
||||
}) {
|
||||
}): JSX.Element {
|
||||
const { updateSSEData, isBuilding, setIsBuilding, sseData } = useSSE();
|
||||
const { reactFlowInstance } = useContext(typesContext);
|
||||
const { setTabsState } = useContext(TabsContext);
|
||||
|
|
@ -30,12 +32,12 @@ export default function BuildTrigger({
|
|||
const eventClick = isBuilding ? "pointer-events-none" : "";
|
||||
const [progress, setProgress] = useState(0);
|
||||
|
||||
async function handleBuild(flow: FlowType) {
|
||||
async function handleBuild(flow: FlowType): Promise<void> {
|
||||
try {
|
||||
if (isBuilding) {
|
||||
return;
|
||||
}
|
||||
const errors = validateNodes(reactFlowInstance);
|
||||
const errors = validateNodes(reactFlowInstance!);
|
||||
if (errors.length > 0) {
|
||||
setErrorData({
|
||||
title: "Oops! Looks like you missed something",
|
||||
|
|
@ -69,7 +71,7 @@ export default function BuildTrigger({
|
|||
const response = await postBuildInit(flow);
|
||||
const { flowId } = response.data;
|
||||
// Step 2: Use the session ID to establish an SSE connection using EventSource
|
||||
let validationResults = [];
|
||||
let validationResults: boolean[] = [];
|
||||
let finished = false;
|
||||
const apiUrl = `/api/v1/build/stream/${flowId}`;
|
||||
const eventSource = new EventSource(apiUrl);
|
||||
|
|
@ -91,7 +93,8 @@ export default function BuildTrigger({
|
|||
// If the event is a log, log it
|
||||
setSuccessData({ title: parsedData.log });
|
||||
} else if (parsedData.input_keys !== undefined) {
|
||||
setTabsState((old) => {
|
||||
//@ts-ignore
|
||||
setTabsState((old: TabsState) => {
|
||||
return {
|
||||
...old,
|
||||
[flowId]: {
|
||||
|
|
@ -120,13 +123,13 @@ export default function BuildTrigger({
|
|||
// Step 3: Wait for the stream to finish
|
||||
while (!finished) {
|
||||
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
|
||||
return validationResults.every((result) => result);
|
||||
}
|
||||
|
||||
function processStreamResult(parsedData) {
|
||||
function processStreamResult(parsedData: parsedDataType) {
|
||||
// Process each chunk of data here
|
||||
// Parse the chunk and update the context
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -8,12 +8,18 @@ import {
|
|||
FLOW_NOT_BUILT_TITLE,
|
||||
} from "../../../constants/constants";
|
||||
import { alertContext } from "../../../contexts/alertContext";
|
||||
import { chatTriggerPropType } from "../../../types/components";
|
||||
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);
|
||||
|
||||
function handleClick() {
|
||||
function handleClick(): void {
|
||||
if (isBuilt) {
|
||||
if (canOpen) {
|
||||
setOpen(true);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { getBuildStatus } from "../../controllers/API";
|
|||
import FormModal from "../../modals/formModal";
|
||||
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 [canOpen, setCanOpen] = useState(false);
|
||||
const { tabsState, isBuilt, setIsBuilt } = useContext(TabsContext);
|
||||
|
|
@ -44,11 +44,11 @@ export default function Chat({ flow }: ChatType) {
|
|||
}, [flow]);
|
||||
|
||||
const prevNodesRef = useRef<any[] | undefined>();
|
||||
const nodes = useNodes();
|
||||
const nodes: NodeType[] = useNodes();
|
||||
useEffect(() => {
|
||||
const prevNodes = prevNodesRef.current;
|
||||
const currentNodes = nodes.map((node: NodeType) =>
|
||||
_.cloneDeep(node.data.node.template)
|
||||
_.cloneDeep(node.data.node?.template)
|
||||
);
|
||||
if (
|
||||
tabsState &&
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export default function CodeAreaComponent({
|
|||
dynamic={dynamic}
|
||||
value={myValue}
|
||||
nodeClass={nodeClass}
|
||||
setNodeClass={setNodeClass}
|
||||
setNodeClass={setNodeClass!}
|
||||
setValue={(value: string) => {
|
||||
setMyValue(value);
|
||||
onChange(value);
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import {
|
|||
TabsTrigger,
|
||||
} from "../../components/ui/tabs";
|
||||
import { darkContext } from "../../contexts/darkContext";
|
||||
import { FlowType } from "../../types/flow/index";
|
||||
import { codeTabsPropsType } from "../../types/components";
|
||||
import { classNames } from "../../utils/utils";
|
||||
import IconComponent from "../genericIconComponent";
|
||||
|
||||
|
|
@ -40,28 +40,15 @@ export default function CodeTabsComponent({
|
|||
setActiveTab,
|
||||
isMessage,
|
||||
tweaks,
|
||||
}: {
|
||||
flow?: FlowType;
|
||||
tabs: any;
|
||||
activeTab: string;
|
||||
setActiveTab: any;
|
||||
isMessage?: boolean;
|
||||
tweaks?: {
|
||||
tweak?: any;
|
||||
tweaksList?: any;
|
||||
buildContent?: any;
|
||||
getValue?: any;
|
||||
buildTweakObject?: any;
|
||||
};
|
||||
}) {
|
||||
}: codeTabsPropsType) {
|
||||
const [isCopied, setIsCopied] = useState<Boolean>(false);
|
||||
const [data, setData] = useState(flow ? flow["data"]["nodes"] : null);
|
||||
const [openAccordion, setOpenAccordion] = useState([]);
|
||||
const [data, setData] = useState(flow ? flow["data"]!["nodes"] : null);
|
||||
const [openAccordion, setOpenAccordion] = useState<string[]>([]);
|
||||
const { dark } = useContext(darkContext);
|
||||
|
||||
useEffect(() => {
|
||||
if (flow && flow["data"]["nodes"]) {
|
||||
setData(flow["data"]["nodes"]);
|
||||
if (flow && flow["data"]!["nodes"]) {
|
||||
setData(flow["data"]!["nodes"]);
|
||||
}
|
||||
}, [flow]);
|
||||
|
||||
|
|
@ -102,8 +89,8 @@ export default function CodeTabsComponent({
|
|||
};
|
||||
|
||||
function openAccordions() {
|
||||
let accordionsToOpen = [];
|
||||
tweaks.tweak.current.forEach((el) => {
|
||||
let accordionsToOpen: string[] = [];
|
||||
tweaks?.tweak!.current.forEach((el) => {
|
||||
Object.keys(el).forEach((key) => {
|
||||
if (Object.keys(el[key]).length > 0) {
|
||||
accordionsToOpen.push(key);
|
||||
|
|
@ -205,9 +192,9 @@ export default function CodeTabsComponent({
|
|||
: "overflow-hidden"
|
||||
)}
|
||||
>
|
||||
{data.map((node: any, index) => (
|
||||
{data?.map((node: any, index) => (
|
||||
<div className="px-3" key={index}>
|
||||
{tweaks.tweaksList.current.includes(
|
||||
{tweaks?.tweaksList!.current.includes(
|
||||
node["data"]["id"]
|
||||
) && (
|
||||
<AccordionComponent
|
||||
|
|
@ -290,14 +277,14 @@ export default function CodeTabsComponent({
|
|||
setData((old) => {
|
||||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList[
|
||||
newInputList![
|
||||
index
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
return newInputList;
|
||||
});
|
||||
tweaks.buildTweakObject(
|
||||
tweaks.buildTweakObject!(
|
||||
node["data"]["id"],
|
||||
target,
|
||||
node.data.node.template[
|
||||
|
|
@ -310,7 +297,7 @@ export default function CodeTabsComponent({
|
|||
templateField
|
||||
].multiline ? (
|
||||
<ShadTooltip
|
||||
content={tweaks.buildContent(
|
||||
content={tweaks.buildContent!(
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].value
|
||||
|
|
@ -339,14 +326,14 @@ export default function CodeTabsComponent({
|
|||
setData((old) => {
|
||||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList[
|
||||
newInputList![
|
||||
index
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
return newInputList;
|
||||
});
|
||||
tweaks.buildTweakObject(
|
||||
tweaks.buildTweakObject!(
|
||||
node["data"]["id"],
|
||||
target,
|
||||
node.data.node
|
||||
|
|
@ -384,14 +371,14 @@ export default function CodeTabsComponent({
|
|||
setData((old) => {
|
||||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList[
|
||||
newInputList![
|
||||
index
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
return newInputList;
|
||||
});
|
||||
tweaks.buildTweakObject(
|
||||
tweaks.buildTweakObject!(
|
||||
node["data"]["id"],
|
||||
target,
|
||||
node.data.node.template[
|
||||
|
|
@ -417,14 +404,14 @@ export default function CodeTabsComponent({
|
|||
setData((old) => {
|
||||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList[
|
||||
newInputList![
|
||||
index
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = e;
|
||||
return newInputList;
|
||||
});
|
||||
tweaks.buildTweakObject(
|
||||
tweaks.buildTweakObject!(
|
||||
node["data"]["id"],
|
||||
e,
|
||||
node.data.node.template[
|
||||
|
|
@ -440,7 +427,7 @@ export default function CodeTabsComponent({
|
|||
templateField
|
||||
].type === "file" ? (
|
||||
<ShadTooltip
|
||||
content={tweaks.buildContent(
|
||||
content={tweaks.buildContent!(
|
||||
!node.data.node.template[
|
||||
templateField
|
||||
].value ||
|
||||
|
|
@ -508,14 +495,14 @@ export default function CodeTabsComponent({
|
|||
setData((old) => {
|
||||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList[
|
||||
newInputList![
|
||||
index
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
return newInputList;
|
||||
});
|
||||
tweaks.buildTweakObject(
|
||||
tweaks.buildTweakObject!(
|
||||
node["data"]["id"],
|
||||
target,
|
||||
node.data.node.template[
|
||||
|
|
@ -544,14 +531,14 @@ export default function CodeTabsComponent({
|
|||
setData((old) => {
|
||||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList[
|
||||
newInputList![
|
||||
index
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
return newInputList;
|
||||
});
|
||||
tweaks.buildTweakObject(
|
||||
tweaks.buildTweakObject!(
|
||||
node["data"]["id"],
|
||||
target,
|
||||
node.data.node.template[
|
||||
|
|
@ -596,14 +583,14 @@ export default function CodeTabsComponent({
|
|||
setData((old) => {
|
||||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList[
|
||||
newInputList![
|
||||
index
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
return newInputList;
|
||||
});
|
||||
tweaks.buildTweakObject(
|
||||
tweaks.buildTweakObject!(
|
||||
node["data"]["id"],
|
||||
target,
|
||||
node.data.node.template[
|
||||
|
|
@ -617,7 +604,7 @@ export default function CodeTabsComponent({
|
|||
templateField
|
||||
].type === "prompt" ? (
|
||||
<ShadTooltip
|
||||
content={tweaks.buildContent(
|
||||
content={tweaks.buildContent!(
|
||||
!node.data.node.template[
|
||||
templateField
|
||||
].value ||
|
||||
|
|
@ -651,14 +638,14 @@ export default function CodeTabsComponent({
|
|||
setData((old) => {
|
||||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList[
|
||||
newInputList![
|
||||
index
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
return newInputList;
|
||||
});
|
||||
tweaks.buildTweakObject(
|
||||
tweaks.buildTweakObject!(
|
||||
node["data"]["id"],
|
||||
target,
|
||||
node.data.node.template[
|
||||
|
|
@ -673,8 +660,8 @@ export default function CodeTabsComponent({
|
|||
templateField
|
||||
].type === "code" ? (
|
||||
<ShadTooltip
|
||||
content={tweaks.buildContent(
|
||||
tweaks.getValue(
|
||||
content={tweaks.buildContent!(
|
||||
tweaks.getValue!(
|
||||
node.data.node.template[
|
||||
templateField
|
||||
].value,
|
||||
|
|
@ -706,14 +693,14 @@ export default function CodeTabsComponent({
|
|||
setData((old) => {
|
||||
let newInputList =
|
||||
cloneDeep(old);
|
||||
newInputList[
|
||||
newInputList![
|
||||
index
|
||||
].data.node.template[
|
||||
templateField
|
||||
].value = target;
|
||||
return newInputList;
|
||||
});
|
||||
tweaks.buildTweakObject(
|
||||
tweaks.buildTweakObject!(
|
||||
node["data"]["id"],
|
||||
target,
|
||||
node.data.node.template[
|
||||
|
|
@ -742,7 +729,7 @@ export default function CodeTabsComponent({
|
|||
</AccordionComponent>
|
||||
)}
|
||||
|
||||
{tweaks.tweaksList.current.length === 0 && (
|
||||
{tweaks?.tweaksList!.current.length === 0 && (
|
||||
<>
|
||||
<div className="pt-3">
|
||||
No tweaks are available for this flow.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export default function Dropdown({
|
|||
editNode = false,
|
||||
numberOfOptions = 0,
|
||||
apiModal = false,
|
||||
}: DropDownComponentType) {
|
||||
}: DropDownComponentType): JSX.Element {
|
||||
let [internalValue, setInternalValue] = useState(
|
||||
value === "" || !value ? "Choose an option" : value
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export default function FloatComponent({
|
|||
onChange,
|
||||
disabled,
|
||||
editNode = false,
|
||||
}: FloatComponentType) {
|
||||
}: FloatComponentType): JSX.Element {
|
||||
const step = 0.1;
|
||||
const min = 0;
|
||||
const max = 1;
|
||||
|
|
|
|||
|
|
@ -7,5 +7,11 @@ export default function IconComponent({
|
|||
iconColor,
|
||||
}: IconComponentProps): JSX.Element {
|
||||
const TargetIcon = nodeIconsLucide[name] ?? nodeIconsLucide["unknown"];
|
||||
return <TargetIcon className={className} style={{ color: iconColor }} />;
|
||||
return (
|
||||
<TargetIcon
|
||||
strokeWidth={1.5}
|
||||
className={className}
|
||||
style={{ color: iconColor }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,10 +12,11 @@ import { Link, useNavigate } from "react-router-dom";
|
|||
import { alertContext } from "../../../../contexts/alertContext";
|
||||
import { undoRedoContext } from "../../../../contexts/undoRedoContext";
|
||||
import FlowSettingsModal from "../../../../modals/flowSettingsModal";
|
||||
import { menuBarPropsType } from "../../../../types/components";
|
||||
import IconComponent from "../../../genericIconComponent";
|
||||
import { Button } from "../../../ui/button";
|
||||
|
||||
export const MenuBar = ({ flows, tabId }) => {
|
||||
export const MenuBar = ({ flows, tabId }: menuBarPropsType): JSX.Element => {
|
||||
const { addFlow } = useContext(TabsContext);
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const { undo, redo } = useContext(undoRedoContext);
|
||||
|
|
@ -25,12 +26,12 @@ export const MenuBar = ({ flows, tabId }) => {
|
|||
|
||||
function handleAddFlow() {
|
||||
try {
|
||||
addFlow(null, true).then((id) => {
|
||||
addFlow(undefined, true).then((id) => {
|
||||
navigate("/flow/" + id);
|
||||
});
|
||||
// saveFlowStyleInDataBase();
|
||||
} catch (err) {
|
||||
setErrorData(err);
|
||||
setErrorData(err as { title: string; list?: Array<string> });
|
||||
}
|
||||
}
|
||||
let current_flow = flows.find((flow) => flow.id === tabId);
|
||||
|
|
@ -45,7 +46,9 @@ export const MenuBar = ({ flows, tabId }) => {
|
|||
<DropdownMenuTrigger asChild>
|
||||
<Button asChild variant="primary" size="sm">
|
||||
<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" />
|
||||
</div>
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { Button } from "../ui/button";
|
|||
import { Separator } from "../ui/separator";
|
||||
import MenuBar from "./components/menuBar";
|
||||
|
||||
export default function Header() {
|
||||
export default function Header(): JSX.Element {
|
||||
const { flows, tabId } = useContext(TabsContext);
|
||||
const { dark, setDark } = useContext(darkContext);
|
||||
const { notificationCenter } = useContext(alertContext);
|
||||
|
|
@ -31,24 +31,12 @@ export default function Header() {
|
|||
return (
|
||||
<div className="header-arrangement">
|
||||
<div className="header-start-display">
|
||||
{tabId === "" || !tabId ? (
|
||||
<div className="ml-2">
|
||||
<a
|
||||
href="https://www.langflow.org/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="header-waitlist-link-box"
|
||||
>
|
||||
<span className="pr-1 text-2xl">⛓️</span>
|
||||
<span>Join The Waitlist</span>
|
||||
</a>
|
||||
</div>
|
||||
) : (
|
||||
<Link to="/">
|
||||
<span className="ml-4 text-2xl">⛓️</span>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<Link to="/">
|
||||
<span className="ml-4 text-2xl">⛓️</span>
|
||||
</Link>
|
||||
<Button variant="outline" className="">
|
||||
Sign out
|
||||
</Button>
|
||||
{flows.findIndex((f) => tabId === f.id) !== -1 && tabId !== "" && (
|
||||
<MenuBar flows={flows} tabId={tabId} />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import * as Form from "@radix-ui/react-form";
|
||||
import { useEffect, useState } from "react";
|
||||
import { InputComponentType } from "../../types/components";
|
||||
import { handleKeyDown } from "../../utils/reactflowUtils";
|
||||
import { classNames } from "../../utils/utils";
|
||||
import { Input } from "../ui/input";
|
||||
|
||||
|
|
@ -8,9 +8,13 @@ export default function InputComponent({
|
|||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
required = false,
|
||||
isForm = false,
|
||||
password,
|
||||
editNode = false,
|
||||
}: InputComponentType) {
|
||||
placeholder = "Type something...",
|
||||
className,
|
||||
}: InputComponentType): JSX.Element {
|
||||
const [pwdVisible, setPwdVisible] = useState(false);
|
||||
|
||||
// Clear component state
|
||||
|
|
@ -22,23 +26,47 @@ export default function InputComponent({
|
|||
|
||||
return (
|
||||
<div className="relative w-full">
|
||||
<Input
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
className={classNames(
|
||||
password && !pwdVisible && value !== "" ? " text-clip password " : "",
|
||||
editNode ? " input-edit-node " : "",
|
||||
password && editNode ? "pr-8" : "",
|
||||
password && !editNode ? "pr-10" : ""
|
||||
)}
|
||||
placeholder={password && editNode ? "Key" : "Type something..."}
|
||||
onChange={(event) => {
|
||||
onChange(event.target.value);
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
handleKeyDown(e, value, "");
|
||||
}}
|
||||
/>
|
||||
{isForm ? (
|
||||
<Form.Control asChild>
|
||||
<Input
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
required={required}
|
||||
className={classNames(
|
||||
password && !pwdVisible && value !== ""
|
||||
? " text-clip password "
|
||||
: "",
|
||||
editNode ? " input-edit-node " : "",
|
||||
password && editNode ? "pr-8" : "",
|
||||
password && !editNode ? "pr-10" : "",
|
||||
className!
|
||||
)}
|
||||
placeholder={password && editNode ? "Key" : placeholder}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Form.Control>
|
||||
) : (
|
||||
<Input
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
required={required}
|
||||
className={classNames(
|
||||
password && !pwdVisible && value !== ""
|
||||
? " text-clip password "
|
||||
: "",
|
||||
editNode ? " input-edit-node " : "",
|
||||
password && editNode ? "pr-8" : "",
|
||||
password && !editNode ? "pr-10" : "",
|
||||
className!
|
||||
)}
|
||||
placeholder={password && editNode ? "Key" : placeholder}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{password && (
|
||||
<button
|
||||
className={classNames(
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export default function InputFileComponent({
|
|||
fileTypes,
|
||||
onFileChange,
|
||||
editNode = false,
|
||||
}: FileComponentType) {
|
||||
}: FileComponentType): JSX.Element {
|
||||
const [myValue, setMyValue] = useState(value);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
|
|
@ -41,7 +41,7 @@ export default function InputFileComponent({
|
|||
setMyValue(value);
|
||||
}, [value]);
|
||||
|
||||
const handleButtonClick = () => {
|
||||
const handleButtonClick = (): void => {
|
||||
// Create a file input element
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
|
|
@ -49,7 +49,7 @@ export default function InputFileComponent({
|
|||
input.style.display = "none"; // Hidden from view
|
||||
input.multiple = false; // Allow only one file selection
|
||||
|
||||
input.onchange = (event: Event) => {
|
||||
input.onchange = (event: Event): void => {
|
||||
setLoading(true);
|
||||
|
||||
// Get the selected file
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export default function InputListComponent({
|
|||
onChange,
|
||||
disabled,
|
||||
editNode = false,
|
||||
}: InputListComponentType) {
|
||||
}: InputListComponentType): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
onChange([""]);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export default function IntComponent({
|
|||
onChange,
|
||||
disabled,
|
||||
editNode = false,
|
||||
}: FloatComponentType) {
|
||||
}: FloatComponentType): JSX.Element {
|
||||
const min = 0;
|
||||
|
||||
// Clear component state
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
type LoadingComponentProps = {
|
||||
remSize: number;
|
||||
};
|
||||
import { LoadingComponentProps } from "../../types/components";
|
||||
|
||||
export default function LoadingComponent({ remSize }: LoadingComponentProps) {
|
||||
export default function LoadingComponent({
|
||||
remSize,
|
||||
}: LoadingComponentProps): JSX.Element {
|
||||
return (
|
||||
<div role="status" className="m-auto w-min">
|
||||
<svg
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export default function PromptAreaComponent({
|
|||
onChange,
|
||||
disabled,
|
||||
editNode = false,
|
||||
}: TextAreaComponentType) {
|
||||
}: TextAreaComponentType): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
onChange("");
|
||||
|
|
@ -23,9 +23,9 @@ export default function PromptAreaComponent({
|
|||
|
||||
useEffect(() => {
|
||||
if (value !== "" && !editNode) {
|
||||
postValidatePrompt(field_name, value, nodeClass).then((apiReturn) => {
|
||||
postValidatePrompt(field_name!, value, nodeClass!).then((apiReturn) => {
|
||||
if (apiReturn.data) {
|
||||
setNodeClass(apiReturn.data.frontend_node);
|
||||
setNodeClass!(apiReturn.data.frontend_node);
|
||||
// need to update reactFlowInstance to re-render the nodes.
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export default function TextAreaComponent({
|
|||
onChange,
|
||||
disabled,
|
||||
editNode = false,
|
||||
}: TextAreaComponentType) {
|
||||
}: TextAreaComponentType): JSX.Element {
|
||||
// Clear text area
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export default function ToggleComponent({
|
|||
enabled,
|
||||
setEnabled,
|
||||
disabled,
|
||||
}: ToggleComponentType) {
|
||||
}: ToggleComponentType): JSX.Element {
|
||||
// set component state as disabled
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ export default function ToggleShadComponent({
|
|||
setEnabled,
|
||||
disabled,
|
||||
size,
|
||||
}: ToggleComponentType) {
|
||||
}: ToggleComponentType): JSX.Element {
|
||||
let scaleX, scaleY;
|
||||
switch (size) {
|
||||
case "small":
|
||||
|
|
|
|||
|
|
@ -23,14 +23,14 @@ export default function RenameLabel(props) {
|
|||
});
|
||||
if (inputRef.current) {
|
||||
setTimeout(() => {
|
||||
inputRef.current.focus();
|
||||
inputRef.current?.focus();
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
resizeInput();
|
||||
}, [isRename]);
|
||||
|
||||
const inputRef = useRef(null);
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
const resizeInput = () => {
|
||||
const input = inputRef.current;
|
||||
|
|
|
|||
120
src/frontend/src/components/ui/select.tsx
Normal file
120
src/frontend/src/components/ui/select.tsx
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
"use client";
|
||||
|
||||
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||
import { Check, ChevronDown } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { cn } from "../../utils/utils";
|
||||
|
||||
const Select = SelectPrimitive.Root;
|
||||
|
||||
const SelectGroup = SelectPrimitive.Group;
|
||||
|
||||
const SelectValue = SelectPrimitive.Value;
|
||||
|
||||
const SelectTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
));
|
||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
||||
|
||||
const SelectContent = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
>(({ className, children, position = "popper", ...props }, ref) => (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
>
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
));
|
||||
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
||||
|
||||
const SelectLabel = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
||||
|
||||
const SelectItem = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
));
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
||||
|
||||
const SelectSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectLabel,
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
};
|
||||
|
|
@ -509,5 +509,93 @@ export const URL_EXCLUDED_FROM_ERROR_RETRIES = [
|
|||
"/api/v1/custom_component",
|
||||
"/api/v1/validate/prompt",
|
||||
];
|
||||
export const CONTROL_INPUT_STATE = {
|
||||
password: "",
|
||||
cnfPassword: "",
|
||||
username: "",
|
||||
};
|
||||
|
||||
export const skipNodeUpdate = ["CustomComponent"];
|
||||
export const CONTROL_LOGIN_STATE = {
|
||||
username: "",
|
||||
password: "",
|
||||
};
|
||||
export const tabsCode = [];
|
||||
|
||||
export function tabsArray(codes: string[], method: number) {
|
||||
if (!method) return;
|
||||
if (method === 0) {
|
||||
return [
|
||||
{
|
||||
name: "cURL",
|
||||
mode: "bash",
|
||||
image: "https://curl.se/logo/curl-symbol-transparent.png",
|
||||
language: "sh",
|
||||
code: codes[0],
|
||||
},
|
||||
{
|
||||
name: "Python API",
|
||||
mode: "python",
|
||||
image:
|
||||
"https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
|
||||
language: "py",
|
||||
code: codes[1],
|
||||
},
|
||||
{
|
||||
name: "Python Code",
|
||||
mode: "python",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
language: "py",
|
||||
code: codes[2],
|
||||
},
|
||||
{
|
||||
name: "Chat Widget HTML",
|
||||
description:
|
||||
"Insert this code anywhere in your <body> tag. To use with react and other libs, check our <a class='link-color' href='https://langflow.org/guidelines/widget'>documentation</a>.",
|
||||
mode: "html",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
language: "py",
|
||||
code: codes[3],
|
||||
},
|
||||
];
|
||||
}
|
||||
return [
|
||||
{
|
||||
name: "cURL",
|
||||
mode: "bash",
|
||||
image: "https://curl.se/logo/curl-symbol-transparent.png",
|
||||
language: "sh",
|
||||
code: codes[0],
|
||||
},
|
||||
{
|
||||
name: "Python API",
|
||||
mode: "python",
|
||||
image:
|
||||
"https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
|
||||
language: "py",
|
||||
code: codes[1],
|
||||
},
|
||||
{
|
||||
name: "Python Code",
|
||||
mode: "python",
|
||||
language: "py",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
code: codes[2],
|
||||
},
|
||||
{
|
||||
name: "Chat Widget HTML",
|
||||
description:
|
||||
"Insert this code anywhere in your <body> tag. To use with react and other libs, check our <a class='link-color' href='https://langflow.org/guidelines/widget'>documentation</a>.",
|
||||
mode: "html",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
language: "py",
|
||||
code: codes[3],
|
||||
},
|
||||
{
|
||||
name: "Tweaks",
|
||||
mode: "python",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
language: "py",
|
||||
code: codes[4],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,9 @@
|
|||
import { createContext, ReactNode, useState } from "react";
|
||||
import { AlertItemType } from "../types/alerts";
|
||||
import { alertContextType } from "../types/typesContext";
|
||||
|
||||
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;
|
||||
loading: boolean;
|
||||
setLoading: (newState: boolean) => void;
|
||||
};
|
||||
|
||||
//initial values to alertContextType
|
||||
const initialValue: alertContextType = {
|
||||
errorData: { title: "", list: [] },
|
||||
|
|
@ -70,7 +47,7 @@ export function AlertProvider({ children }: { children: ReactNode }) {
|
|||
});
|
||||
const [successOpen, setSuccessOpen] = useState(false);
|
||||
const [notificationCenter, setNotificationCenter] = useState(false);
|
||||
const [notificationList, setNotificationList] = useState([]);
|
||||
const [notificationList, setNotificationList] = useState<AlertItemType[]>([]);
|
||||
const pushNotificationList = (notification: AlertItemType) => {
|
||||
setNotificationList((old) => {
|
||||
let newNotificationList = _.cloneDeep(old);
|
||||
|
|
|
|||
77
src/frontend/src/contexts/authContext.tsx
Normal file
77
src/frontend/src/contexts/authContext.tsx
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { createContext, useEffect, useState } from "react";
|
||||
import { AuthContextType, userData } from "../types/contexts/auth";
|
||||
|
||||
const initialValue: AuthContextType = {
|
||||
isAuthenticated: false,
|
||||
accessToken: null,
|
||||
login: () => {},
|
||||
logout: () => {},
|
||||
refreshAccessToken: () => Promise.resolve(),
|
||||
userData: null,
|
||||
setUserData: () => {},
|
||||
};
|
||||
|
||||
const AuthContext = createContext<AuthContextType>(initialValue);
|
||||
|
||||
export function AuthProvider({ children }): React.ReactElement {
|
||||
const [accessToken, setAccessToken] = useState<string | null>(null);
|
||||
const [userData, setUserData] = useState<userData | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const storedAccessToken = localStorage.getItem("access_token");
|
||||
if (storedAccessToken) {
|
||||
setAccessToken(storedAccessToken);
|
||||
}
|
||||
}, []);
|
||||
|
||||
function login(newAccessToken: string, refreshToken: string) {
|
||||
localStorage.setItem("access_token", newAccessToken);
|
||||
setAccessToken(newAccessToken);
|
||||
// Store refreshToken if needed
|
||||
}
|
||||
|
||||
function logout() {
|
||||
localStorage.removeItem("access_token");
|
||||
// Clear refreshToken if used
|
||||
setAccessToken(null);
|
||||
}
|
||||
|
||||
async function refreshAccessToken(refreshToken: string) {
|
||||
try {
|
||||
// Call your API to refresh the access token using the refresh token
|
||||
const response = await fetch("/api/refresh-token", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ refreshToken }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
login(data.accessToken, refreshToken);
|
||||
} else {
|
||||
logout();
|
||||
}
|
||||
} catch (error) {
|
||||
logout();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
// !! to convert string to boolean
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
isAuthenticated: !!accessToken,
|
||||
accessToken,
|
||||
login,
|
||||
logout,
|
||||
refreshAccessToken,
|
||||
setUserData,
|
||||
userData,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,9 +1,5 @@
|
|||
import { createContext, useEffect, useState } from "react";
|
||||
|
||||
type darkContextType = {
|
||||
dark: {};
|
||||
setDark: (newState: {}) => void;
|
||||
};
|
||||
import { darkContextType } from "../types/typesContext";
|
||||
|
||||
const initialValue = {
|
||||
dark: {},
|
||||
|
|
@ -14,13 +10,13 @@ export const darkContext = createContext<darkContextType>(initialValue);
|
|||
|
||||
export function DarkProvider({ children }) {
|
||||
const [dark, setDark] = useState(
|
||||
JSON.parse(window.localStorage.getItem("isDark")) ?? false
|
||||
JSON.parse(window.localStorage.getItem("isDark")!) ?? false
|
||||
);
|
||||
useEffect(() => {
|
||||
if (dark) {
|
||||
document.getElementById("body").classList.add("dark");
|
||||
document.getElementById("body")!.classList.add("dark");
|
||||
} else {
|
||||
document.getElementById("body").classList.remove("dark");
|
||||
document.getElementById("body")!.classList.remove("dark");
|
||||
}
|
||||
window.localStorage.setItem("isDark", dark.toString());
|
||||
}, [dark]);
|
||||
|
|
|
|||
|
|
@ -1,34 +1,5 @@
|
|||
import { createContext, ReactNode, useState } from "react";
|
||||
|
||||
//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;
|
||||
};
|
||||
import { locationContextType } from "../types/typesContext";
|
||||
|
||||
//initial value for location context
|
||||
const initialValue = {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { addEdge } from "reactflow";
|
||||
import { Edge, Node, ReactFlowJsonObject, addEdge } from "reactflow";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import { skipNodeUpdate } from "../constants/constants";
|
||||
import {
|
||||
|
|
@ -19,8 +19,9 @@ import {
|
|||
uploadFlowsToDatabase,
|
||||
} from "../controllers/API";
|
||||
import { APIClassType, APITemplateType } from "../types/api";
|
||||
import { FlowType, NodeType } from "../types/flow";
|
||||
import { TabsContextType, TabsState } from "../types/tabs";
|
||||
import { tweakType } from "../types/components";
|
||||
import { FlowType, NodeDataType, NodeType } from "../types/flow";
|
||||
import { TabsContextType, TabsState, errorsVarType } from "../types/tabs";
|
||||
import {
|
||||
addVersionToDuplicates,
|
||||
updateIds,
|
||||
|
|
@ -74,9 +75,12 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
||||
const [id, setId] = useState(uid());
|
||||
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 [getTweak, setTweak] = useState([]);
|
||||
const [getTweak, setTweak] = useState<tweakType>([]);
|
||||
|
||||
const newNodeId = useRef(uid());
|
||||
function incrementNodeId() {
|
||||
|
|
@ -131,8 +135,8 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
//get tabs from db
|
||||
return readFlowsFromDatabase();
|
||||
}
|
||||
function processDBData(DbData) {
|
||||
DbData.forEach((flow) => {
|
||||
function processDBData(DbData: FlowType[]) {
|
||||
DbData.forEach((flow: FlowType) => {
|
||||
try {
|
||||
if (!flow.data) {
|
||||
return;
|
||||
|
|
@ -145,7 +149,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
});
|
||||
}
|
||||
|
||||
function processFlowEdges(flow) {
|
||||
function processFlowEdges(flow: FlowType) {
|
||||
if (!flow.data || !flow.data.edges) return;
|
||||
flow.data.edges.forEach((edge) => {
|
||||
edge.className = "";
|
||||
|
|
@ -154,14 +158,14 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
flow.data.nodes.forEach((node: NodeType) => {
|
||||
if (skipNodeUpdate.includes(node.data.type)) return;
|
||||
|
|
@ -182,7 +186,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
}
|
||||
|
||||
function updateNodeBaseClasses(node: NodeType, template: APIClassType) {
|
||||
node.data.node.base_classes = template["base_classes"];
|
||||
node.data.node!.base_classes = template["base_classes"];
|
||||
}
|
||||
|
||||
function updateNodeEdges(
|
||||
|
|
@ -190,10 +194,10 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
node: NodeType,
|
||||
template: APIClassType
|
||||
) {
|
||||
flow.data.edges.forEach((edge) => {
|
||||
flow.data!.edges.forEach((edge) => {
|
||||
if (edge.source === node.id) {
|
||||
edge.sourceHandle = edge.sourceHandle
|
||||
.split("|")
|
||||
?.split("|")
|
||||
.slice(0, 2)
|
||||
.concat(template["base_classes"])
|
||||
.join("|");
|
||||
|
|
@ -202,17 +206,17 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
}
|
||||
|
||||
function updateNodeDescription(node: NodeType, template: APIClassType) {
|
||||
node.data.node.description = template["description"];
|
||||
node.data.node!.description = template["description"];
|
||||
}
|
||||
|
||||
function updateNodeTemplate(node: NodeType, template: APIClassType) {
|
||||
node.data.node.template = updateTemplate(
|
||||
node.data.node!.template = updateTemplate(
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -243,7 +247,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
link.download = `${
|
||||
flowName && flowName != ""
|
||||
? flowName
|
||||
: flows.find((f) => f.id === tabId).name
|
||||
: flows.find((f) => f.id === tabId)!.name
|
||||
}.json`;
|
||||
|
||||
// simulate a click on the link element to trigger the download
|
||||
|
|
@ -295,10 +299,10 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
input.onchange = (e: Event) => {
|
||||
// check if the file type is application/json
|
||||
if (
|
||||
(e.target as HTMLInputElement).files[0].type === "application/json"
|
||||
(e.target as HTMLInputElement).files![0].type === "application/json"
|
||||
) {
|
||||
// get the file from the file input
|
||||
const currentfile = (e.target as HTMLInputElement).files[0];
|
||||
const currentfile = (e.target as HTMLInputElement).files![0];
|
||||
// read the file as text
|
||||
currentfile.text().then((text) => {
|
||||
// parse the text into a JSON object
|
||||
|
|
@ -321,10 +325,10 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
input.onchange = (event: Event) => {
|
||||
// check if the file type is application/json
|
||||
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
|
||||
const file = (event.target as HTMLInputElement).files[0];
|
||||
const file = (event.target as HTMLInputElement).files![0];
|
||||
// read the file as text
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
|
@ -355,15 +359,15 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
*/
|
||||
|
||||
function paste(
|
||||
selectionInstance,
|
||||
selectionInstance: { nodes: Node[]; edges: Edge[] },
|
||||
position: { x: number; y: number; paneX?: number; paneY?: number }
|
||||
) {
|
||||
let minimumX = Infinity;
|
||||
let minimumY = Infinity;
|
||||
let idsMap = {};
|
||||
let nodes = reactFlowInstance.getNodes();
|
||||
let edges = reactFlowInstance.getEdges();
|
||||
selectionInstance.nodes.forEach((node) => {
|
||||
let nodes: Node<NodeDataType>[] = reactFlowInstance!.getNodes();
|
||||
let edges = reactFlowInstance!.getEdges();
|
||||
selectionInstance.nodes.forEach((node: Node) => {
|
||||
if (node.position.y < minimumY) {
|
||||
minimumY = node.position.y;
|
||||
}
|
||||
|
|
@ -373,8 +377,8 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
});
|
||||
|
||||
const insidePosition = position.paneX
|
||||
? { x: position.paneX + position.x, y: position.paneY + position.y }
|
||||
: reactFlowInstance.project({ x: position.x, y: position.y });
|
||||
? { x: position.paneX + position.x, y: position.paneY! + position.y }
|
||||
: reactFlowInstance!.project({ x: position.x, y: position.y });
|
||||
|
||||
selectionInstance.nodes.forEach((node: NodeType) => {
|
||||
// Generate a unique node ID
|
||||
|
|
@ -386,8 +390,8 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
id: newId,
|
||||
type: "genericNode",
|
||||
position: {
|
||||
x: insidePosition.x + node.position.x - minimumX,
|
||||
y: insidePosition.y + node.position.y - minimumY,
|
||||
x: insidePosition.x + node.position!.x - minimumX,
|
||||
y: insidePosition.y + node.position!.y - minimumY,
|
||||
},
|
||||
data: {
|
||||
..._.cloneDeep(node.data),
|
||||
|
|
@ -400,19 +404,19 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
.map((node) => ({ ...node, selected: false }))
|
||||
.concat({ ...newNode, selected: false });
|
||||
});
|
||||
reactFlowInstance.setNodes(nodes);
|
||||
reactFlowInstance!.setNodes(nodes);
|
||||
|
||||
selectionInstance.edges.forEach((edge) => {
|
||||
let source = idsMap[edge.source];
|
||||
let target = idsMap[edge.target];
|
||||
let sourceHandleSplitted = edge.sourceHandle.split("|");
|
||||
let sourceHandleSplitted = edge.sourceHandle!.split("|");
|
||||
let sourceHandle =
|
||||
sourceHandleSplitted[0] +
|
||||
"|" +
|
||||
source +
|
||||
"|" +
|
||||
sourceHandleSplitted.slice(2).join("|");
|
||||
let targetHandleSplitted = edge.targetHandle.split("|");
|
||||
let targetHandleSplitted = edge.targetHandle!.split("|");
|
||||
let targetHandle =
|
||||
targetHandleSplitted.slice(0, -1).join("|") + "|" + target;
|
||||
let id =
|
||||
|
|
@ -440,21 +444,21 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
edges.map((edge) => ({ ...edge, selected: false }))
|
||||
);
|
||||
});
|
||||
reactFlowInstance.setEdges(edges);
|
||||
reactFlowInstance!.setEdges(edges);
|
||||
}
|
||||
|
||||
const addFlow = async (
|
||||
flow?: FlowType,
|
||||
newProject?: Boolean
|
||||
): Promise<String> => {
|
||||
): Promise<String | undefined> => {
|
||||
if (newProject) {
|
||||
let flowData = extractDataFromFlow(flow);
|
||||
let flowData = extractDataFromFlow(flow!);
|
||||
if (flowData.description == "") {
|
||||
flowData.description = getRandomDescription();
|
||||
}
|
||||
|
||||
// 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);
|
||||
processFlowNodes(newFlow);
|
||||
|
||||
|
|
@ -479,13 +483,13 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
}
|
||||
} else {
|
||||
paste(
|
||||
{ nodes: flow.data.nodes, edges: flow.data.edges },
|
||||
{ nodes: flow!.data!.nodes, edges: flow!.data!.edges },
|
||||
{ x: 10, y: 10 }
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const extractDataFromFlow = (flow) => {
|
||||
const extractDataFromFlow = (flow: FlowType) => {
|
||||
let data = flow?.data ? flow.data : null;
|
||||
const description = flow?.description ? flow.description : "";
|
||||
|
||||
|
|
@ -498,17 +502,17 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
return { data, description };
|
||||
};
|
||||
|
||||
const updateEdges = (edges) => {
|
||||
const updateEdges = (edges: Edge[]) => {
|
||||
edges.forEach((edge) => {
|
||||
edge.className =
|
||||
(edge.targetHandle.split("|")[0] === "Text"
|
||||
(edge.targetHandle!.split("|")[0] === "Text"
|
||||
? "stroke-gray-800 "
|
||||
: "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) => {
|
||||
if (skipNodeUpdate.includes(node.data.type)) return;
|
||||
const template = templates[node.data.type];
|
||||
|
|
@ -520,8 +524,8 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
node.data.node.base_classes = template["base_classes"];
|
||||
edges.forEach((edge) => {
|
||||
if (edge.source === node.id) {
|
||||
edge.sourceHandle = edge.sourceHandle
|
||||
.split("|")
|
||||
edge.sourceHandle = edge
|
||||
.sourceHandle!.split("|")
|
||||
.slice(0, 2)
|
||||
.concat(template["base_classes"])
|
||||
.join("|");
|
||||
|
|
@ -536,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,
|
||||
name: flow?.name ?? getRandomName(),
|
||||
data: flowData.data,
|
||||
id: "",
|
||||
});
|
||||
|
||||
const addFlowToLocalState = (newFlow) => {
|
||||
const addFlowToLocalState = (newFlow: FlowType) => {
|
||||
setFlows((prevState) => {
|
||||
return [...prevState, newFlow];
|
||||
});
|
||||
|
|
@ -594,7 +601,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
|||
});
|
||||
}
|
||||
} catch (err) {
|
||||
setErrorData(err);
|
||||
setErrorData(err as errorsVarType);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,5 @@
|
|||
import {
|
||||
createContext,
|
||||
ReactNode,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Node } from "reactflow";
|
||||
import { createContext, ReactNode, useEffect, useState } from "react";
|
||||
import { Node, ReactFlowInstance } from "reactflow";
|
||||
import { getAll } from "../controllers/API";
|
||||
import { APIKindType } from "../types/api";
|
||||
import { typesContextType } from "../types/typesContext";
|
||||
|
|
@ -15,7 +9,7 @@ import { alertContext } from "./alertContext";
|
|||
|
||||
const initialValue: typesContextType = {
|
||||
reactFlowInstance: null,
|
||||
setReactFlowInstance: () => {},
|
||||
setReactFlowInstance: (newState: ReactFlowInstance) => {},
|
||||
deleteNode: () => {},
|
||||
types: {},
|
||||
setTypes: () => {},
|
||||
|
|
@ -29,14 +23,15 @@ export const typesContext = createContext<typesContextType>(initialValue);
|
|||
|
||||
export function TypesProvider({ children }: { children: ReactNode }) {
|
||||
const [types, setTypes] = useState({});
|
||||
const [reactFlowInstance, setReactFlowInstance] = useState(null);
|
||||
const [reactFlowInstance, setReactFlowInstance] =
|
||||
useState<ReactFlowInstance | null>(null);
|
||||
const [templates, setTemplates] = useState({});
|
||||
const [data, setData] = useState({});
|
||||
const { setLoading } = useContext(alertContext);
|
||||
|
||||
useEffect(() => {
|
||||
let delay = 1000; // Start delay of 1 second
|
||||
let intervalId = null;
|
||||
let intervalId: NodeJS.Timer;
|
||||
let retryCount = 0; // Count of retry attempts
|
||||
const maxRetryCount = 5; // Max retry attempts
|
||||
|
||||
|
|
@ -78,7 +73,7 @@ export function TypesProvider({ children }: { children: ReactNode }) {
|
|||
);
|
||||
}
|
||||
// Clear the interval if successful.
|
||||
clearInterval(intervalId);
|
||||
clearInterval(intervalId!);
|
||||
} catch (error) {
|
||||
console.error("An error has occurred while fetching types.");
|
||||
}
|
||||
|
|
@ -86,21 +81,20 @@ export function TypesProvider({ children }: { children: ReactNode }) {
|
|||
|
||||
// Start the initial interval.
|
||||
intervalId = setInterval(getTypes, delay);
|
||||
|
||||
return () => {
|
||||
// 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.
|
||||
isMounted = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
function deleteNode(idx: string) {
|
||||
reactFlowInstance.setNodes(
|
||||
reactFlowInstance.getNodes().filter((node: Node) => node.id !== idx)
|
||||
reactFlowInstance!.setNodes(
|
||||
reactFlowInstance!.getNodes().filter((node: Node) => node.id !== idx)
|
||||
);
|
||||
reactFlowInstance.setEdges(
|
||||
reactFlowInstance
|
||||
reactFlowInstance!.setEdges(
|
||||
reactFlowInstance!
|
||||
.getEdges()
|
||||
.filter((edge) => edge.source !== idx && edge.target !== idx)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,26 +6,15 @@ import {
|
|||
useEffect,
|
||||
useState,
|
||||
} 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 { 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 = {
|
||||
undo: () => {},
|
||||
redo: () => {},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import axios, { AxiosError, AxiosInstance } from "axios";
|
||||
import { useContext, useEffect, useRef } from "react";
|
||||
import { URL_EXCLUDED_FROM_ERROR_RETRIES } from "../../constants/constants";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
|
||||
// Create a new Axios instance
|
||||
|
|
@ -8,7 +7,7 @@ const api: AxiosInstance = axios.create({
|
|||
baseURL: "",
|
||||
});
|
||||
|
||||
function ApiInterceptor() {
|
||||
function ApiInterceptor(): null {
|
||||
const retryCounts = useRef([]);
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
|
||||
|
|
@ -16,32 +15,31 @@ function ApiInterceptor() {
|
|||
const interceptor = api.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error: AxiosError) => {
|
||||
if (URL_EXCLUDED_FROM_ERROR_RETRIES.includes(error.config?.url)) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
let retryCount = 0;
|
||||
|
||||
while (retryCount < 4) {
|
||||
await sleep(5000); // Sleep for 5 seconds
|
||||
retryCount++;
|
||||
try {
|
||||
const response = await axios.request(error.config);
|
||||
return response;
|
||||
} catch (error) {
|
||||
if (retryCount === 3) {
|
||||
setErrorData({
|
||||
title: "There was an error on web connection, please: ",
|
||||
list: [
|
||||
"Refresh the page",
|
||||
"Use a new flow tab",
|
||||
"Check if the backend is up",
|
||||
"Endpoint: " + error.config?.url,
|
||||
],
|
||||
});
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
// if (URL_EXCLUDED_FROM_ERROR_RETRIES.includes(error.config?.url)) {
|
||||
// return Promise.reject(error);
|
||||
// }
|
||||
// let retryCount = 0;
|
||||
// while (retryCount < 4) {
|
||||
// await sleep(5000); // Sleep for 5 seconds
|
||||
// retryCount++;
|
||||
// try {
|
||||
// const response = await axios.request(error.config);
|
||||
// return response;
|
||||
// } catch (error) {
|
||||
// if (retryCount === 3) {
|
||||
// setErrorData({
|
||||
// title: "There was an error on web connection, please: ",
|
||||
// list: [
|
||||
// "Refresh the page",
|
||||
// "Use a new flow tab",
|
||||
// "Check if the backend is up",
|
||||
// "Endpoint: " + error.config?.url,
|
||||
// ],
|
||||
// });
|
||||
// return Promise.reject(error);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -55,7 +53,7 @@ function ApiInterceptor() {
|
|||
}
|
||||
|
||||
// Function to sleep for a given duration in milliseconds
|
||||
function sleep(ms) {
|
||||
function sleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export async function getAll(): Promise<AxiosResponse<APIObjectType>> {
|
|||
|
||||
const GITHUB_API_URL = "https://api.github.com";
|
||||
|
||||
export async function getRepoStars(owner, repo) {
|
||||
export async function getRepoStars(owner: string, repo: string) {
|
||||
try {
|
||||
const response = await api.get(`${GITHUB_API_URL}/repos/${owner}/${repo}`);
|
||||
return response.data.stargazers_count;
|
||||
|
|
@ -100,7 +100,7 @@ export async function getExamples(): Promise<FlowType[]> {
|
|||
export async function saveFlowToDatabase(newFlow: {
|
||||
name: string;
|
||||
id: string;
|
||||
data: ReactFlowJsonObject;
|
||||
data: ReactFlowJsonObject | null;
|
||||
description: string;
|
||||
style?: FlowStyleType;
|
||||
}): Promise<FlowType> {
|
||||
|
|
@ -179,7 +179,7 @@ export async function downloadFlowsFromDatabase() {
|
|||
}
|
||||
}
|
||||
|
||||
export async function uploadFlowsToDatabase(flows) {
|
||||
export async function uploadFlowsToDatabase(flows: FormData) {
|
||||
try {
|
||||
const response = await api.post(`/api/v1/flows/upload/`, flows);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import React, { forwardRef } from "react";
|
||||
import { ReactComponent as PowerPointSVG } from "./PowerPoint.svg";
|
||||
import SvgPowerPoint from "./PowerPoint";
|
||||
|
||||
export const PowerPointIcon = forwardRef<
|
||||
SVGSVGElement,
|
||||
React.PropsWithChildren<{}>
|
||||
>((props, ref) => {
|
||||
return <PowerPointSVG ref={ref} {...props} />;
|
||||
return <SvgPowerPoint ref={ref} {...props} />;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,13 +15,16 @@ import CodeTabsComponent from "../../components/codeTabsComponent";
|
|||
import IconComponent from "../../components/genericIconComponent";
|
||||
import { EXPORT_CODE_DIALOG } from "../../constants/constants";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import { FlowType } from "../../types/flow/index";
|
||||
import { TemplateVariableType } from "../../types/api";
|
||||
import { tweakType, uniqueTweakType } from "../../types/components";
|
||||
import { FlowType, NodeType } from "../../types/flow/index";
|
||||
import { buildTweaks } from "../../utils/reactflowUtils";
|
||||
import {
|
||||
getCurlCode,
|
||||
getPythonApiCode,
|
||||
getPythonCode,
|
||||
getWidgetCode,
|
||||
tabsArray,
|
||||
} from "../../utils/utils";
|
||||
import BaseModal from "../baseModal";
|
||||
|
||||
|
|
@ -40,47 +43,22 @@ const ApiModal = forwardRef(
|
|||
) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState("0");
|
||||
const tweak = useRef([]);
|
||||
const tweaksList = useRef([]);
|
||||
const tweak = useRef<tweakType>([]);
|
||||
const tweaksList = useRef<string[]>([]);
|
||||
const { setTweak, getTweak, tabsState } = useContext(TabsContext);
|
||||
const pythonApiCode = getPythonApiCode(flow, tweak.current, tabsState);
|
||||
const curl_code = getCurlCode(flow, tweak.current, tabsState);
|
||||
const pythonCode = getPythonCode(flow, tweak.current, tabsState);
|
||||
const widgetCode = getWidgetCode(flow, tabsState);
|
||||
const tweaksCode = buildTweaks(flow);
|
||||
const [tabs, setTabs] = useState([
|
||||
{
|
||||
name: "cURL",
|
||||
mode: "bash",
|
||||
image: "https://curl.se/logo/curl-symbol-transparent.png",
|
||||
language: "sh",
|
||||
code: curl_code,
|
||||
},
|
||||
{
|
||||
name: "Python API",
|
||||
mode: "python",
|
||||
image:
|
||||
"https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
|
||||
language: "py",
|
||||
code: pythonApiCode,
|
||||
},
|
||||
{
|
||||
name: "Python Code",
|
||||
mode: "python",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
language: "py",
|
||||
code: pythonCode,
|
||||
},
|
||||
{
|
||||
name: "Chat Widget HTML",
|
||||
description:
|
||||
"Insert this code anywhere in your <body> tag. To use with react and other libs, check our <a class='link-color' href='https://docs.langflow.org/guidelines/widget'>documentation</a>.",
|
||||
mode: "html",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
language: "py",
|
||||
code: widgetCode,
|
||||
},
|
||||
]);
|
||||
const codesArray = [
|
||||
curl_code,
|
||||
pythonApiCode,
|
||||
pythonCode,
|
||||
widgetCode,
|
||||
pythonCode,
|
||||
];
|
||||
const [tabs, setTabs] = useState(tabsArray(codesArray, 0));
|
||||
|
||||
function startState() {
|
||||
tweak.current = [];
|
||||
|
|
@ -89,7 +67,7 @@ const ApiModal = forwardRef(
|
|||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (flow["data"]["nodes"].length == 0) {
|
||||
if (flow["data"]!["nodes"].length == 0) {
|
||||
startState();
|
||||
} else {
|
||||
tweak.current = [];
|
||||
|
|
@ -101,87 +79,16 @@ const ApiModal = forwardRef(
|
|||
|
||||
if (Object.keys(tweaksCode).length > 0) {
|
||||
setActiveTab("0");
|
||||
setTabs([
|
||||
{
|
||||
name: "cURL",
|
||||
mode: "bash",
|
||||
image: "https://curl.se/logo/curl-symbol-transparent.png",
|
||||
language: "sh",
|
||||
code: curl_code,
|
||||
},
|
||||
{
|
||||
name: "Python API",
|
||||
mode: "python",
|
||||
image:
|
||||
"https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
|
||||
language: "py",
|
||||
code: pythonApiCode,
|
||||
},
|
||||
{
|
||||
name: "Python Code",
|
||||
mode: "python",
|
||||
language: "py",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
code: pythonCode,
|
||||
},
|
||||
{
|
||||
name: "Chat Widget HTML",
|
||||
description:
|
||||
"Insert this code anywhere in your <body> tag. To use with react and other libs, check our <a class='link-color' href='https://docs.langflow.org/guidelines/widget'>documentation</a>.",
|
||||
mode: "html",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
language: "py",
|
||||
code: widgetCode,
|
||||
},
|
||||
{
|
||||
name: "Tweaks",
|
||||
mode: "python",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
language: "py",
|
||||
code: pythonCode,
|
||||
},
|
||||
]);
|
||||
setTabs(tabsArray(codesArray, 1));
|
||||
} else {
|
||||
setTabs([
|
||||
{
|
||||
name: "cURL",
|
||||
mode: "bash",
|
||||
image: "https://curl.se/logo/curl-symbol-transparent.png",
|
||||
language: "sh",
|
||||
code: curl_code,
|
||||
},
|
||||
{
|
||||
name: "Python API",
|
||||
mode: "python",
|
||||
image:
|
||||
"https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
|
||||
language: "py",
|
||||
code: pythonApiCode,
|
||||
},
|
||||
{
|
||||
name: "Python Code",
|
||||
mode: "python",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
language: "py",
|
||||
code: pythonCode,
|
||||
},
|
||||
{
|
||||
name: "Chat Widget HTML",
|
||||
description:
|
||||
"Insert this code anywhere in your <body> tag. To use with react and other libs, check our <a class='link-color' href='https://docs.langflow.org/guidelines/widget'>documentation</a>.",
|
||||
mode: "html",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
language: "py",
|
||||
code: widgetCode,
|
||||
},
|
||||
]);
|
||||
setTabs(tabsArray(codesArray, 1));
|
||||
}
|
||||
}, [flow["data"]["nodes"], open]);
|
||||
}, [flow["data"]!["nodes"], open]);
|
||||
|
||||
function filterNodes() {
|
||||
let arrNodesWithValues = [];
|
||||
let arrNodesWithValues: string[] = [];
|
||||
|
||||
flow["data"]["nodes"].forEach((node) => {
|
||||
flow["data"]!["nodes"].forEach((node) => {
|
||||
Object.keys(node["data"]["node"]["template"])
|
||||
.filter(
|
||||
(templateField) =>
|
||||
|
|
@ -204,11 +111,15 @@ const ApiModal = forwardRef(
|
|||
return self.indexOf(value) === index;
|
||||
});
|
||||
}
|
||||
function buildTweakObject(tw, changes, template) {
|
||||
if (template.type === "float") {
|
||||
function buildTweakObject(
|
||||
tw: string,
|
||||
changes: string | string[] | boolean | number,
|
||||
template: TemplateVariableType
|
||||
) {
|
||||
if (typeof changes === "string" && template.type === "float") {
|
||||
changes = parseFloat(changes);
|
||||
}
|
||||
if (template.type === "int") {
|
||||
if (typeof changes === "string" && template.type === "int") {
|
||||
changes = parseInt(changes);
|
||||
}
|
||||
if (template.list === true && Array.isArray(changes)) {
|
||||
|
|
@ -220,7 +131,7 @@ const ApiModal = forwardRef(
|
|||
);
|
||||
|
||||
if (existingTweak) {
|
||||
existingTweak[tw][template["name"]] = changes;
|
||||
existingTweak[tw][template["name"]] = changes as string;
|
||||
|
||||
if (existingTweak[tw][template["name"]] == template.value) {
|
||||
tweak.current.forEach((element) => {
|
||||
|
|
@ -237,7 +148,7 @@ const ApiModal = forwardRef(
|
|||
[tw]: {
|
||||
[template["name"]]: changes,
|
||||
},
|
||||
};
|
||||
} as uniqueTweakType;
|
||||
tweak.current.push(newTweak);
|
||||
}
|
||||
|
||||
|
|
@ -246,15 +157,15 @@ const ApiModal = forwardRef(
|
|||
const pythonCode = getPythonCode(flow, tweak.current, tabsState);
|
||||
const widgetCode = getWidgetCode(flow, tabsState);
|
||||
|
||||
tabs[0].code = curl_code;
|
||||
tabs[1].code = pythonApiCode;
|
||||
tabs[2].code = pythonCode;
|
||||
tabs[3].code = widgetCode;
|
||||
tabs![0].code = curl_code;
|
||||
tabs![1].code = pythonApiCode;
|
||||
tabs![2].code = pythonCode;
|
||||
tabs![3].code = widgetCode;
|
||||
|
||||
setTweak(tweak.current);
|
||||
}
|
||||
|
||||
function buildContent(value) {
|
||||
function buildContent(value: string) {
|
||||
const htmlContent = (
|
||||
<div className="w-[200px]">
|
||||
<span>{value != null && value != "" ? value : "None"}</span>
|
||||
|
|
@ -263,7 +174,11 @@ const ApiModal = forwardRef(
|
|||
return htmlContent;
|
||||
}
|
||||
|
||||
function getValue(value, node, template) {
|
||||
function getValue(
|
||||
value: string,
|
||||
node: NodeType,
|
||||
template: TemplateVariableType
|
||||
) {
|
||||
let returnValue = value ?? "";
|
||||
|
||||
if (getTweak.length > 0) {
|
||||
|
|
@ -299,7 +214,7 @@ const ApiModal = forwardRef(
|
|||
<BaseModal.Content>
|
||||
<CodeTabsComponent
|
||||
flow={flow}
|
||||
tabs={tabs}
|
||||
tabs={tabs!}
|
||||
activeTab={activeTab}
|
||||
setActiveTab={setActiveTab}
|
||||
tweaks={{
|
||||
|
|
|
|||
66
src/frontend/src/modals/ConfirmationModal/index.tsx
Normal file
66
src/frontend/src/modals/ConfirmationModal/index.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { useState } from "react";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { ConfirmationModalType } from "../../types/components";
|
||||
import { nodeIconsLucide } from "../../utils/styleUtils";
|
||||
import BaseModal from "../baseModal";
|
||||
|
||||
export default function ConfirmationModal({
|
||||
title,
|
||||
titleHeader,
|
||||
modalContent,
|
||||
modalContentTitle,
|
||||
cancelText,
|
||||
confirmationText,
|
||||
children,
|
||||
icon,
|
||||
data,
|
||||
index,
|
||||
onConfirm,
|
||||
}: ConfirmationModalType) {
|
||||
const Icon: any = nodeIconsLucide[icon];
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<BaseModal size="x-small" open={open} setOpen={setOpen}>
|
||||
<BaseModal.Trigger>{children}</BaseModal.Trigger>
|
||||
<BaseModal.Header description={titleHeader}>
|
||||
<span className="pr-2">{title}</span>
|
||||
<Icon
|
||||
name="icon"
|
||||
className="h-6 w-6 pl-1 text-foreground"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</BaseModal.Header>
|
||||
<BaseModal.Content>
|
||||
{modalContentTitle != "" && (
|
||||
<>
|
||||
<strong>{modalContentTitle}</strong>
|
||||
<br></br>
|
||||
</>
|
||||
)}
|
||||
<span>{modalContent}</span>
|
||||
</BaseModal.Content>
|
||||
|
||||
<BaseModal.Footer>
|
||||
<Button
|
||||
className="ml-3"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
onConfirm(index, data);
|
||||
}}
|
||||
>
|
||||
{confirmationText}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
{cancelText}
|
||||
</Button>
|
||||
</BaseModal.Footer>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
|
|
@ -25,6 +25,7 @@ import { limitScrollFieldsModal } from "../../constants/constants";
|
|||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import { typesContext } from "../../contexts/typesContext";
|
||||
import { NodeDataType } from "../../types/flow";
|
||||
import { TabsState } from "../../types/tabs";
|
||||
import { classNames } from "../../utils/utils";
|
||||
import BaseModal from "../baseModal";
|
||||
|
||||
|
|
@ -53,19 +54,22 @@ const EditNodeModal = forwardRef(
|
|||
?.getEdges()
|
||||
.some((edge) => edge.targetHandle === data.id) ?? false;
|
||||
|
||||
function changeAdvanced(templateParam) {
|
||||
function changeAdvanced(templateParam: string): void {
|
||||
setMyData((old) => {
|
||||
let newData = cloneDeep(old);
|
||||
newData.node.template[templateParam].advanced =
|
||||
!newData.node.template[templateParam].advanced;
|
||||
newData.node!.template[templateParam].advanced =
|
||||
!newData.node!.template[templateParam].advanced;
|
||||
return newData;
|
||||
});
|
||||
}
|
||||
|
||||
const handleOnNewValue = (newValue: any, name) => {
|
||||
const handleOnNewValue = (
|
||||
newValue: string | string[] | boolean,
|
||||
name: string
|
||||
) => {
|
||||
setMyData((old) => {
|
||||
let newData = cloneDeep(old);
|
||||
newData.node.template[name].value = newValue;
|
||||
newData.node!.template[name].value = newValue;
|
||||
return newData;
|
||||
});
|
||||
};
|
||||
|
|
@ -77,7 +81,7 @@ const EditNodeModal = forwardRef(
|
|||
return (
|
||||
<BaseModal size="large-h-full" open={modalOpen} setOpen={setModalOpen}>
|
||||
<BaseModal.Trigger>{children}</BaseModal.Trigger>
|
||||
<BaseModal.Header description={myData.node?.description}>
|
||||
<BaseModal.Header description={myData.node?.description!}>
|
||||
<span className="pr-2">{myData.type}</span>
|
||||
<Badge variant="secondary">ID: {myData.id}</Badge>
|
||||
</BaseModal.Header>
|
||||
|
|
@ -112,11 +116,11 @@ const EditNodeModal = forwardRef(
|
|||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody className="p-0">
|
||||
{Object.keys(myData.node.template)
|
||||
{Object.keys(myData.node!.template)
|
||||
.filter(
|
||||
(templateParam) =>
|
||||
templateParam.charAt(0) !== "_" &&
|
||||
myData.node.template[templateParam].show &&
|
||||
myData.node?.template[templateParam].show &&
|
||||
(myData.node.template[templateParam].type ===
|
||||
"str" ||
|
||||
myData.node.template[templateParam].type ===
|
||||
|
|
@ -135,13 +139,13 @@ const EditNodeModal = forwardRef(
|
|||
.map((templateParam, index) => (
|
||||
<TableRow key={index} className="h-10">
|
||||
<TableCell className="truncate p-0 text-center text-sm text-foreground sm:px-3">
|
||||
{myData.node.template[templateParam].name
|
||||
{myData.node?.template[templateParam].name
|
||||
? myData.node.template[templateParam].name
|
||||
: myData.node.template[templateParam]
|
||||
: myData.node?.template[templateParam]
|
||||
.display_name}
|
||||
</TableCell>
|
||||
<TableCell className="w-[300px] p-0 text-center text-xs text-foreground ">
|
||||
{myData.node.template[templateParam].type ===
|
||||
{myData.node?.template[templateParam].type ===
|
||||
"str" &&
|
||||
!myData.node.template[templateParam].options ? (
|
||||
<div className="mx-auto">
|
||||
|
|
@ -171,7 +175,7 @@ const EditNodeModal = forwardRef(
|
|||
myData.node.template[templateParam]
|
||||
.value ?? ""
|
||||
}
|
||||
onChange={(value: string) => {
|
||||
onChange={(value: string | string[]) => {
|
||||
handleOnNewValue(value, templateParam);
|
||||
}}
|
||||
/>
|
||||
|
|
@ -193,7 +197,7 @@ const EditNodeModal = forwardRef(
|
|||
/>
|
||||
)}
|
||||
</div>
|
||||
) : myData.node.template[templateParam].type ===
|
||||
) : myData.node?.template[templateParam].type ===
|
||||
"bool" ? (
|
||||
<div className="ml-auto">
|
||||
{" "}
|
||||
|
|
@ -211,7 +215,7 @@ const EditNodeModal = forwardRef(
|
|||
size="small"
|
||||
/>
|
||||
</div>
|
||||
) : myData.node.template[templateParam].type ===
|
||||
) : myData.node?.template[templateParam].type ===
|
||||
"float" ? (
|
||||
<div className="mx-auto">
|
||||
<FloatComponent
|
||||
|
|
@ -226,7 +230,7 @@ const EditNodeModal = forwardRef(
|
|||
}}
|
||||
/>
|
||||
</div>
|
||||
) : myData.node.template[templateParam].type ===
|
||||
) : myData.node?.template[templateParam].type ===
|
||||
"str" &&
|
||||
myData.node.template[templateParam].options ? (
|
||||
<div className="mx-auto">
|
||||
|
|
@ -246,7 +250,7 @@ const EditNodeModal = forwardRef(
|
|||
}
|
||||
></Dropdown>
|
||||
</div>
|
||||
) : myData.node.template[templateParam].type ===
|
||||
) : myData.node?.template[templateParam].type ===
|
||||
"int" ? (
|
||||
<div className="mx-auto">
|
||||
<IntComponent
|
||||
|
|
@ -261,7 +265,7 @@ const EditNodeModal = forwardRef(
|
|||
}}
|
||||
/>
|
||||
</div>
|
||||
) : myData.node.template[templateParam].type ===
|
||||
) : myData.node?.template[templateParam].type ===
|
||||
"file" ? (
|
||||
<div className="mx-auto">
|
||||
<InputFileComponent
|
||||
|
|
@ -271,7 +275,7 @@ const EditNodeModal = forwardRef(
|
|||
myData.node.template[templateParam]
|
||||
.value ?? ""
|
||||
}
|
||||
onChange={(value: string) => {
|
||||
onChange={(value: string | string[]) => {
|
||||
handleOnNewValue(value, templateParam);
|
||||
}}
|
||||
fileTypes={
|
||||
|
|
@ -283,13 +287,13 @@ const EditNodeModal = forwardRef(
|
|||
.suffixes
|
||||
}
|
||||
onFileChange={(filePath: string) => {
|
||||
data.node.template[
|
||||
data.node!.template[
|
||||
templateParam
|
||||
].file_path = filePath;
|
||||
}}
|
||||
></InputFileComponent>
|
||||
</div>
|
||||
) : myData.node.template[templateParam].type ===
|
||||
) : myData.node?.template[templateParam].type ===
|
||||
"prompt" ? (
|
||||
<div className="mx-auto">
|
||||
<PromptAreaComponent
|
||||
|
|
@ -304,17 +308,17 @@ const EditNodeModal = forwardRef(
|
|||
myData.node.template[templateParam]
|
||||
.value ?? ""
|
||||
}
|
||||
onChange={(value: string) => {
|
||||
onChange={(value: string | string[]) => {
|
||||
handleOnNewValue(value, templateParam);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : myData.node.template[templateParam].type ===
|
||||
) : myData.node?.template[templateParam].type ===
|
||||
"code" ? (
|
||||
<div className="mx-auto">
|
||||
<CodeAreaComponent
|
||||
dynamic={
|
||||
data.node.template[templateParam]
|
||||
data.node!.template[templateParam]
|
||||
.dynamic ?? false
|
||||
}
|
||||
setNodeClass={(nodeClass) => {
|
||||
|
|
@ -327,12 +331,12 @@ const EditNodeModal = forwardRef(
|
|||
myData.node.template[templateParam]
|
||||
.value ?? ""
|
||||
}
|
||||
onChange={(value: string) => {
|
||||
onChange={(value: string | string[]) => {
|
||||
handleOnNewValue(value, templateParam);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : myData.node.template[templateParam].type ===
|
||||
) : myData.node?.template[templateParam].type ===
|
||||
"Any" ? (
|
||||
"-"
|
||||
) : (
|
||||
|
|
@ -343,7 +347,7 @@ const EditNodeModal = forwardRef(
|
|||
<div className="items-center text-center">
|
||||
<ToggleShadComponent
|
||||
enabled={
|
||||
!myData.node.template[templateParam]
|
||||
!myData.node?.template[templateParam]
|
||||
.advanced
|
||||
}
|
||||
setEnabled={(e) =>
|
||||
|
|
@ -369,7 +373,8 @@ const EditNodeModal = forwardRef(
|
|||
className="mt-3"
|
||||
onClick={() => {
|
||||
setData(cloneDeep(myData)); //saves data with actual state of modal
|
||||
setTabsState((prev) => {
|
||||
//@ts-ignore
|
||||
setTabsState((prev: TabsState) => {
|
||||
return {
|
||||
...prev,
|
||||
[tabId]: {
|
||||
|
|
|
|||
|
|
@ -1,180 +0,0 @@
|
|||
import { useState } from "react";
|
||||
import CodeAreaComponent from "../../../../components/codeAreaComponent";
|
||||
import Dropdown from "../../../../components/dropdownComponent";
|
||||
import FloatComponent from "../../../../components/floatComponent";
|
||||
import InputComponent from "../../../../components/inputComponent";
|
||||
import InputFileComponent from "../../../../components/inputFileComponent";
|
||||
import InputListComponent from "../../../../components/inputListComponent";
|
||||
import IntComponent from "../../../../components/intComponent";
|
||||
import PromptAreaComponent from "../../../../components/promptComponent";
|
||||
import TextAreaComponent from "../../../../components/textAreaComponent";
|
||||
import ToggleComponent from "../../../../components/toggleComponent";
|
||||
import { classNames } from "../../../../utils/utils";
|
||||
|
||||
export default function ModalField({
|
||||
data,
|
||||
title,
|
||||
required,
|
||||
id,
|
||||
name,
|
||||
type,
|
||||
index,
|
||||
}) {
|
||||
const [enabled, setEnabled] = useState(
|
||||
data.node.template[name]?.value ?? false
|
||||
);
|
||||
const display =
|
||||
type === "str" ||
|
||||
type === "int" ||
|
||||
type === "prompt" ||
|
||||
type === "bool" ||
|
||||
type === "float" ||
|
||||
type === "file" ||
|
||||
type === "code";
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
"flex w-full flex-row items-center justify-between",
|
||||
display ? "" : "hidden",
|
||||
Object.keys(data.node.template).filter(
|
||||
(t) =>
|
||||
t.charAt(0) !== "_" &&
|
||||
data.node.template[t].advanced &&
|
||||
data.node.template[t].show
|
||||
).length -
|
||||
1 ===
|
||||
index
|
||||
? "pb-4"
|
||||
: ""
|
||||
)}
|
||||
>
|
||||
{display && (
|
||||
<div>
|
||||
<span className="mx-2">{title}</span>
|
||||
<span className="text-status-red">{required ? " *" : ""}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{type === "str" && !data.node.template[name].options ? (
|
||||
<div className="w-1/2">
|
||||
{data.node.template[name].list ? (
|
||||
<InputListComponent
|
||||
disabled={false}
|
||||
value={
|
||||
!data.node.template[name].value ||
|
||||
data.node.template[name].value === ""
|
||||
? [""]
|
||||
: data.node.template[name].value
|
||||
}
|
||||
onChange={(t: string[]) => {
|
||||
data.node.template[name].value = t;
|
||||
}}
|
||||
/>
|
||||
) : data.node.template[name].multiline ? (
|
||||
<TextAreaComponent
|
||||
disabled={false}
|
||||
value={data.node.template[name].value ?? ""}
|
||||
onChange={(t: string) => {
|
||||
data.node.template[name].value = t;
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<InputComponent
|
||||
disabled={false}
|
||||
password={data.node.template[name].password ?? false}
|
||||
value={data.node.template[name].value ?? ""}
|
||||
onChange={(t) => {
|
||||
data.node.template[name].value = t;
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : type === "bool" ? (
|
||||
<div className="ml-auto">
|
||||
{" "}
|
||||
<ToggleComponent
|
||||
disabled={false}
|
||||
enabled={enabled}
|
||||
setEnabled={(t) => {
|
||||
data.node.template[name].value = t;
|
||||
setEnabled(t);
|
||||
}}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
) : type === "float" ? (
|
||||
<div className="w-1/2">
|
||||
<FloatComponent
|
||||
disabled={false}
|
||||
value={data.node.template[name].value ?? ""}
|
||||
onChange={(t) => {
|
||||
data.node.template[name].value = t;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : type === "str" && data.node.template[name].options ? (
|
||||
<div className="w-1/2">
|
||||
<Dropdown
|
||||
options={data.node.template[name].options}
|
||||
onSelect={(newValue) => (data.node.template[name].value = newValue)}
|
||||
value={data.node.template[name].value ?? "Choose an option"}
|
||||
></Dropdown>
|
||||
</div>
|
||||
) : type === "int" ? (
|
||||
<div className="w-1/2">
|
||||
<IntComponent
|
||||
disabled={false}
|
||||
value={data.node.template[name].value ?? ""}
|
||||
onChange={(t) => {
|
||||
data.node.template[name].value = t;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : type === "file" ? (
|
||||
<div className="w-1/2">
|
||||
<InputFileComponent
|
||||
disabled={false}
|
||||
value={data.node.template[name].value ?? ""}
|
||||
onChange={(t: string) => {
|
||||
data.node.template[name].value = t;
|
||||
}}
|
||||
fileTypes={data.node.template[name].fileTypes}
|
||||
suffixes={data.node.template[name].suffixes}
|
||||
onFileChange={(t: string) => {
|
||||
data.node.template[name].file_path = t;
|
||||
}}
|
||||
></InputFileComponent>
|
||||
</div>
|
||||
) : type === "prompt" ? (
|
||||
<div className="w-1/2">
|
||||
<PromptAreaComponent
|
||||
field_name={name}
|
||||
disabled={false}
|
||||
value={data.node.template[name].value ?? ""}
|
||||
onChange={(t: string) => {
|
||||
data.node.template[name].value = t;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : type === "code" ? (
|
||||
<div className="w-1/2">
|
||||
<CodeAreaComponent
|
||||
dynamic={data.node.template[name].dynamic ?? false}
|
||||
setNodeClass={(nodeClass) => {
|
||||
data.node = nodeClass;
|
||||
}}
|
||||
nodeClass={data.node}
|
||||
disabled={false}
|
||||
value={data.node.template[name].value ?? ""}
|
||||
onChange={(t: string) => {
|
||||
data.node.template[name].value = t;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="hidden"></div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Fragment, useContext, useRef, useState } from "react";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import { limitScrollFieldsModal } from "../../constants/constants";
|
||||
import { typesContext } from "../../contexts/typesContext";
|
||||
import { NodeDataType } from "../../types/flow";
|
||||
import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils";
|
||||
import { classNames, toTitleCase } from "../../utils/utils";
|
||||
import ModalField from "./components/ModalField";
|
||||
|
||||
export default function NodeModal({ data }: { data: NodeDataType }) {
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const { types } = useContext(typesContext);
|
||||
const ref = useRef();
|
||||
// any to avoid type conflict
|
||||
const Icon: any = nodeIconsLucide[types[data.type]];
|
||||
return (
|
||||
<Dialog
|
||||
open={modalOpen}
|
||||
as="div"
|
||||
className="relative z-10"
|
||||
onClose={setModalOpen}
|
||||
initialFocus={ref}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="node-modal-div" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="node-modal-dialog-arrangement">
|
||||
<div className="node-modal-dialog-div">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<Dialog.Panel className="node-modal-dialog-panel">
|
||||
<div className=" node-modal-dialog-panel-div">
|
||||
<button
|
||||
type="button"
|
||||
className="node-modal-dialog-button"
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Close</span>
|
||||
<IconComponent
|
||||
name="X"
|
||||
className="h-6 w-6"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div className="node-modal-dialog-icon-div">
|
||||
<div className="node-modal-icon-arrangement">
|
||||
<Icon
|
||||
strokeWidth={1.5}
|
||||
className="node-modal-icon"
|
||||
style={{
|
||||
color: nodeColors[types[data.type]] ?? nodeColors.unknown,
|
||||
}}
|
||||
/>
|
||||
<div className="node-modal-title-div">
|
||||
<Dialog.Title as="h3" className="node-modal-title">
|
||||
{data.type}
|
||||
</Dialog.Title>
|
||||
</div>
|
||||
</div>
|
||||
<div className="node-modal-template-div">
|
||||
<div className="flex-max-width h-[445px]">
|
||||
<div
|
||||
className={classNames(
|
||||
"node-modal-template",
|
||||
Object.keys(data.node.template).filter(
|
||||
(t) =>
|
||||
t.charAt(0) !== "_" &&
|
||||
data.node.template[t].advanced &&
|
||||
data.node.template[t].show
|
||||
).length > limitScrollFieldsModal
|
||||
? "overflow-scroll overflow-x-hidden custom-scroll"
|
||||
: "overflow-hidden"
|
||||
)}
|
||||
>
|
||||
<div className="node-modal-template-column">
|
||||
{Object.keys(data.node.template)
|
||||
.filter(
|
||||
(t) =>
|
||||
t.charAt(0) !== "_" &&
|
||||
data.node.template[t].advanced &&
|
||||
data.node.template[t].show
|
||||
)
|
||||
.map((t: string, idx) => {
|
||||
return (
|
||||
<ModalField
|
||||
key={idx}
|
||||
data={data}
|
||||
title={
|
||||
data.node.template[t].display_name
|
||||
? data.node.template[t].display_name
|
||||
: data.node.template[t].name
|
||||
? toTitleCase(data.node.template[t].name)
|
||||
: toTitleCase(t)
|
||||
}
|
||||
required={data.node.template[t].required}
|
||||
id={
|
||||
data.node.template[t].type +
|
||||
"|" +
|
||||
t +
|
||||
"|" +
|
||||
data.id
|
||||
}
|
||||
name={t}
|
||||
type={data.node.template[t].type}
|
||||
index={idx}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="node-modal-button-box">
|
||||
<button
|
||||
type="button"
|
||||
className="node-modal-button"
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
>
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
241
src/frontend/src/modals/UserManagementModal/index.tsx
Normal file
241
src/frontend/src/modals/UserManagementModal/index.tsx
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
import * as Form from "@radix-ui/react-form";
|
||||
import { useEffect, useState } from "react";
|
||||
import InputComponent from "../../components/inputComponent";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { UserManagementType } from "../../types/components";
|
||||
import { nodeIconsLucide } from "../../utils/styleUtils";
|
||||
import BaseModal from "../baseModal";
|
||||
|
||||
export default function UserManagementModal({
|
||||
title,
|
||||
titleHeader,
|
||||
cancelText,
|
||||
confirmationText,
|
||||
children,
|
||||
icon,
|
||||
data,
|
||||
index,
|
||||
onConfirm,
|
||||
}: UserManagementType) {
|
||||
const Icon: any = nodeIconsLucide[icon];
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const [password, setPassword] = useState(data?.password ?? "");
|
||||
const [username, setUserName] = useState(data?.user ?? "");
|
||||
const [confirmPassword, setConfirmPassword] = useState(data?.password ?? "");
|
||||
|
||||
useEffect(() => {
|
||||
if (!data) {
|
||||
resetForm();
|
||||
}
|
||||
}, [data, open]);
|
||||
|
||||
function resetForm() {
|
||||
setPassword("");
|
||||
setUserName("");
|
||||
setConfirmPassword("");
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseModal size="medium-h-full" open={open} setOpen={setOpen}>
|
||||
<BaseModal.Trigger>{children}</BaseModal.Trigger>
|
||||
<BaseModal.Header description={titleHeader}>
|
||||
<span className="pr-2">{title}</span>
|
||||
<Icon
|
||||
name="icon"
|
||||
className="h-6 w-6 pl-1 text-foreground"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</BaseModal.Header>
|
||||
<BaseModal.Content>
|
||||
<Form.Root
|
||||
onSubmit={(event) => {
|
||||
if (password !== confirmPassword) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const data = Object.fromEntries(new FormData(event.currentTarget));
|
||||
resetForm();
|
||||
onConfirm(index ?? -1, data);
|
||||
setOpen(false);
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
<div className="grid gap-5">
|
||||
<Form.Field name="username">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
Username{" "}
|
||||
<span className="font-medium text-destructive">*</span>
|
||||
</Form.Label>
|
||||
</div>
|
||||
<Form.Control asChild>
|
||||
<input
|
||||
onChange={(input) => {
|
||||
setUserName(input.target.value);
|
||||
}}
|
||||
value={username}
|
||||
className="primary-input"
|
||||
required
|
||||
placeholder="Username"
|
||||
/>
|
||||
</Form.Control>
|
||||
<Form.Message match="valueMissing" className="field-invalid">
|
||||
Please enter your username
|
||||
</Form.Message>
|
||||
</Form.Field>
|
||||
|
||||
<div className="flex flex-row">
|
||||
<div className="mr-3 basis-1/2">
|
||||
<Form.Field
|
||||
name="password"
|
||||
serverInvalid={password != confirmPassword}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
Password{" "}
|
||||
<span className="font-medium text-destructive">*</span>
|
||||
</Form.Label>
|
||||
</div>
|
||||
<InputComponent
|
||||
onChange={(input) => {
|
||||
setPassword(input);
|
||||
}}
|
||||
value={password}
|
||||
password={true}
|
||||
isForm
|
||||
className="primary-input"
|
||||
required
|
||||
placeholder="Password"
|
||||
/>
|
||||
<Form.Message className="field-invalid" match="valueMissing">
|
||||
Please enter a password
|
||||
</Form.Message>
|
||||
|
||||
{password != confirmPassword && (
|
||||
<Form.Message className="field-invalid">
|
||||
Passwords do not match
|
||||
</Form.Message>
|
||||
)}
|
||||
</Form.Field>
|
||||
</div>
|
||||
|
||||
<div className="basis-1/2">
|
||||
<Form.Field
|
||||
name="confirmpassword"
|
||||
serverInvalid={password != confirmPassword}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
Confirm password{" "}
|
||||
<span className="font-medium text-destructive">*</span>
|
||||
</Form.Label>
|
||||
</div>
|
||||
<InputComponent
|
||||
onChange={(input) => {
|
||||
setConfirmPassword(input);
|
||||
}}
|
||||
value={confirmPassword}
|
||||
password={true}
|
||||
isForm
|
||||
className="primary-input"
|
||||
required
|
||||
placeholder="Confirm your password"
|
||||
/>
|
||||
<Form.Message className="field-invalid" match="valueMissing">
|
||||
Please confirm your password
|
||||
</Form.Message>
|
||||
</Form.Field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/*
|
||||
<Form.Field name="email">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
Email <span className="font-medium text-destructive">*</span>
|
||||
</Form.Label>
|
||||
<Form.Message className="field-invalid" match="valueMissing">
|
||||
Please enter your email
|
||||
</Form.Message>
|
||||
<Form.Message className="field-invalid" match="typeMismatch">
|
||||
Please provide a valid email
|
||||
</Form.Message>
|
||||
</div>
|
||||
<Form.Control asChild>
|
||||
<input className="primary-input" type="email" required />
|
||||
</Form.Control>
|
||||
</Form.Field> */}
|
||||
|
||||
{/*
|
||||
<Form.Field name="birth">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "baseline",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Form.Label className="data-[invalid]:label-invalid">
|
||||
Date of birth{" "}
|
||||
<span className="font-medium text-destructive">*</span>
|
||||
</Form.Label>
|
||||
<Form.Message className="field-invalid" match="valueMissing">
|
||||
Please enter your date of birth
|
||||
</Form.Message>
|
||||
</div>
|
||||
<Form.Control asChild>
|
||||
<input
|
||||
type="date"
|
||||
className="primary-input"
|
||||
required
|
||||
max={new Date().toISOString().split("T")[0]}
|
||||
/>
|
||||
</Form.Control>
|
||||
</Form.Field> */}
|
||||
</div>
|
||||
|
||||
<div className="float-right">
|
||||
<Form.Submit asChild>
|
||||
<Button className="mr-3 mt-8">{confirmationText}</Button>
|
||||
</Form.Submit>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
{cancelText}
|
||||
</Button>
|
||||
</div>
|
||||
</Form.Root>
|
||||
</BaseModal.Content>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import {
|
|||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "../../components/ui/dialog";
|
||||
import { modalHeaderType } from "../../types/components";
|
||||
|
||||
type ContentProps = { children: ReactNode };
|
||||
type HeaderProps = { children: ReactNode; description: string };
|
||||
|
|
@ -22,10 +23,10 @@ const Trigger: React.FC<ContentProps> = ({ children }) => {
|
|||
return <>{children}</>;
|
||||
};
|
||||
|
||||
const Header: React.FC<{ children: ReactNode; description: string }> = ({
|
||||
const Header: React.FC<{ children: ReactNode; description: string | null }> = ({
|
||||
children,
|
||||
description,
|
||||
}) => {
|
||||
}: modalHeaderType): JSX.Element => {
|
||||
return (
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center">{children}</DialogTitle>
|
||||
|
|
@ -47,7 +48,15 @@ interface BaseModalProps {
|
|||
open?: boolean;
|
||||
setOpen?: (open: boolean) => void;
|
||||
disable?: boolean;
|
||||
size?: "smaller" | "small" | "medium" | "large" | "large-h-full";
|
||||
size?:
|
||||
| "x-small"
|
||||
| "smaller"
|
||||
| "small"
|
||||
| "medium"
|
||||
| "large"
|
||||
| "large-h-full"
|
||||
| "small-h-full"
|
||||
| "medium-h-full";
|
||||
}
|
||||
function BaseModal({
|
||||
open,
|
||||
|
|
@ -73,6 +82,10 @@ function BaseModal({
|
|||
let height: string;
|
||||
|
||||
switch (size) {
|
||||
case "x-small":
|
||||
minWidth = "min-w-[20vw]";
|
||||
height = "h-[10vh]";
|
||||
break;
|
||||
case "smaller":
|
||||
minWidth = "min-w-[40vw]";
|
||||
height = "h-[27vh]";
|
||||
|
|
@ -81,10 +94,16 @@ function BaseModal({
|
|||
minWidth = "min-w-[40vw]";
|
||||
height = "h-[40vh]";
|
||||
break;
|
||||
case "small-h-full":
|
||||
minWidth = "min-w-[40vw]";
|
||||
break;
|
||||
case "medium":
|
||||
minWidth = "min-w-[60vw]";
|
||||
height = "h-[60vh]";
|
||||
break;
|
||||
case "medium-h-full":
|
||||
minWidth = "min-w-[60vw]";
|
||||
break;
|
||||
case "large":
|
||||
minWidth = "min-w-[80vw]";
|
||||
height = "h-[80vh]";
|
||||
|
|
@ -108,7 +127,9 @@ function BaseModal({
|
|||
{triggerChild}
|
||||
</DialogTrigger>
|
||||
<DialogContent className={minWidth}>
|
||||
{headerChild}
|
||||
<div className="truncate-doubleline word-break-break-word">
|
||||
{headerChild}
|
||||
</div>
|
||||
<div className={`mt-2 flex flex-col ${height} w-full `}>
|
||||
{ContentChild}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import "ace-builds/src-noconflict/mode-python";
|
|||
import "ace-builds/src-noconflict/theme-github";
|
||||
import "ace-builds/src-noconflict/theme-twilight";
|
||||
// import "ace-builds/webpack-resolver";
|
||||
import { ReactNode, useContext, useEffect, useState } from "react";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import AceEditor from "react-ace";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import { Button } from "../../components/ui/button";
|
||||
|
|
@ -13,7 +13,7 @@ import { alertContext } from "../../contexts/alertContext";
|
|||
import { darkContext } from "../../contexts/darkContext";
|
||||
import { typesContext } from "../../contexts/typesContext";
|
||||
import { postCustomComponent, postValidateCode } from "../../controllers/API";
|
||||
import { APIClassType } from "../../types/api";
|
||||
import { codeAreaModalPropsType } from "../../types/components";
|
||||
import BaseModal from "../baseModal";
|
||||
|
||||
export default function CodeAreaModal({
|
||||
|
|
@ -23,27 +23,20 @@ export default function CodeAreaModal({
|
|||
setNodeClass,
|
||||
children,
|
||||
dynamic,
|
||||
}: {
|
||||
setValue: (value: string) => void;
|
||||
value: string;
|
||||
nodeClass?: APIClassType;
|
||||
children: ReactNode;
|
||||
setNodeClass?: (Class: APIClassType) => void;
|
||||
dynamic?: boolean;
|
||||
}) {
|
||||
}: codeAreaModalPropsType): JSX.Element {
|
||||
const [code, setCode] = useState(value);
|
||||
const { dark } = useContext(darkContext);
|
||||
const { reactFlowInstance } = useContext(typesContext);
|
||||
const [height, setHeight] = useState(null);
|
||||
const [height, setHeight] = useState<string | null>(null);
|
||||
const { setErrorData, setSuccessData } = useContext(alertContext);
|
||||
const [error, setError] = useState<{
|
||||
detail: { error: string; traceback: string };
|
||||
}>(null);
|
||||
detail: { error: string | undefined; traceback: string | undefined };
|
||||
} | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// if nodeClass.template has more fields other than code and dynamic is true
|
||||
// do not run handleClick
|
||||
if (dynamic && Object.keys(nodeClass.template).length > 2) {
|
||||
if (dynamic && Object.keys(nodeClass!.template).length > 2) {
|
||||
return;
|
||||
}
|
||||
processCode();
|
||||
|
|
@ -90,7 +83,7 @@ export default function CodeAreaModal({
|
|||
}
|
||||
|
||||
function processDynamicField() {
|
||||
postCustomComponent(code, nodeClass)
|
||||
postCustomComponent(code, nodeClass!)
|
||||
.then((apiReturn) => {
|
||||
const { data } = apiReturn;
|
||||
if (data) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ReactNode, forwardRef, useContext, useEffect, useState } from "react";
|
||||
import { ReactNode, forwardRef, useContext, useState } from "react";
|
||||
import EditFlowSettings from "../../components/EditFlowSettingsComponent";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import { Button } from "../../components/ui/button";
|
||||
|
|
@ -8,78 +8,78 @@ import { TabsContext } from "../../contexts/tabsContext";
|
|||
import { removeApiKeys } from "../../utils/reactflowUtils";
|
||||
import BaseModal from "../baseModal";
|
||||
|
||||
const ExportModal = forwardRef((props: { children: ReactNode }, ref) => {
|
||||
const { flows, tabId, updateFlow, downloadFlow } = useContext(TabsContext);
|
||||
const [checked, setChecked] = useState(false);
|
||||
const flow = flows.find((f) => f.id === tabId);
|
||||
useEffect(() => {
|
||||
setName(flow.name);
|
||||
setDescription(flow.description);
|
||||
}, [flow.name, flow.description]);
|
||||
const [name, setName] = useState(flow.name);
|
||||
const [description, setDescription] = useState(flow.description);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [invalidName, setInvalidName] = useState(false);
|
||||
|
||||
return (
|
||||
<BaseModal size="smaller" open={open} setOpen={setOpen}>
|
||||
<BaseModal.Trigger>{props.children}</BaseModal.Trigger>
|
||||
<BaseModal.Header description={EXPORT_DIALOG_SUBTITLE}>
|
||||
<span className="pr-2">Export</span>
|
||||
<IconComponent
|
||||
name="Download"
|
||||
className="h-6 w-6 pl-1 text-foreground"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</BaseModal.Header>
|
||||
<BaseModal.Content>
|
||||
<EditFlowSettings
|
||||
invalidName={invalidName}
|
||||
setInvalidName={setInvalidName}
|
||||
name={name}
|
||||
description={description}
|
||||
flows={flows}
|
||||
tabId={tabId}
|
||||
setName={setName}
|
||||
setDescription={setDescription}
|
||||
updateFlow={updateFlow}
|
||||
/>
|
||||
<div className="mt-3 flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="terms"
|
||||
onCheckedChange={(event: boolean) => {
|
||||
setChecked(event);
|
||||
}}
|
||||
const ExportModal = forwardRef(
|
||||
(props: { children: ReactNode }, ref): JSX.Element => {
|
||||
const { flows, tabId, updateFlow, downloadFlow, saveFlow } =
|
||||
useContext(TabsContext);
|
||||
const [checked, setChecked] = useState(false);
|
||||
const [name, setName] = useState(
|
||||
flows.find((flow) => flow.id === tabId)?.name
|
||||
);
|
||||
const [invalidName, setInvalidName] = useState(false);
|
||||
const [description, setDescription] = useState(
|
||||
flows.find((flow) => flow.id === tabId)?.description
|
||||
);
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<BaseModal size="smaller" open={open} setOpen={setOpen}>
|
||||
<BaseModal.Trigger>{props.children}</BaseModal.Trigger>
|
||||
<BaseModal.Header description={EXPORT_DIALOG_SUBTITLE}>
|
||||
<span className="pr-2">Export</span>
|
||||
<IconComponent
|
||||
name="Download"
|
||||
className="h-6 w-6 pl-1 text-foreground"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<label htmlFor="terms" className="export-modal-save-api text-sm ">
|
||||
Save with my API keys
|
||||
</label>
|
||||
</div>
|
||||
</BaseModal.Content>
|
||||
</BaseModal.Header>
|
||||
<BaseModal.Content>
|
||||
<EditFlowSettings
|
||||
invalidName={invalidName}
|
||||
setInvalidName={setInvalidName}
|
||||
name={name!}
|
||||
description={description!}
|
||||
flows={flows}
|
||||
tabId={tabId}
|
||||
setName={setName}
|
||||
setDescription={setDescription}
|
||||
/>
|
||||
<div className="mt-3 flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="terms"
|
||||
onCheckedChange={(event: boolean): void => {
|
||||
setChecked(event);
|
||||
}}
|
||||
/>
|
||||
<label htmlFor="terms" className="export-modal-save-api text-sm ">
|
||||
Save with my API keys
|
||||
</label>
|
||||
</div>
|
||||
</BaseModal.Content>
|
||||
|
||||
<BaseModal.Footer>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (checked)
|
||||
downloadFlow(
|
||||
flows.find((flow) => flow.id === tabId),
|
||||
name,
|
||||
description
|
||||
);
|
||||
else
|
||||
downloadFlow(
|
||||
removeApiKeys(flows.find((flow) => flow.id === tabId)),
|
||||
name,
|
||||
description
|
||||
);
|
||||
setOpen(false);
|
||||
}}
|
||||
type="submit"
|
||||
>
|
||||
Download Flow
|
||||
</Button>
|
||||
</BaseModal.Footer>
|
||||
</BaseModal>
|
||||
);
|
||||
});
|
||||
<BaseModal.Footer>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (checked)
|
||||
downloadFlow(
|
||||
flows.find((flow) => flow.id === tabId)!,
|
||||
name!,
|
||||
description
|
||||
);
|
||||
else
|
||||
downloadFlow(
|
||||
removeApiKeys(flows.find((flow) => flow.id === tabId)!),
|
||||
name!,
|
||||
description
|
||||
);
|
||||
setOpen(false);
|
||||
}}
|
||||
type="submit"
|
||||
>
|
||||
Download Flow
|
||||
</Button>
|
||||
</BaseModal.Footer>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
);
|
||||
export default ExportModal;
|
||||
|
|
|
|||
|
|
@ -1,35 +1,34 @@
|
|||
import { useContext, useEffect, useState } from "react";
|
||||
import { useContext, useState } from "react";
|
||||
import EditFlowSettings from "../../components/EditFlowSettingsComponent";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { SETTINGS_DIALOG_SUBTITLE } from "../../constants/constants";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import { FlowSettingsPropsType } from "../../types/components";
|
||||
import BaseModal from "../baseModal";
|
||||
|
||||
export default function FlowSettingsModal({
|
||||
open,
|
||||
setOpen,
|
||||
}: {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
}) {
|
||||
const { setSuccessData } = useContext(alertContext);
|
||||
}: FlowSettingsPropsType): JSX.Element {
|
||||
const { setErrorData, setSuccessData } = useContext(alertContext);
|
||||
const ref = useRef();
|
||||
const { flows, tabId, updateFlow, saveFlow } = useContext(TabsContext);
|
||||
const flow = flows.find((f) => f.id === tabId);
|
||||
useEffect(() => {
|
||||
setName(flow.name);
|
||||
setDescription(flow.description);
|
||||
}, [flow.name, flow.description]);
|
||||
const [name, setName] = useState(flow.name);
|
||||
const [description, setDescription] = useState(flow.description);
|
||||
const maxLength = 50;
|
||||
const [name, setName] = useState(
|
||||
flows.find((flow) => flow.id === tabId)!.name
|
||||
);
|
||||
const [description, setDescription] = useState(
|
||||
flows.find((flow) => flow.id === tabId)!.description
|
||||
);
|
||||
const [invalidName, setInvalidName] = useState(false);
|
||||
|
||||
function handleClick() {
|
||||
function handleClick(): void {
|
||||
let savedFlow = flows.find((flow) => flow.id === tabId);
|
||||
savedFlow.name = name;
|
||||
savedFlow.description = description;
|
||||
saveFlow(savedFlow);
|
||||
savedFlow!.name = name;
|
||||
savedFlow!.description = description;
|
||||
saveFlow(savedFlow!);
|
||||
setSuccessData({ title: "Changes saved successfully" });
|
||||
setOpen(false);
|
||||
}
|
||||
|
|
@ -49,7 +48,6 @@ export default function FlowSettingsModal({
|
|||
tabId={tabId}
|
||||
setName={setName}
|
||||
setDescription={setDescription}
|
||||
updateFlow={updateFlow}
|
||||
/>
|
||||
</BaseModal.Content>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useEffect } from "react";
|
||||
import IconComponent from "../../../components/genericIconComponent";
|
||||
import { Textarea } from "../../../components/ui/textarea";
|
||||
import { chatInputType } from "../../../types/components";
|
||||
import { classNames } from "../../../utils/utils";
|
||||
|
||||
export default function ChatInput({
|
||||
|
|
@ -10,7 +11,7 @@ export default function ChatInput({
|
|||
setChatValue,
|
||||
inputRef,
|
||||
noInput,
|
||||
}) {
|
||||
}: chatInputType): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (!lockChat && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
|
|
@ -46,7 +47,7 @@ export default function ChatInput({
|
|||
}`,
|
||||
}}
|
||||
value={lockChat ? "Thinking..." : chatValue}
|
||||
onChange={(event) => {
|
||||
onChange={(event): void => {
|
||||
setChatValue(event.target.value);
|
||||
}}
|
||||
className={classNames(
|
||||
|
|
@ -75,7 +76,7 @@ export default function ChatInput({
|
|||
: "bg-chat-send text-background"
|
||||
)}
|
||||
disabled={lockChat}
|
||||
onClick={() => sendMessage()}
|
||||
onClick={(): void => sendMessage()}
|
||||
>
|
||||
{lockChat ? (
|
||||
<IconComponent
|
||||
|
|
|
|||
|
|
@ -3,16 +3,12 @@ import { useState } from "react";
|
|||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
||||
import { programmingLanguages } from "../../../../constants/constants";
|
||||
import { Props } from "../../../../types/components";
|
||||
|
||||
interface Props {
|
||||
language: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export function CodeBlock({ language, value }) {
|
||||
export function CodeBlock({ language, value }: Props): JSX.Element {
|
||||
const [isCopied, setIsCopied] = useState<Boolean>(false);
|
||||
|
||||
const copyToClipboard = () => {
|
||||
const copyToClipboard = (): void => {
|
||||
if (!navigator.clipboard || !navigator.clipboard.writeText) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -25,7 +21,7 @@ export function CodeBlock({ language, value }) {
|
|||
}, 2000);
|
||||
});
|
||||
};
|
||||
const downloadAsFile = () => {
|
||||
const downloadAsFile = (): void => {
|
||||
const fileExtension = programmingLanguages[language] || ".file";
|
||||
const suggestedFileName = `${"generated-code"}${fileExtension}`;
|
||||
const fileName = window.prompt("enter file name", suggestedFileName);
|
||||
|
|
|
|||
|
|
@ -9,18 +9,15 @@ import Robot from "../../../assets/robot.png";
|
|||
import SanitizedHTMLWrapper from "../../../components/SanitizedHTMLWrapper";
|
||||
import CodeTabsComponent from "../../../components/codeTabsComponent";
|
||||
import IconComponent from "../../../components/genericIconComponent";
|
||||
import { ChatMessageType } from "../../../types/chat";
|
||||
import { chatMessagePropsType } from "../../../types/components";
|
||||
import { classNames } from "../../../utils/utils";
|
||||
import FileCard from "../fileComponent";
|
||||
|
||||
export default function ChatMessage({
|
||||
chat,
|
||||
lockChat,
|
||||
lastMessage,
|
||||
}: {
|
||||
chat: ChatMessageType;
|
||||
lockChat: boolean;
|
||||
lastMessage: boolean;
|
||||
}) {
|
||||
}: chatMessagePropsType): JSX.Element {
|
||||
const convert = new Convert({ newline: true });
|
||||
const [hidden, setHidden] = useState(true);
|
||||
const template = chat.template;
|
||||
|
|
@ -57,7 +54,7 @@ export default function ChatMessage({
|
|||
<div className="form-modal-chat-text">
|
||||
{hidden && chat.thought && chat.thought !== "" && (
|
||||
<div
|
||||
onClick={() => setHidden((prev) => !prev)}
|
||||
onClick={(): void => setHidden((prev) => !prev)}
|
||||
className="form-modal-chat-icon-div"
|
||||
>
|
||||
<IconComponent
|
||||
|
|
@ -192,7 +189,7 @@ export default function ChatMessage({
|
|||
? template?.split("\n")?.map((line, index) => {
|
||||
const regex = /{([^}]+)}/g;
|
||||
let match;
|
||||
let parts = [];
|
||||
let parts: Array<JSX.Element | string> = [];
|
||||
let lastIndex = 0;
|
||||
while ((match = regex.exec(line)) !== null) {
|
||||
// Push text up to the match
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
import * as base64js from "base64-js";
|
||||
import { useState } from "react";
|
||||
import IconComponent from "../../../components/genericIconComponent";
|
||||
import { fileCardPropsType } from "../../../types/components";
|
||||
|
||||
export default function FileCard({ fileName, content, fileType }) {
|
||||
const handleDownload = () => {
|
||||
export default function FileCard({
|
||||
fileName,
|
||||
content,
|
||||
fileType,
|
||||
}: fileCardPropsType): JSX.Element {
|
||||
const handleDownload = (): void => {
|
||||
const byteArray = new Uint8Array(base64js.toByteArray(content));
|
||||
const blob = new Blob([byteArray], { type: "application/octet-stream" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
|
@ -16,10 +21,10 @@ export default function FileCard({ fileName, content, fileType }) {
|
|||
URL.revokeObjectURL(url);
|
||||
};
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
function handleMouseEnter() {
|
||||
function handleMouseEnter(): void {
|
||||
setIsHovered(true);
|
||||
}
|
||||
function handleMouseLeave() {
|
||||
function handleMouseLeave(): void {
|
||||
setIsHovered(false);
|
||||
}
|
||||
|
||||
|
|
@ -55,7 +60,7 @@ export default function FileCard({ fileName, content, fileType }) {
|
|||
return (
|
||||
<button onClick={handleDownload} className="file-card-modal-button">
|
||||
<div className="file-card-modal-div">
|
||||
{" "}
|
||||
ooooooooooooooo{" "}
|
||||
{fileType === "image" ? (
|
||||
<img
|
||||
src={`data:image/png;base64,${content}`}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import {
|
|||
import { Textarea } from "../../components/ui/textarea";
|
||||
import { CHAT_FORM_DIALOG_SUBTITLE } from "../../constants/constants";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import { TabsState } from "../../types/tabs";
|
||||
import { validateNodes } from "../../utils/reactflowUtils";
|
||||
|
||||
export default function FormModal({
|
||||
|
|
@ -34,7 +35,7 @@ export default function FormModal({
|
|||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
flow: FlowType;
|
||||
}) {
|
||||
}): JSX.Element {
|
||||
const { tabsState, setTabsState } = useContext(TabsContext);
|
||||
const [chatValue, setChatValue] = useState(() => {
|
||||
try {
|
||||
|
|
@ -45,11 +46,11 @@ export default function FormModal({
|
|||
const inputKeys = formKeysData.input_keys;
|
||||
const handleKeys = formKeysData.handle_keys;
|
||||
|
||||
const keyToUse = Object.keys(inputKeys).find(
|
||||
(key) => !handleKeys.some((j) => j === key) && inputKeys[key] === ""
|
||||
const keyToUse = Object.keys(inputKeys!).find(
|
||||
(key) => !handleKeys?.some((j) => j === key) && inputKeys![key] === ""
|
||||
);
|
||||
|
||||
return inputKeys[keyToUse];
|
||||
return inputKeys![keyToUse!];
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
// return a sensible default or `undefined` if no default is possible
|
||||
|
|
@ -63,16 +64,17 @@ export default function FormModal({
|
|||
const ws = useRef<WebSocket | null>(null);
|
||||
const [lockChat, setLockChat] = useState(false);
|
||||
const isOpen = useRef(open);
|
||||
const messagesRef = useRef(null);
|
||||
const messagesRef = useRef<HTMLDivElement | null>(null);
|
||||
const id = useRef(flow.id);
|
||||
const tabsStateFlowId = tabsState[flow.id];
|
||||
const tabsStateFlowIdFormKeysData = tabsStateFlowId.formKeysData;
|
||||
const [chatKey, setChatKey] = useState(() => {
|
||||
if (tabsState[flow.id]?.formKeysData?.input_keys) {
|
||||
return Object.keys(tabsState[flow.id].formKeysData.input_keys).find(
|
||||
return Object.keys(tabsState[flow.id].formKeysData.input_keys!).find(
|
||||
(key) =>
|
||||
!tabsState[flow.id].formKeysData.handle_keys.some((j) => j === key) &&
|
||||
tabsState[flow.id].formKeysData.input_keys[key] === ""
|
||||
!tabsState[flow.id].formKeysData.handle_keys!.some(
|
||||
(j) => j === key
|
||||
) && tabsState[flow.id].formKeysData.input_keys![key] === ""
|
||||
);
|
||||
}
|
||||
// TODO: return a sensible default
|
||||
|
|
@ -149,7 +151,7 @@ export default function FormModal({
|
|||
});
|
||||
}
|
||||
|
||||
function handleOnClose(event: CloseEvent) {
|
||||
function handleOnClose(event: CloseEvent): void {
|
||||
if (isOpen.current) {
|
||||
setErrorData({ title: event.reason });
|
||||
setTimeout(() => {
|
||||
|
|
@ -159,8 +161,12 @@ export default function FormModal({
|
|||
}
|
||||
}
|
||||
|
||||
function getWebSocketUrl(chatId, isDevelopment = false) {
|
||||
const isSecureProtocol = window.location.protocol === "https:";
|
||||
function getWebSocketUrl(
|
||||
chatId: string,
|
||||
isDevelopment: boolean = false
|
||||
): string {
|
||||
const isSecureProtocol =
|
||||
window.location.protocol === "https:" || window.location.port === "443";
|
||||
const webSocketProtocol = isSecureProtocol ? "wss" : "ws";
|
||||
const host = isDevelopment ? "localhost:7860" : window.location.host;
|
||||
const chatEndpoint = `/api/v1/chat/${chatId}`;
|
||||
|
|
@ -211,7 +217,7 @@ export default function FormModal({
|
|||
});
|
||||
}
|
||||
if (data.type === "start") {
|
||||
addChatHistory("", false, chatKey);
|
||||
addChatHistory("", false, chatKey!);
|
||||
isStream = true;
|
||||
}
|
||||
if (data.type === "end") {
|
||||
|
|
@ -240,7 +246,7 @@ export default function FormModal({
|
|||
}
|
||||
}
|
||||
|
||||
function connectWS() {
|
||||
function connectWS(): void {
|
||||
try {
|
||||
const urlWs = getWebSocketUrl(
|
||||
id.current,
|
||||
|
|
@ -307,15 +313,15 @@ export default function FormModal({
|
|||
// do not add connectWS on dependencies array
|
||||
}, [lockChat]);
|
||||
|
||||
async function sendAll(data: sendAllProps) {
|
||||
async function sendAll(data: sendAllProps): Promise<void> {
|
||||
try {
|
||||
if (ws) {
|
||||
ws.current.send(JSON.stringify(data));
|
||||
ws.current?.send(JSON.stringify(data));
|
||||
}
|
||||
} catch (error) {
|
||||
setErrorData({
|
||||
title: "There was an error sending the message",
|
||||
list: [error.message],
|
||||
list: [(error as { message: string }).message],
|
||||
});
|
||||
setChatValue(data.inputs);
|
||||
connectWS();
|
||||
|
|
@ -326,7 +332,7 @@ export default function FormModal({
|
|||
if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" });
|
||||
}, [chatHistory]);
|
||||
|
||||
const ref = useRef(null);
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (open && ref.current) {
|
||||
|
|
@ -334,30 +340,31 @@ export default function FormModal({
|
|||
}
|
||||
}, [open]);
|
||||
|
||||
function sendMessage() {
|
||||
let nodeValidationErrors = validateNodes(reactFlowInstance);
|
||||
function sendMessage(): void {
|
||||
let nodeValidationErrors = validateNodes(reactFlowInstance!);
|
||||
if (nodeValidationErrors.length === 0) {
|
||||
setLockChat(true);
|
||||
let inputs = tabsState[id.current].formKeysData.input_keys;
|
||||
setChatValue("");
|
||||
const message = inputs;
|
||||
addChatHistory(
|
||||
message,
|
||||
message!,
|
||||
true,
|
||||
chatKey,
|
||||
chatKey!,
|
||||
tabsState[flow.id].formKeysData.template
|
||||
);
|
||||
sendAll({
|
||||
...reactFlowInstance.toObject(),
|
||||
inputs: inputs,
|
||||
...reactFlowInstance?.toObject()!,
|
||||
inputs: inputs!,
|
||||
chatHistory,
|
||||
name: flow.name,
|
||||
description: flow.description,
|
||||
});
|
||||
setTabsState((old) => {
|
||||
//@ts-ignore
|
||||
setTabsState((old: TabsState) => {
|
||||
if (!chatKey) return old;
|
||||
let newTabsState = _.cloneDeep(old);
|
||||
newTabsState[id.current].formKeysData.input_keys[chatKey] = "";
|
||||
newTabsState[id.current].formKeysData.input_keys![chatKey] = "";
|
||||
return newTabsState;
|
||||
});
|
||||
} else {
|
||||
|
|
@ -367,18 +374,18 @@ export default function FormModal({
|
|||
});
|
||||
}
|
||||
}
|
||||
function clearChat() {
|
||||
function clearChat(): void {
|
||||
setChatHistory([]);
|
||||
ws.current.send(JSON.stringify({ clear_history: true }));
|
||||
ws.current?.send(JSON.stringify({ clear_history: true }));
|
||||
if (lockChat) setLockChat(false);
|
||||
}
|
||||
|
||||
function handleOnCheckedChange(checked: boolean, i: string) {
|
||||
if (checked === true) {
|
||||
setChatKey(i);
|
||||
setChatValue(tabsState[flow.id].formKeysData.input_keys[i]);
|
||||
setChatValue(tabsState[flow.id].formKeysData.input_keys![i]);
|
||||
} else {
|
||||
setChatKey(null);
|
||||
setChatKey(null!);
|
||||
setChatValue("");
|
||||
}
|
||||
}
|
||||
|
|
@ -424,7 +431,7 @@ export default function FormModal({
|
|||
|
||||
{tabsState[id.current]?.formKeysData?.input_keys
|
||||
? Object.keys(
|
||||
tabsState[id.current].formKeysData.input_keys
|
||||
tabsState[id.current].formKeysData.input_keys!
|
||||
).map((key, index) => (
|
||||
<div className="file-component-accordion-div" key={index}>
|
||||
<AccordionComponent
|
||||
|
|
@ -448,7 +455,7 @@ export default function FormModal({
|
|||
size="small"
|
||||
disabled={tabsState[
|
||||
id.current
|
||||
].formKeysData.handle_keys.some(
|
||||
].formKeysData.handle_keys!.some(
|
||||
(t) => t === key
|
||||
)}
|
||||
/>
|
||||
|
|
@ -459,7 +466,7 @@ export default function FormModal({
|
|||
keyValue={key}
|
||||
>
|
||||
<div className="file-component-tab-column">
|
||||
{tabsState[id.current].formKeysData.handle_keys.some(
|
||||
{tabsState[id.current].formKeysData.handle_keys!.some(
|
||||
(t) => t === key
|
||||
) && (
|
||||
<div className="font-normal text-muted-foreground ">
|
||||
|
|
@ -469,14 +476,18 @@ export default function FormModal({
|
|||
<Textarea
|
||||
className="custom-scroll"
|
||||
value={
|
||||
tabsState[id.current].formKeysData.input_keys[key]
|
||||
tabsState[id.current].formKeysData.input_keys![
|
||||
key
|
||||
]
|
||||
}
|
||||
onChange={(e) => {
|
||||
setTabsState((old) => {
|
||||
//@ts-ignore
|
||||
setTabsState((old: TabsState) => {
|
||||
let newTabsState = _.cloneDeep(old);
|
||||
newTabsState[
|
||||
id.current
|
||||
].formKeysData.input_keys[key] = e.target.value;
|
||||
].formKeysData.input_keys![key] =
|
||||
e.target.value;
|
||||
return newTabsState;
|
||||
});
|
||||
}}
|
||||
|
|
@ -488,7 +499,7 @@ export default function FormModal({
|
|||
</div>
|
||||
))
|
||||
: null}
|
||||
{tabsState[id.current].formKeysData.memory_keys.map(
|
||||
{tabsState[id.current].formKeysData.memory_keys!.map(
|
||||
(key, index) => (
|
||||
<div className="file-component-accordion-div" key={index}>
|
||||
<AccordionComponent
|
||||
|
|
@ -582,10 +593,11 @@ export default function FormModal({
|
|||
sendMessage={sendMessage}
|
||||
setChatValue={(value) => {
|
||||
setChatValue(value);
|
||||
setTabsState((old) => {
|
||||
//@ts-ignore
|
||||
setTabsState((old: TabsState) => {
|
||||
let newTabsState = _.cloneDeep(old);
|
||||
newTabsState[id.current].formKeysData.input_keys[
|
||||
chatKey
|
||||
newTabsState[id.current].formKeysData.input_keys![
|
||||
chatKey!
|
||||
] = value;
|
||||
return newTabsState;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ReactNode, useContext, useEffect, useRef, useState } from "react";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import SanitizedHTMLWrapper from "../../components/SanitizedHTMLWrapper";
|
||||
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
|
|
@ -15,8 +15,7 @@ import {
|
|||
import { TypeModal } from "../../constants/enums";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { postValidatePrompt } from "../../controllers/API";
|
||||
import { APIClassType } from "../../types/api";
|
||||
import { handleKeyDown } from "../../utils/reactflowUtils";
|
||||
import { genericModalPropsType } from "../../types/components";
|
||||
import {
|
||||
classNames,
|
||||
getRandomKeyByssmm,
|
||||
|
|
@ -34,39 +33,29 @@ export default function GenericModal({
|
|||
nodeClass,
|
||||
setNodeClass,
|
||||
children,
|
||||
}: {
|
||||
field_name?: string;
|
||||
setValue: (value: string) => void;
|
||||
value: string;
|
||||
buttonText: string;
|
||||
modalTitle: string;
|
||||
type: number;
|
||||
children: ReactNode;
|
||||
nodeClass?: APIClassType;
|
||||
setNodeClass?: (Class: APIClassType) => void;
|
||||
}) {
|
||||
}: genericModalPropsType): JSX.Element {
|
||||
const [myButtonText] = useState(buttonText);
|
||||
const [myModalTitle] = useState(modalTitle);
|
||||
const [myModalType] = useState(type);
|
||||
const [inputValue, setInputValue] = useState(value);
|
||||
const [isEdit, setIsEdit] = useState(true);
|
||||
const [wordsHighlight, setWordsHighlight] = useState([]);
|
||||
const [wordsHighlight, setWordsHighlight] = useState<string[]>([]);
|
||||
const { setErrorData, setSuccessData, setNoticeData } =
|
||||
useContext(alertContext);
|
||||
const ref = useRef();
|
||||
const divRef = useRef(null);
|
||||
const divRefPrompt = useRef(null);
|
||||
|
||||
function checkVariables(valueToCheck) {
|
||||
function checkVariables(valueToCheck: string): void {
|
||||
const regex = /\{([^{}]+)\}/g;
|
||||
const matches = [];
|
||||
const matches: string[] = [];
|
||||
let match;
|
||||
while ((match = regex.exec(valueToCheck))) {
|
||||
matches.push(`{${match[1]}}`);
|
||||
}
|
||||
|
||||
let invalid_chars = [];
|
||||
let fixed_variables = [];
|
||||
let invalid_chars: string[] = [];
|
||||
let fixed_variables: string[] = [];
|
||||
let input_variables = matches;
|
||||
for (let variable of input_variables) {
|
||||
let new_var = variable;
|
||||
|
|
@ -107,7 +96,7 @@ export default function GenericModal({
|
|||
.replace(regexHighlight, varHighlightHTML({ name: "$1" }))
|
||||
.replace(/\n/g, "<br />");
|
||||
|
||||
const TextAreaContentView = () => {
|
||||
const TextAreaContentView = (): JSX.Element => {
|
||||
return (
|
||||
<SanitizedHTMLWrapper
|
||||
className={getClassByNumberLength()}
|
||||
|
|
@ -120,7 +109,7 @@ export default function GenericModal({
|
|||
);
|
||||
};
|
||||
|
||||
function getClassByNumberLength() {
|
||||
function getClassByNumberLength(): string {
|
||||
let sumOfCaracteres: number = 0;
|
||||
wordsHighlight.forEach((element) => {
|
||||
sumOfCaracteres = sumOfCaracteres + element.replace(/[{}]/g, "").length;
|
||||
|
|
@ -130,8 +119,8 @@ export default function GenericModal({
|
|||
: "code-nohighlight";
|
||||
}
|
||||
|
||||
function validatePrompt(closeModal: boolean) {
|
||||
postValidatePrompt(field_name, inputValue, nodeClass)
|
||||
function validatePrompt(closeModal: boolean): void {
|
||||
postValidatePrompt(field_name, inputValue, nodeClass!)
|
||||
.then((apiReturn) => {
|
||||
if (apiReturn.data) {
|
||||
let inputVariables = apiReturn.data.input_variables ?? [];
|
||||
|
|
@ -145,7 +134,7 @@ export default function GenericModal({
|
|||
setSuccessData({
|
||||
title: "Prompt is ready",
|
||||
});
|
||||
setNodeClass(apiReturn.data?.frontend_node);
|
||||
setNodeClass!(apiReturn.data?.frontend_node);
|
||||
setModalOpen(closeModal);
|
||||
setValue(inputValue);
|
||||
}
|
||||
|
|
@ -222,6 +211,7 @@ export default function GenericModal({
|
|||
<TextAreaContentView />
|
||||
) : type !== TypeModal.PROMPT ? (
|
||||
<Textarea
|
||||
//@ts-ignore
|
||||
ref={ref}
|
||||
className="form-input h-full w-full rounded-lg focus-visible:ring-1"
|
||||
value={inputValue}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ReactNode } from "react";
|
||||
import { buttonBoxPropsType } from "../../../types/components";
|
||||
import { classNames } from "../../../utils/utils";
|
||||
|
||||
export default function ButtonBox({
|
||||
|
|
@ -10,16 +10,7 @@ export default function ButtonBox({
|
|||
textColor,
|
||||
deactivate,
|
||||
size,
|
||||
}: {
|
||||
onClick: () => void;
|
||||
title: string;
|
||||
description: string;
|
||||
icon: ReactNode;
|
||||
bgColor: string;
|
||||
textColor: string;
|
||||
deactivate?: boolean;
|
||||
size: "small" | "medium" | "big";
|
||||
}) {
|
||||
}: buttonBoxPropsType): JSX.Element {
|
||||
let bigCircle: string;
|
||||
let smallCircle: string;
|
||||
let titleFontSize: string;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import { FlowType } from "../../types/flow";
|
|||
import { classNames } from "../../utils/utils";
|
||||
import ButtonBox from "./buttonBox";
|
||||
|
||||
export default function ImportModal() {
|
||||
export default function ImportModal(): JSX.Element {
|
||||
const [open, setOpen] = useState(true);
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const ref = useRef();
|
||||
|
|
@ -32,7 +32,7 @@ export default function ImportModal() {
|
|||
const [examples, setExamples] = useState<FlowType[]>([]);
|
||||
const { uploadFlow, addFlow } = useContext(TabsContext);
|
||||
|
||||
function handleExamples() {
|
||||
function handleExamples(): void {
|
||||
setLoadingExamples(true);
|
||||
getExamples()
|
||||
.then((result) => {
|
||||
|
|
|
|||
31
src/frontend/src/pages/AdminPage/LoginPage/index.tsx
Normal file
31
src/frontend/src/pages/AdminPage/LoginPage/index.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { useNavigate } from "react-router-dom";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
import { Input } from "../../../components/ui/input";
|
||||
|
||||
export default function LoginAdminPage() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
function loginAdmin() {
|
||||
navigate("/admin/");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center bg-muted">
|
||||
<div className="flex w-72 flex-col items-center justify-center gap-2">
|
||||
<span className="mb-4 text-5xl">⛓️</span>
|
||||
<span className="mb-6 text-2xl font-semibold text-primary">Admin</span>
|
||||
<Input className="bg-background" placeholder="Email address" />
|
||||
<Input className="bg-background" placeholder="Password" />
|
||||
<Button
|
||||
onClick={() => {
|
||||
loginAdmin();
|
||||
}}
|
||||
variant="default"
|
||||
className="w-full"
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
401
src/frontend/src/pages/AdminPage/index.tsx
Normal file
401
src/frontend/src/pages/AdminPage/index.tsx
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
import _ from "lodash";
|
||||
import { X } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import PaginatorComponent from "../../components/PaginatorComponent";
|
||||
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { Input } from "../../components/ui/input";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "../../components/ui/table";
|
||||
import ConfirmationModal from "../../modals/ConfirmationModal";
|
||||
import UserManagementModal from "../../modals/UserManagementModal";
|
||||
|
||||
export default function AdminPage() {
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
|
||||
const [size, setPageSize] = useState(10);
|
||||
const [index, setPageIndex] = useState(1);
|
||||
|
||||
const userList = useRef([
|
||||
{
|
||||
user: generateRandomString(50),
|
||||
email: generateRandomString(50) + "@example.com",
|
||||
password: generateRandomString(50),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
{
|
||||
user: generateRandomString(8),
|
||||
email: generateRandomString(10) + "@example.com",
|
||||
password: generateRandomString(12),
|
||||
register_date: generateRandomDate(),
|
||||
},
|
||||
]);
|
||||
|
||||
const [filterUserList, setFilterUserList] = useState(userList.current);
|
||||
|
||||
function generateRandomString(length) {
|
||||
const characters =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
let result = "";
|
||||
for (let i = 0; i < length; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * characters.length);
|
||||
result += characters.charAt(randomIndex);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function generateRandomDate() {
|
||||
const start = new Date(2010, 0, 1);
|
||||
const end = new Date();
|
||||
const randomTimestamp =
|
||||
start.getTime() + Math.random() * (end.getTime() - start.getTime());
|
||||
const randomDate = new Date(randomTimestamp);
|
||||
|
||||
const options = { year: "numeric", month: "short", day: "numeric" };
|
||||
return randomDate.toLocaleDateString("en-US");
|
||||
}
|
||||
|
||||
const [editUser, setEditUser] = useState(-1);
|
||||
const [editedUser, setEditedUser] = useState("");
|
||||
const [modalEditOpen, setModalEditOpen] = useState(false);
|
||||
const [modalDeleteOpen, setModalDeleteOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
resetFilter();
|
||||
}, []);
|
||||
|
||||
const handleInputChange = (event, index) => {
|
||||
const user = _.cloneDeepWith(userList.current);
|
||||
user[index].password = event.target.value;
|
||||
userList.current = user;
|
||||
|
||||
const userFilter = _.cloneDeepWith(filterUserList);
|
||||
userFilter[index].password = event.target.value;
|
||||
setFilterUserList(userFilter);
|
||||
|
||||
setEditedUser(event.target.value);
|
||||
};
|
||||
|
||||
function handleChangePagination(pageIndex: number, pageSize: number) {
|
||||
setPageIndex(pageIndex);
|
||||
setPageSize(pageSize);
|
||||
|
||||
const startIndex = (pageIndex - 1) * pageSize;
|
||||
const endIndex = startIndex + pageSize;
|
||||
const newList = userList.current.slice(startIndex, endIndex);
|
||||
|
||||
setFilterUserList(newList);
|
||||
}
|
||||
|
||||
function resetFilter() {
|
||||
setFilterUserList(userList.current);
|
||||
setPageIndex(1);
|
||||
setPageSize(10);
|
||||
|
||||
const startIndex = (index - 1) * size;
|
||||
const endIndex = index + size - 1;
|
||||
const newList = userList.current.slice(startIndex, endIndex);
|
||||
|
||||
console.log(userList.current);
|
||||
|
||||
setFilterUserList(newList);
|
||||
}
|
||||
|
||||
function handleFilterUsers(input: string) {
|
||||
setInputValue(input);
|
||||
|
||||
if (input === "") {
|
||||
resetFilter();
|
||||
} else {
|
||||
const filteredList = userList.current.filter(
|
||||
(user) =>
|
||||
user.user.toLowerCase().includes(input.toLowerCase()) ||
|
||||
user.email.toLowerCase().includes(input.toLowerCase())
|
||||
);
|
||||
setFilterUserList(filteredList);
|
||||
}
|
||||
}
|
||||
|
||||
function handleDeleteUser(index) {
|
||||
const user = _.cloneDeepWith(userList.current);
|
||||
user.splice(index, 1);
|
||||
userList.current = user;
|
||||
|
||||
const userFilter = _.cloneDeepWith(filterUserList);
|
||||
userFilter.splice(index, 1);
|
||||
setFilterUserList(userFilter);
|
||||
|
||||
resetFilter();
|
||||
}
|
||||
|
||||
function handleEditUser(index, user) {
|
||||
const newUser = _.cloneDeepWith(userList.current);
|
||||
newUser[index].password = user.password;
|
||||
newUser[index].user = user.username;
|
||||
userList.current = newUser;
|
||||
resetFilter();
|
||||
}
|
||||
|
||||
function handleNewUser(user) {
|
||||
const newUser = {
|
||||
user: user.username,
|
||||
email: generateRandomString(50) + "@example.com",
|
||||
password: user.password,
|
||||
register_date: generateRandomDate(),
|
||||
};
|
||||
|
||||
userList.current.unshift(newUser);
|
||||
console.log(userList.current);
|
||||
|
||||
resetFilter();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="main-page-panel">
|
||||
<div className="m-auto flex h-full flex-row justify-center">
|
||||
<div className="basis-5/6">
|
||||
<div className="m-auto flex h-full flex-col space-y-8 p-8 ">
|
||||
<div className="flex items-center justify-between space-y-2">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold tracking-tight">
|
||||
Welcome back!
|
||||
</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Here's a list of all users!
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2"></div>
|
||||
</div>
|
||||
|
||||
{userList.current.length === 0 && (
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<h2>There's no users left :)</h2>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{userList.current.length > 0 && (
|
||||
<>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-1 items-center space-x-2">
|
||||
<Input
|
||||
value={inputValue}
|
||||
placeholder="Filter users..."
|
||||
className="h-8 w-[150px] lg:w-[250px]"
|
||||
onChange={(e) => handleFilterUsers(e.target.value)}
|
||||
/>
|
||||
{inputValue.length > 0 && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
resetFilter();
|
||||
setInputValue("");
|
||||
}}
|
||||
variant="ghost"
|
||||
className="h-8 px-2 lg:px-3"
|
||||
>
|
||||
Reset
|
||||
<X className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<UserManagementModal
|
||||
title="New User"
|
||||
titleHeader={"Add a new user"}
|
||||
cancelText="Cancel"
|
||||
confirmationText="Save"
|
||||
icon={"UserPlus2"}
|
||||
onConfirm={(index, user) => {
|
||||
handleNewUser(user);
|
||||
}}
|
||||
>
|
||||
<Button>New User</Button>
|
||||
</UserManagementModal>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted
|
||||
custom-scroll"
|
||||
>
|
||||
<Table className="table-fixed bg-muted outline-1 ">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="h-10">User</TableHead>
|
||||
<TableHead className="h-10">Password</TableHead>
|
||||
<TableHead className="h-10 w-[100px] text-right"></TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filterUserList.map((user, index) => (
|
||||
<TableRow key={user.user}>
|
||||
<TableCell className="truncate py-2 font-medium">
|
||||
{user.user}
|
||||
</TableCell>
|
||||
<TableCell className="truncate py-2">
|
||||
{user.password}
|
||||
</TableCell>
|
||||
<TableCell className="flex w-[100px] py-2 text-right">
|
||||
<div className="flex">
|
||||
<UserManagementModal
|
||||
title="Edit"
|
||||
titleHeader={`${user.user}`}
|
||||
cancelText="Cancel"
|
||||
confirmationText="Edit"
|
||||
icon={"UserPlus2"}
|
||||
data={user}
|
||||
index={index}
|
||||
onConfirm={(index, user) => {
|
||||
handleEditUser(index, user);
|
||||
}}
|
||||
>
|
||||
<ShadTooltip content="Edit" side="top">
|
||||
<IconComponent
|
||||
name="Pencil"
|
||||
className="h-4 w-4 cursor-pointer"
|
||||
/>
|
||||
</ShadTooltip>
|
||||
</UserManagementModal>
|
||||
|
||||
<ConfirmationModal
|
||||
title="Delete"
|
||||
titleHeader="Delete User"
|
||||
modalContentTitle="Attention!"
|
||||
modalContent="Are you sure you want to delete this user? This action cannot be undone."
|
||||
cancelText="Cancel"
|
||||
confirmationText="Delete"
|
||||
icon={"UserMinus2"}
|
||||
data={user}
|
||||
index={index}
|
||||
onConfirm={(index, user) => {
|
||||
handleDeleteUser(index);
|
||||
}}
|
||||
>
|
||||
<ShadTooltip content="Delete" side="top">
|
||||
<IconComponent
|
||||
name="Trash2"
|
||||
className="ml-2 h-4 w-4 cursor-pointer"
|
||||
/>
|
||||
</ShadTooltip>
|
||||
</ConfirmationModal>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<PaginatorComponent
|
||||
pageIndex={index}
|
||||
pageSize={size}
|
||||
totalRowsCount={filterUserList.length}
|
||||
paginate={(pageIndex, pageSize) => {
|
||||
handleChangePagination(pageSize, pageIndex);
|
||||
}}
|
||||
></PaginatorComponent>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import IconComponent from "../../components/genericIconComponent";
|
|||
import Header from "../../components/headerComponent";
|
||||
import { getExamples } from "../../controllers/API";
|
||||
import { FlowType } from "../../types/flow";
|
||||
export default function CommunityPage() {
|
||||
export default function CommunityPage(): JSX.Element {
|
||||
const { flows, setTabId, downloadFlows, uploadFlows, addFlow } =
|
||||
useContext(TabsContext);
|
||||
|
||||
|
|
@ -22,7 +22,7 @@ export default function CommunityPage() {
|
|||
const [examples, setExamples] = useState<FlowType[]>([]);
|
||||
|
||||
// Show community examples on screen
|
||||
function handleExamples() {
|
||||
function handleExamples(): void {
|
||||
setLoadingExamples(true);
|
||||
getExamples()
|
||||
.then((result) => {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ const ConnectionLineComponent = ({
|
|||
toX,
|
||||
toY,
|
||||
connectionLineStyle = {}, // provide a default value for connectionLineStyle
|
||||
}: ConnectionLineComponentProps) => {
|
||||
}: ConnectionLineComponentProps): JSX.Element => {
|
||||
return (
|
||||
<g>
|
||||
<path
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue