Merge branch 'dev' into docs-explain-io-playground

This commit is contained in:
Mendon Kissling 2024-06-07 13:48:43 -04:00
commit 25307e7509
62 changed files with 1276 additions and 962 deletions

View file

@ -80,7 +80,10 @@ jobs:
langflowai/langflow-frontend:1.0-alpha langflowai/langflow-frontend:1.0-alpha
restart-space: restart-space:
name: Restart HuggingFace Spaces
if: ${{ inputs.release_type == 'main' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: docker_build
strategy: strategy:
matrix: matrix:
python-version: python-version:

View file

@ -35,6 +35,10 @@ jobs:
with: with:
python-version: "3.10" python-version: "3.10"
cache: "poetry" cache: "poetry"
- name: Set up Nodejs 20
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Check Version - name: Check Version
id: check-version id: check-version
run: | run: |

View file

@ -168,6 +168,7 @@ build_and_install:
build_frontend: build_frontend:
cd src/frontend && CI='' npm run build cd src/frontend && CI='' npm run build
rm -rf src/backend/base/langflow/frontend
cp -r src/frontend/build src/backend/base/langflow/frontend cp -r src/frontend/build src/backend/base/langflow/frontend
build: build:

View file

@ -1,6 +1,7 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
# Keep this syntax directive! It's used to enable Docker BuildKit # Keep this syntax directive! It's used to enable Docker BuildKit
################################ ################################
# BUILDER-BASE # BUILDER-BASE
# Used to build deps + create our virtual environment # Used to build deps + create our virtual environment
@ -47,12 +48,10 @@ WORKDIR /app
COPY pyproject.toml poetry.lock README.md ./ COPY pyproject.toml poetry.lock README.md ./
COPY src/ ./src COPY src/ ./src
COPY scripts/ ./scripts COPY scripts/ ./scripts
RUN python -m pip install requests --user && cd ./scripts && python update_dependencies.py RUN python -m pip install requests --user && cd ./scripts && python update_dependencies.py
RUN $POETRY_HOME/bin/poetry lock --no-update \ RUN $POETRY_HOME/bin/poetry lock --no-update \
&& $POETRY_HOME/bin/poetry install --no-interaction --no-ansi -E deploy \
&& $POETRY_HOME/bin/poetry build -f wheel \ && $POETRY_HOME/bin/poetry build -f wheel \
&& $POETRY_HOME/bin/poetry run pip install dist/*.whl && $POETRY_HOME/bin/poetry run pip install dist/*.whl --force-reinstall
################################ ################################
# RUNTIME # RUNTIME

View file

@ -10,8 +10,7 @@ Langflow provides an API key functionality that allows users to access their ind
The default user and password are set using the LANGFLOW_SUPERUSER and The default user and password are set using the LANGFLOW_SUPERUSER and
LANGFLOW_SUPERUSER_PASSWORD environment variables. LANGFLOW_SUPERUSER_PASSWORD environment variables.
The default values are The default values are `langflow` and `langflow`, respectively.
langflow and langflow, respectively.
</Admonition> </Admonition>

View file

@ -1,62 +1,51 @@
# Command Line Interface (CLI) # Command Line Interface (CLI)
## Overview
Langflow's Command Line Interface (CLI) is a powerful tool that allows you to interact with the Langflow server from the command line. The CLI provides a wide range of commands to help you shape Langflow to your needs. Langflow's Command Line Interface (CLI) is a powerful tool that allows you to interact with the Langflow server from the command line. The CLI provides a wide range of commands to help you shape Langflow to your needs.
Running the CLI without any arguments will display a list of available commands and options. The available commands are below. Navigate to their individual sections of this page to see the parameters.
* [langflow](#overview)
* [langflow api-key](#langflow-api-key)
* [langflow copy-db](#langflow-copy-db)
* [langflow migration](#langflow-migration)
* [langflow run](#langflow-run)
* [langflow superuser](#langflow-superuser)
## Overview
Running the CLI without any arguments displays a list of available options and commands.
```bash ```bash
python -m langflow run --help langflow
# or # or
python -m langflow run langflow --help
# or
python -m langflow
``` ```
Each option for `run` command are detailed below: | Command | Description |
| ------- | ----------- |
| `api-key` | Creates an API key for the default superuser if AUTO_LOGIN is enabled. |
| `copy-db` | Copy the database files to the current directory (`which langflow`). |
| `migration` | Run or test migrations. |
| `run` | Run the Langflow. |
| `superuser` | Create a superuser. |
- `--help`: Displays all available options. ### Options
- `--host`: Defines the host to bind the server to. Can be set using the `LANGFLOW_HOST` environment variable. The default is `127.0.0.1`.
- `--workers`: Sets the number of worker processes. Can be set using the `LANGFLOW_WORKERS` environment variable. The default is `1`.
- `--timeout`: Sets the worker timeout in seconds. The default is `60`.
- `--port`: Sets the port to listen on. Can be set using the `LANGFLOW_PORT` environment variable. The default is `7860`.
- `--env-file`: Specifies the path to the .env file containing environment variables. The default is `.env`.
- `--log-level`: Defines the logging level. Can be set using the `LANGFLOW_LOG_LEVEL` environment variable. The default is `critical`.
- `--components-path`: Specifies the path to the directory containing custom components. Can be set using the `LANGFLOW_COMPONENTS_PATH` environment variable. The default is `langflow/components`.
- `--log-file`: Specifies the path to the log file. Can be set using the `LANGFLOW_LOG_FILE` environment variable. The default is `logs/langflow.log`.
- `--cache`: Select the type of cache to use. Options are `InMemoryCache` and `SQLiteCache`. Can be set using the `LANGFLOW_LANGCHAIN_CACHE` environment variable. The default is `SQLiteCache`.
- `--dev/--no-dev`: Toggles the development mode. The default is `no-dev`.
- `--path`: Specifies the path to the frontend directory containing build files. This option is for development purposes only. Can be set using the `LANGFLOW_FRONTEND_PATH` environment variable.
- `--open-browser/--no-open-browser`: Toggles the option to open the browser after starting the server. Can be set using the `LANGFLOW_OPEN_BROWSER` environment variable. The default is `open-browser`.
- `--remove-api-keys/--no-remove-api-keys`: Toggles the option to remove API keys from the projects saved in the database. Can be set using the `LANGFLOW_REMOVE_API_KEYS` environment variable. The default is `no-remove-api-keys`.
- `--install-completion [bash|zsh|fish|powershell|pwsh]`: Installs completion for the specified shell.
- `--show-completion [bash|zsh|fish|powershell|pwsh]`: Shows completion for the specified shell, allowing you to copy it or customize the installation.
- `--backend-only`: This parameter, with a default value of `False`, allows running only the backend server without the frontend. It can also be set using the `LANGFLOW_BACKEND_ONLY` environment variable.
- `--store`: This parameter, with a default value of `True`, enables the store features, use `--no-store` to deactivate it. It can be configured using the `LANGFLOW_STORE` environment variable.
These parameters are important for users who need to customize the behavior of Langflow, especially in development or specialized deployment scenarios. | Option | Description |
| ------ | ----------- |
| `--install-completion` | Install completion for the current shell. |
| `--show-completion` | Show completion for the current shell, to copy it or customize the installation. |
| `--help` | Show this message and exit. |
### API Key Command ## langflow api-key
The `api-key` command allows you to create an API key for accessing Langflow's API when `LANGFLOW_AUTO_LOGIN` is set to `True`. Run the `api-key` command to create an API key for the default superuser if `LANGFLOW_AUTO_LOGIN` is set to `True`.
```bash
python -m langflow api-key --help
Usage: langflow api-key [OPTIONS]
Creates an API key for the default superuser if AUTO_LOGIN is enabled.
Args: log_level (str, optional): Logging level. Defaults to "error".
Returns: None
╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ --log-level TEXT Logging level. [env var: LANGFLOW_LOG_LEVEL] [default: error] │
│ --help Show this message and exit. │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
```
Once you run the `api-key` command, it will create an API key for the default superuser if `LANGFLOW_AUTO_LOGIN` is set to `True`.
```bash ```bash
langflow api-key
# or
python -m langflow api-key python -m langflow api-key
╭─────────────────────────────────────────────────────────────────────╮ ╭─────────────────────────────────────────────────────────────────────╮
│ API Key Created Successfully: │ │ API Key Created Successfully: │
@ -67,11 +56,99 @@ python -m langflow api-key
│ Make sure to store it in a secure location. │ │ Make sure to store it in a secure location. │
│ │ │ │
│ The API key has been copied to your clipboard. Cmd + V to paste it. │ │ The API key has been copied to your clipboard. Cmd + V to paste it. │
╰─────────────────────────────────────────────────────────────────────╯ ╰──────────────────────────────
``` ```
### Environment Variables ### Options
| Option | Type | Description |
|------------------|------|-------------------------------------------------------------|
| --log-level | TEXT | Logging level. [env var: LANGFLOW_LOG_LEVEL] [default: error] |
| --help | | Show this message and exit. |
## langflow copy-db
Run the `copy-db` command to copy the cached `langflow.db` and `langflow-pre.db` database files to the current directory.
If the files exist in the cache directory, they will be copied to the same directory as `__main__.py`, which can be found with `which langflow`.
### Options
None.
## langflow migration
Run or test migrations with the [Alembic](https://pypi.org/project/alembic/) database tool.
```bash
langflow migration
# or
python -m langflow migration
```
### Options
| Option | Description |
|-----------------|-------------------------------------------------------------|
| `--test, --no-test` | Run migrations in test mode. [default: test] |
| `--fix, --no-fix` | Fix migrations. This is a destructive operation, and should only be used if you know what you are doing. [default: no-fix] |
| `--help` | Show this message and exit. |
## langflow run
Run Langflow.
```bash
langflow run
# or
python -m langflow run
```
### Options
| Option | Description |
|-------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `--help` | Displays all available options. |
| `--host` | Defines the host to bind the server to. Can be set using the `LANGFLOW_HOST` environment variable. The default is `127.0.0.1`. |
| `--workers` | Sets the number of worker processes. Can be set using the `LANGFLOW_WORKERS` environment variable. The default is `1`. |
| `--timeout` | Sets the worker timeout in seconds. The default is `60`. |
| `--port` | Sets the port to listen on. Can be set using the `LANGFLOW_PORT` environment variable. The default is `7860`. |
| `--env-file` | Specifies the path to the .env file containing environment variables. The default is `.env`. |
| `--log-level` | Defines the logging level. Can be set using the `LANGFLOW_LOG_LEVEL` environment variable. The default is `critical`. |
| `--components-path` | Specifies the path to the directory containing custom components. Can be set using the `LANGFLOW_COMPONENTS_PATH` environment variable. The default is `langflow/components`. |
| `--log-file` | Specifies the path to the log file. Can be set using the `LANGFLOW_LOG_FILE` environment variable. The default is `logs/langflow.log`. |
| `--cache` | Select the type of cache to use. Options are `InMemoryCache` and `SQLiteCache`. Can be set using the `LANGFLOW_LANGCHAIN_CACHE` environment variable. The default is `SQLiteCache`. |
| `--dev`/`--no-dev` | Toggles the development mode. The default is `no-dev`. |
| `--path` | Specifies the path to the frontend directory containing build files. This option is for development purposes only. Can be set using the `LANGFLOW_FRONTEND_PATH` environment variable. |
| `--open-browser`/`--no-open-browser`| Toggles the option to open the browser after starting the server. Can be set using the `LANGFLOW_OPEN_BROWSER` environment variable. The default is `open-browser`. |
| `--remove-api-keys`/`--no-remove-api-keys`| Toggles the option to remove API keys from the projects saved in the database. Can be set using the `LANGFLOW_REMOVE_API_KEYS` environment variable. The default is `no-remove-api-keys`. |
| `--install-completion [bash\|zsh\|fish\|powershell\|pwsh]`| Installs completion for the specified shell. |
| `--show-completion [bash\|zsh\|fish\|powershell\|pwsh]` | Shows completion for the specified shell, allowing you to copy it or customize the installation. |
| `--backend-only` | This parameter, with a default value of `False`, allows running only the backend server without the frontend. It can also be set using the `LANGFLOW_BACKEND_ONLY` environment variable. For more, see [Backend-only](../deployment/backend-only.md).|
| `--store` | This parameter, with a default value of `True`, enables the store features, use `--no-store` to deactivate it. It can be configured using the `LANGFLOW_STORE` environment variable. |
#### Environment Variables
You can configure many of the CLI options using environment variables. These can be exported in your operating system or added to a `.env` file and loaded using the `--env-file` option. You can configure many of the CLI options using environment variables. These can be exported in your operating system or added to a `.env` file and loaded using the `--env-file` option.
A sample `.env` file named `.env.example` is included with the project. Copy this file to a new file named `.env` and replace the example values with your actual settings. If you're setting values in both your OS and the `.env` file, the `.env` settings will take precedence. A sample `.env` file named `.env.example` is included with the project. Copy this file to a new file named `.env` and replace the example values with your actual settings. If you're setting values in both your OS and the `.env` file, the `.env` settings will take precedence.
## langflow superuser
Create a superuser for Langflow.
```bash
langflow superuser
# or
python -m langflow superuser
```
### Options
| Option | Type | Description |
|----------------|-------|-------------------------------------------------------------|
| `--username` | TEXT | Username for the superuser. [default: None] [required] |
| `--password` | TEXT | Password for the superuser. [default: None] [required] |
| `--log-level` | TEXT | Logging level. [env var: LANGFLOW_LOG_LEVEL] [default: error] |
| `--help` | | Show this message and exit. |

View file

@ -10,7 +10,7 @@ Langflow [Discord](https://discord.gg/EqksyE2EX9) server.
--- ---
## 🐦 Stay tunned for **Langflow** on Twitter ## 🐦 Stay tuned for **Langflow** on Twitter
Follow [@langflow_ai](https://twitter.com/langflow_ai) on **Twitter** to get the latest news about **Langflow**. Follow [@langflow_ai](https://twitter.com/langflow_ai) on **Twitter** to get the latest news about **Langflow**.

View file

@ -0,0 +1,113 @@
# Backend-only
You can run Langflow in `--backend-only` mode to expose your Langflow app as an API, without running the frontend UI.
Start langflow in backend-only mode with `python3 -m langflow run --backend-only`.
The terminal prints ` Welcome to ⛓ Langflow `, and a blank window opens at `http://127.0.0.1:7864/all`.
Langflow will now serve requests to its API without the frontend running.
## Prerequisites
* [Langflow installed](../getting-started/install-langflow.mdx)
* [OpenAI API key](https://platform.openai.com)
* [A Langflow flow created](../starter-projects/basic-prompting.mdx)
## Download your flow's curl call
1. Click API.
2. Click **curl** > **Copy code** and save the code to your local machine.
It will look something like this:
```curl
curl -X POST \
"http://127.0.0.1:7864/api/v1/run/ef7e0554-69e5-4e3e-ab29-ee83bcd8d9ef?stream=false" \
-H 'Content-Type: application/json'\
-d '{"input_value": "message",
"output_type": "chat",
"input_type": "chat",
"tweaks": {
"Prompt-kvo86": {},
"OpenAIModel-MilkD": {},
"ChatOutput-ktwdw": {},
"ChatInput-xXC4F": {}
}}'
```
Note the flow ID of `ef7e0554-69e5-4e3e-ab29-ee83bcd8d9ef`. You can find this ID in the UI as well to ensure you're querying the right flow.
## Start Langflow in backend-only mode
1. Stop Langflow with Ctrl+C.
2. Start langflow in backend-only mode with `python3 -m langflow run --backend-only`.
The terminal prints ` Welcome to ⛓ Langflow `, and a blank window opens at `http://127.0.0.1:7864/all`.
Langflow will now serve requests to its API.
3. Run the curl code you copied from the UI.
You should get a result like this:
```bash
{"session_id":"ef7e0554-69e5-4e3e-ab29-ee83bcd8d9ef:bf81d898868ac87e1b4edbd96c131c5dee801ea2971122cc91352d144a45b880","outputs":[{"inputs":{"input_value":"hi, are you there?"},"outputs":[{"results":{"result":"Arrr, ahoy matey! Aye, I be here. What be ye needin', me hearty?"},"artifacts":{"message":"Arrr, ahoy matey! Aye, I be here. What be ye needin', me hearty?","sender":"Machine","sender_name":"AI"},"messages":[{"message":"Arrr, ahoy matey! Aye, I be here. What be ye needin', me hearty?","sender":"Machine","sender_name":"AI","component_id":"ChatOutput-ktwdw"}],"component_display_name":"Chat Output","component_id":"ChatOutput-ktwdw","used_frozen_result":false}]}]}%
```
Again, note that the flow ID matches.
Langflow is receiving your POST request, running the flow, and returning the result, all without running the frontend. Cool!
## Download your flow's Python API call
Instead of using curl, you can download your flow as a Python API call instead.
1. Click API.
2. Click **Python API** > **Copy code** and save the code to your local machine.
The code will look something like this:
```python
import requests
from typing import Optional
BASE_API_URL = "http://127.0.0.1:7864/api/v1/run"
FLOW_ID = "ef7e0554-69e5-4e3e-ab29-ee83bcd8d9ef"
# You can tweak the flow by adding a tweaks dictionary
# e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}}
def run_flow(message: str,
flow_id: str,
output_type: str = "chat",
input_type: str = "chat",
tweaks: Optional[dict] = None,
api_key: Optional[str] = None) -> dict:
"""
Run a flow with a given message and optional tweaks.
:param message: The message to send to the flow
:param flow_id: The ID of the flow to run
:param tweaks: Optional tweaks to customize the flow
:return: The JSON response from the flow
"""
api_url = f"{BASE_API_URL}/{flow_id}"
payload = {
"input_value": message,
"output_type": output_type,
"input_type": input_type,
}
headers = None
if tweaks:
payload["tweaks"] = tweaks
if api_key:
headers = {"x-api-key": api_key}
response = requests.post(api_url, json=payload, headers=headers)
return response.json()
# Setup any tweaks you want to apply to the flow
message = "message"
print(run_flow(message=message, flow_id=FLOW_ID))
```
3. Run your Python app:
```python
python3 app.py
```
The result is similar to the curl call:
```bash
{'session_id': 'ef7e0554-69e5-4e3e-ab29-ee83bcd8d9ef:bf81d898868ac87e1b4edbd96c131c5dee801ea2971122cc91352d144a45b880', 'outputs': [{'inputs': {'input_value': 'message'}, 'outputs': [{'results': {'result': "Arrr matey! What be yer message for this ol' pirate? Speak up or walk the plank!"}, 'artifacts': {'message': "Arrr matey! What be yer message for this ol' pirate? Speak up or walk the plank!", 'sender': 'Machine', 'sender_name': 'AI'}, 'messages': [{'message': "Arrr matey! What be yer message for this ol' pirate? Speak up or walk the plank!", 'sender': 'Machine', 'sender_name': 'AI', 'component_id': 'ChatOutput-ktwdw'}], 'component_display_name': 'Chat Output', 'component_id': 'ChatOutput-ktwdw', 'used_frozen_result': False}]}]}
```
Your Python app POSTs to your Langflow server, and the server runs the flow and returns the result.
See [API](../administration/api.mdx) for more ways to interact with your headless Langflow server.

View file

@ -0,0 +1,65 @@
# Docker
This guide will help you get LangFlow up and running using Docker and Docker Compose.
## Prerequisites
- Docker
- Docker Compose
## Steps
1. Clone the LangFlow repository:
```sh
git clone https://github.com/langflow-ai/langflow.git
```
2. Navigate to the `docker_example` directory:
```sh
cd langflow/docker_example
```
3. Run the Docker Compose file:
```sh
docker compose up
```
LangFlow will now be accessible at [http://localhost:7860/](http://localhost:7860/).
## Docker Compose Configuration
The Docker Compose configuration spins up two services: `langflow` and `postgres`.
### LangFlow Service
The `langflow` service uses the `langflowai/langflow:latest` Docker image and exposes port 7860. It depends on the `postgres` service.
Environment variables:
- `LANGFLOW_DATABASE_URL`: The connection string for the PostgreSQL database.
- `LANGFLOW_CONFIG_DIR`: The directory where LangFlow stores logs, file storage, monitor data, and secret keys.
Volumes:
- `langflow-data`: This volume is mapped to `/var/lib/langflow` in the container.
### PostgreSQL Service
The `postgres` service uses the `postgres:16` Docker image and exposes port 5432.
Environment variables:
- `POSTGRES_USER`: The username for the PostgreSQL database.
- `POSTGRES_PASSWORD`: The password for the PostgreSQL database.
- `POSTGRES_DB`: The name of the PostgreSQL database.
Volumes:
- `langflow-postgres`: This volume is mapped to `/var/lib/postgresql/data` in the container.
## Switching to a Specific LangFlow Version
If you want to use a specific version of LangFlow, you can modify the `image` field under the `langflow` service in the Docker Compose file. For example, to use version 1.0-alpha, change `langflowai/langflow:latest` to `langflowai/langflow:1.0-alpha`.

View file

@ -113,7 +113,10 @@ module.exports = {
type: "category", type: "category",
label: "Deployment", label: "Deployment",
collapsed: true, collapsed: true,
items: ["deployment/gcp-deployment"], items: ["deployment/docker",
"deployment/backend-only",
"deployment/gcp-deployment",
],
}, },
{ {
type: "category", type: "category",

View file

@ -1,3 +1,3 @@
<svg width="18" height="18" viewBox="0 0 24 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="1200" height="1227" viewBox="0 0 1200 1227" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M24 2.36764C23.1181 2.76923 22.1687 3.04081 21.1728 3.16215C22.1898 2.5381 22.9703 1.54857 23.338 0.369812C22.3856 0.947636 21.3334 1.368 20.2092 1.59334C19.3133 0.612492 18.0328 0 16.6156 0C13.8983 0 11.6936 2.26074 11.6936 5.04874C11.6936 5.44456 11.7359 5.82881 11.8204 6.19862C7.72812 5.98771 4.10072 3.97977 1.67071 0.921629C1.24669 1.66992 1.00439 2.5381 1.00439 3.46262C1.00439 5.21343 1.87357 6.75911 3.19493 7.66485C2.38915 7.64029 1.62845 7.41061 0.963547 7.03502V7.09713C0.963547 9.54422 2.66102 11.5854 4.91495 12.0476C4.5022 12.1661 4.06691 12.2253 3.61754 12.2253C3.30058 12.2253 2.99066 12.195 2.69062 12.1358C3.31748 14.1408 5.1347 15.6013 7.29001 15.6403C5.6052 16.9953 3.48089 17.8028 1.17485 17.8028C0.777598 17.8028 0.384575 17.7796 0 17.7334C2.17926 19.1636 4.76844 20 7.54781 20C16.6057 20 21.5573 12.3077 21.5573 5.63524C21.5573 5.41566 21.5531 5.19609 21.5447 4.98084C22.5067 4.26868 23.3422 3.38027 24 2.36764Z" fill="#00AAEC"/> <path d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z" fill="white"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 430 B

Before After
Before After

1100
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "langflow" name = "langflow"
version = "1.0.0a43" version = "1.0.0a49"
description = "A Python package with a built-in web application" description = "A Python package with a built-in web application"
authors = ["Langflow <contact@langflow.org>"] authors = ["Langflow <contact@langflow.org>"]
maintainers = [ maintainers = [
@ -66,7 +66,7 @@ qianfan = "0.3.5"
pgvector = "^0.2.3" pgvector = "^0.2.3"
pyautogen = "^0.2.0" pyautogen = "^0.2.0"
langchain-google-genai = "^1.0.1" langchain-google-genai = "^1.0.1"
langchain-cohere = "^0.1.0rc1" langchain-cohere = "^0.1.5"
elasticsearch = "^8.12.0" elasticsearch = "^8.12.0"
pytube = "^15.0.0" pytube = "^15.0.0"
dspy-ai = "^2.4.0" dspy-ai = "^2.4.0"
@ -81,7 +81,7 @@ langchain-google-vertexai = "^1.0.3"
langchain-groq = "^0.1.3" langchain-groq = "^0.1.3"
langchain-pinecone = "^0.1.0" langchain-pinecone = "^0.1.0"
langchain-mistralai = "^0.1.6" langchain-mistralai = "^0.1.6"
couchbase = "^4.2.1" couchbase = { extras = ["couchbase"], version = "^4.2.1", optional = true }
youtube-transcript-api = "^0.6.2" youtube-transcript-api = "^0.6.2"
markdown = "^3.6" markdown = "^3.6"
langchain-chroma = "^0.1.1" langchain-chroma = "^0.1.1"
@ -118,6 +118,7 @@ vulture = "^2.11"
[tool.poetry.extras] [tool.poetry.extras]
deploy = ["celery", "redis", "flower"] deploy = ["celery", "redis", "flower"]
couchbase = ["couchbase"]
local = ["llama-cpp-python", "sentence-transformers", "ctransformers"] local = ["llama-cpp-python", "sentence-transformers", "ctransformers"]
@ -140,7 +141,7 @@ testpaths = ["tests", "integration"]
console_output_style = "progress" console_output_style = "progress"
filterwarnings = ["ignore::DeprecationWarning"] filterwarnings = ["ignore::DeprecationWarning"]
log_cli = true log_cli = true
markers = ["async_test"] markers = ["async_test", "api_key_required"]
[tool.ruff] [tool.ruff]

View file

@ -1,27 +0,0 @@
from .AstraDBSearch import AstraDBSearchComponent
from .ChromaSearch import ChromaSearchComponent
from .FAISSSearch import FAISSSearchComponent
from .MongoDBAtlasVectorSearch import MongoDBAtlasSearchComponent
from .PineconeSearch import PineconeSearchComponent
from .QdrantSearch import QdrantSearchComponent
from .RedisSearch import RedisSearchComponent
from .SupabaseVectorStoreSearch import SupabaseSearchComponent
from .VectaraSearch import VectaraSearchComponent
from .WeaviateSearch import WeaviateSearchVectorStore
from .pgvectorSearch import PGVectorSearchComponent
from .Couchbase import CouchbaseSearchComponent # type: ignore
__all__ = [
"AstraDBSearchComponent",
"ChromaSearchComponent",
"CouchbaseSearchComponent",
"FAISSSearchComponent",
"MongoDBAtlasSearchComponent",
"PineconeSearchComponent",
"QdrantSearchComponent",
"RedisSearchComponent",
"SupabaseSearchComponent",
"VectaraSearchComponent",
"WeaviateSearchVectorStore",
"PGVectorSearchComponent",
]

View file

@ -1,28 +0,0 @@
from .AstraDB import AstraDBVectorStoreComponent
from .Chroma import ChromaComponent
from .FAISS import FAISSComponent
from .MongoDBAtlasVector import MongoDBAtlasComponent
from .Pinecone import PineconeComponent
from .Qdrant import QdrantComponent
from .Redis import RedisComponent
from .SupabaseVectorStore import SupabaseComponent
from .Vectara import VectaraComponent
from .Weaviate import WeaviateVectorStoreComponent
from .pgvector import PGVectorComponent
from .Couchbase import CouchbaseComponent
__all__ = [
"AstraDBVectorStoreComponent",
"ChromaComponent",
"CouchbaseComponent",
"FAISSComponent",
"MongoDBAtlasComponent",
"PineconeComponent",
"QdrantComponent",
"RedisComponent",
"SupabaseComponent",
"VectaraComponent",
"WeaviateVectorStoreComponent",
"base",
"PGVectorComponent",
]

View file

@ -297,7 +297,7 @@ class CodeParser:
bases = self.execute_and_inspect_classes(self.code) bases = self.execute_and_inspect_classes(self.code)
except Exception as e: except Exception as e:
# If the code cannot be executed, return an empty list # If the code cannot be executed, return an empty list
logger.exception(e) logger.debug(e)
bases = [] bases = []
raise e raise e
return bases return bases

View file

@ -78,7 +78,8 @@ class DirectoryReader:
component_tuple = (*build_component(component), component) component_tuple = (*build_component(component), component)
components.append(component_tuple) components.append(component_tuple)
except Exception as e: except Exception as e:
logger.error(f"Error while loading component { component['name']}: {e}") logger.debug(f"Error while loading component { component['name']}")
logger.debug(e)
continue continue
items.append({"name": menu["name"], "path": menu["path"], "components": components}) items.append({"name": menu["name"], "path": menu["path"], "components": components})
filtered = [menu for menu in items if menu["components"]] filtered = [menu for menu in items if menu["components"]]
@ -266,8 +267,7 @@ class DirectoryReader:
if validation_result: if validation_result:
try: try:
output_types = self.get_output_types_from_code(result_content) output_types = self.get_output_types_from_code(result_content)
except Exception as exc: except Exception:
logger.exception(f"Error while getting output types from code: {str(exc)}")
output_types = [component_name_camelcase] output_types = [component_name_camelcase]
else: else:
output_types = [component_name_camelcase] output_types = [component_name_camelcase]

View file

@ -251,7 +251,7 @@ def get_flow_by_id_or_endpoint_name(
flow = db.get(Flow, flow_id) flow = db.get(Flow, flow_id)
except ValueError: except ValueError:
endpoint_name = flow_id_or_name endpoint_name = flow_id_or_name
stmt = select(Flow).where(Flow.name == endpoint_name) stmt = select(Flow).where(Flow.endpoint_name == endpoint_name)
if user_id: if user_id:
stmt = stmt.where(Flow.user_id == user_id) stmt = stmt.where(Flow.user_id == user_id)
flow = db.exec(stmt).first() flow = db.exec(stmt).first()

View file

@ -1159,13 +1159,13 @@ files = [
[[package]] [[package]]
name = "langchain" name = "langchain"
version = "0.2.2" version = "0.2.3"
description = "Building applications with LLMs through composability" description = "Building applications with LLMs through composability"
optional = false optional = false
python-versions = "<4.0,>=3.8.1" python-versions = "<4.0,>=3.8.1"
files = [ files = [
{file = "langchain-0.2.2-py3-none-any.whl", hash = "sha256:58ca0c47bcdd156da66f50a0a4fcedc49bf6950827f4a6b06c8c4842d55805f3"}, {file = "langchain-0.2.3-py3-none-any.whl", hash = "sha256:5dc33cd9c8008693d328b7cb698df69073acecc89ad9c2a95f243b3314f8d834"},
{file = "langchain-0.2.2.tar.gz", hash = "sha256:9d61e50e9cdc2bea659bc5e6c03650ba048fda63a307490ae368e539f61a0d3a"}, {file = "langchain-0.2.3.tar.gz", hash = "sha256:81962cc72cce6515f7bd71e01542727870789bf8b666c6913d85559080c1a201"},
] ]
[package.dependencies] [package.dependencies]
@ -1181,29 +1181,15 @@ requests = ">=2,<3"
SQLAlchemy = ">=1.4,<3" SQLAlchemy = ">=1.4,<3"
tenacity = ">=8.1.0,<9.0.0" tenacity = ">=8.1.0,<9.0.0"
[package.extras]
azure = ["azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-textanalytics (>=5.3.0,<6.0.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-core (>=1.26.4,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "azure-search-documents (==11.4.0b8)", "openai (<2)"]
clarifai = ["clarifai (>=9.1.0)"]
cli = ["typer (>=0.9.0,<0.10.0)"]
cohere = ["cohere (>=4,<6)"]
docarray = ["docarray[hnswlib] (>=0.32.0,<0.33.0)"]
embeddings = ["sentence-transformers (>=2,<3)"]
extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cohere (>=4,<6)", "couchbase (>=4.1.9,<5.0.0)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "langchain-openai (>=0.1,<0.2)", "lxml (>=4.9.3,<6.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "rdflib (==7.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"]
javascript = ["esprima (>=4.0.1,<5.0.0)"]
llms = ["clarifai (>=9.1.0)", "cohere (>=4,<6)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (<2)", "openlm (>=0.0.5,<0.0.6)", "torch (>=1,<3)", "transformers (>=4,<5)"]
openai = ["openai (<2)", "tiktoken (>=0.7,<1.0)"]
qdrant = ["qdrant-client (>=1.3.1,<2.0.0)"]
text-helpers = ["chardet (>=5.1.0,<6.0.0)"]
[[package]] [[package]]
name = "langchain-community" name = "langchain-community"
version = "0.2.3" version = "0.2.4"
description = "Community contributed LangChain integrations." description = "Community contributed LangChain integrations."
optional = false optional = false
python-versions = "<4.0,>=3.8.1" python-versions = "<4.0,>=3.8.1"
files = [ files = [
{file = "langchain_community-0.2.3-py3-none-any.whl", hash = "sha256:aa895545be2f3f4aa2fea36f6da2e3b4ec50ce61ec986e8f146901a1e9138138"}, {file = "langchain_community-0.2.4-py3-none-any.whl", hash = "sha256:8582e9800f4837660dc297cccd2ee1ddc1d8c440d0fe8b64edb07620f0373b0e"},
{file = "langchain_community-0.2.3.tar.gz", hash = "sha256:a3c35af215e47b700e7cb4e548fa8b45c6d46d52b5a5a65af2577c5a0104fc9f"}, {file = "langchain_community-0.2.4.tar.gz", hash = "sha256:2bb6a1a36b8500a564d25d76469c02457b1a7c3afea6d4a609a47c06b993e3e4"},
] ]
[package.dependencies] [package.dependencies]
@ -1218,19 +1204,15 @@ requests = ">=2,<3"
SQLAlchemy = ">=1.4,<3" SQLAlchemy = ">=1.4,<3"
tenacity = ">=8.1.0,<9.0.0" tenacity = ">=8.1.0,<9.0.0"
[package.extras]
cli = ["typer (>=0.9.0,<0.10.0)"]
extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "azure-ai-documentintelligence (>=1.0.0b1,<2.0.0)", "azure-identity (>=1.15.0,<2.0.0)", "azure-search-documents (==11.4.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.6,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cloudpathlib (>=0.18,<0.19)", "cloudpickle (>=2.0.0)", "cohere (>=4,<5)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "elasticsearch (>=8.12.0,<9.0.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "friendli-client (>=1.2.4,<2.0.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "gradientai (>=1.4.0,<2.0.0)", "hdbcli (>=2.19.21,<3.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "httpx (>=0.24.1,<0.25.0)", "httpx-sse (>=0.4.0,<0.5.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "lxml (>=4.9.3,<6.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "nvidia-riva-client (>=2.14.0,<3.0.0)", "oci (>=2.119.1,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "oracle-ads (>=2.9.1,<3.0.0)", "oracledb (>=2.2.0,<3.0.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "premai (>=0.3.25,<0.4.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pyjwt (>=2.8.0,<3.0.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "rdflib (==7.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "simsimd (>=4.3.1,<5.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "tidb-vector (>=0.0.3,<1.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "tree-sitter (>=0.20.2,<0.21.0)", "tree-sitter-languages (>=1.8.0,<2.0.0)", "upstash-redis (>=0.15.0,<0.16.0)", "vdms (>=0.0.20,<0.0.21)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"]
[[package]] [[package]]
name = "langchain-core" name = "langchain-core"
version = "0.2.4" version = "0.2.5"
description = "Building applications with LLMs through composability" description = "Building applications with LLMs through composability"
optional = false optional = false
python-versions = "<4.0,>=3.8.1" python-versions = "<4.0,>=3.8.1"
files = [ files = [
{file = "langchain_core-0.2.4-py3-none-any.whl", hash = "sha256:5212f7ec78a525e88a178ed3aefe2fd7134b03fb92573dfbab9914f1d92d6ec5"}, {file = "langchain_core-0.2.5-py3-none-any.whl", hash = "sha256:abe5138f22acff23a079ec538be5268bbf97cf023d51987a0dd474d2a16cae3e"},
{file = "langchain_core-0.2.4.tar.gz", hash = "sha256:82bdcc546eb0341cefcf1f4ecb3e49836fff003903afddda2d1312bb8491ef81"}, {file = "langchain_core-0.2.5.tar.gz", hash = "sha256:4a5c2f56b22396a63ef4790043660e393adbfa6832b978f023ca996a04b8e752"},
] ]
[package.dependencies] [package.dependencies]
@ -1241,9 +1223,6 @@ pydantic = ">=1,<3"
PyYAML = ">=5.3" PyYAML = ">=5.3"
tenacity = ">=8.1.0,<9.0.0" tenacity = ">=8.1.0,<9.0.0"
[package.extras]
extended-testing = ["jinja2 (>=3,<4)"]
[[package]] [[package]]
name = "langchain-experimental" name = "langchain-experimental"
version = "0.0.60" version = "0.0.60"
@ -1296,13 +1275,13 @@ types-requests = ">=2.31.0.2,<3.0.0.0"
[[package]] [[package]]
name = "langsmith" name = "langsmith"
version = "0.1.72" version = "0.1.75"
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
optional = false optional = false
python-versions = "<4.0,>=3.8.1" python-versions = "<4.0,>=3.8.1"
files = [ files = [
{file = "langsmith-0.1.72-py3-none-any.whl", hash = "sha256:a4456707669521bd75b7431b9205a6b99579fb9ff01bd338f52d29df11a7662d"}, {file = "langsmith-0.1.75-py3-none-any.whl", hash = "sha256:d08b08dd6b3fa4da170377f95123d77122ef4c52999d10fff4ae08ff70d07aed"},
{file = "langsmith-0.1.72.tar.gz", hash = "sha256:262ae9e8aceaba50f3a0f5b6eb559d6110886f0afc6b0ed5270e7d3d3f1fd8d6"}, {file = "langsmith-0.1.75.tar.gz", hash = "sha256:61274e144ea94c297dd78ce03e6dfae18459fe9bd8ab5094d61a0c4816561279"},
] ]
[package.dependencies] [package.dependencies]
@ -1600,13 +1579,13 @@ files = [
[[package]] [[package]]
name = "marshmallow" name = "marshmallow"
version = "3.21.2" version = "3.21.3"
description = "A lightweight library for converting complex datatypes to and from native Python datatypes." description = "A lightweight library for converting complex datatypes to and from native Python datatypes."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "marshmallow-3.21.2-py3-none-any.whl", hash = "sha256:70b54a6282f4704d12c0a41599682c5c5450e843b9ec406308653b47c59648a1"}, {file = "marshmallow-3.21.3-py3-none-any.whl", hash = "sha256:86ce7fb914aa865001a4b2092c4c2872d13bc347f3d42673272cabfdbad386f1"},
{file = "marshmallow-3.21.2.tar.gz", hash = "sha256:82408deadd8b33d56338d2182d455db632c6313aa2af61916672146bb32edc56"}, {file = "marshmallow-3.21.3.tar.gz", hash = "sha256:4f57c5e050a54d66361e826f94fba213eb10b67b2fdb02c3e0343ce207ba1662"},
] ]
[package.dependencies] [package.dependencies]

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "langflow-base" name = "langflow-base"
version = "0.0.57" version = "0.0.60"
description = "A Python package with a built-in web application" description = "A Python package with a built-in web application"
authors = ["Langflow <contact@langflow.org>"] authors = ["Langflow <contact@langflow.org>"]
maintainers = [ maintainers = [

View file

@ -41,7 +41,6 @@
"class-variance-authority": "^0.6.1", "class-variance-authority": "^0.6.1",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"cmdk": "^1.0.0", "cmdk": "^1.0.0",
"debounce-promise": "^3.1.2",
"dompurify": "^3.0.5", "dompurify": "^3.0.5",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"emoji-regex": "^10.3.0", "emoji-regex": "^10.3.0",
@ -53,6 +52,7 @@
"million": "^3.0.6", "million": "^3.0.6",
"moment": "^2.29.4", "moment": "^2.29.4",
"openseadragon": "^4.1.1", "openseadragon": "^4.1.1",
"p-debounce": "^4.0.0",
"playwright": "^1.42.0", "playwright": "^1.42.0",
"react": "^18.2.21", "react": "^18.2.21",
"react-ace": "^10.1.0", "react-ace": "^10.1.0",
@ -480,7 +480,6 @@
}, },
"node_modules/@clack/prompts/node_modules/is-unicode-supported": { "node_modules/@clack/prompts/node_modules/is-unicode-supported": {
"version": "1.3.0", "version": "1.3.0",
"extraneous": true,
"inBundle": true, "inBundle": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -5699,11 +5698,6 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/debounce-promise": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/debounce-promise/-/debounce-promise-3.1.2.tgz",
"integrity": "sha512-rZHcgBkbYavBeD9ej6sP56XfG53d51CD4dnaw989YX/nZ/ZJfgRx/9ePKmTNiUiyQvh4mtrMoS3OAWW+yoYtpg=="
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.4", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@ -10051,6 +10045,15 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/p-debounce": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/p-debounce/-/p-debounce-4.0.0.tgz",
"integrity": "sha512-4Ispi9I9qYGO4lueiLDhe4q4iK5ERK8reLsuzH6BPaXn53EGaua8H66PXIFGrW897hwjXp+pVLrm/DLxN0RF0A==",
"license": "MIT",
"engines": {
"node": ">=12"
}
},
"node_modules/p-finally": { "node_modules/p-finally": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",

View file

@ -36,7 +36,6 @@
"class-variance-authority": "^0.6.1", "class-variance-authority": "^0.6.1",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"cmdk": "^1.0.0", "cmdk": "^1.0.0",
"debounce-promise": "^3.1.2",
"dompurify": "^3.0.5", "dompurify": "^3.0.5",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"emoji-regex": "^10.3.0", "emoji-regex": "^10.3.0",
@ -48,6 +47,7 @@
"million": "^3.0.6", "million": "^3.0.6",
"moment": "^2.29.4", "moment": "^2.29.4",
"openseadragon": "^4.1.1", "openseadragon": "^4.1.1",
"p-debounce": "^4.0.0",
"playwright": "^1.42.0", "playwright": "^1.42.0",
"react": "^18.2.21", "react": "^18.2.21",
"react-ace": "^10.1.0", "react-ace": "^10.1.0",

View file

@ -1,5 +1,5 @@
import { cloneDeep } from "lodash";
import emojiRegex from "emoji-regex"; import emojiRegex from "emoji-regex";
import { cloneDeep } from "lodash";
import { useCallback, useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { NodeToolbar, useUpdateNodeInternals } from "reactflow"; import { NodeToolbar, useUpdateNodeInternals } from "reactflow";
import IconComponent from "../../components/genericIconComponent"; import IconComponent from "../../components/genericIconComponent";
@ -56,14 +56,14 @@ export default function GenericNode({
const [nodeName, setNodeName] = useState(data.node!.display_name); const [nodeName, setNodeName] = useState(data.node!.display_name);
const [inputDescription, setInputDescription] = useState(false); const [inputDescription, setInputDescription] = useState(false);
const [nodeDescription, setNodeDescription] = useState( const [nodeDescription, setNodeDescription] = useState(
data.node?.description! data.node?.description!,
); );
const [isOutdated, setIsOutdated] = useState(false); const [isOutdated, setIsOutdated] = useState(false);
const buildStatus = useFlowStore( const buildStatus = useFlowStore(
(state) => state.flowBuildStatus[data.id]?.status (state) => state.flowBuildStatus[data.id]?.status,
); );
const lastRunTime = useFlowStore( const lastRunTime = useFlowStore(
(state) => state.flowBuildStatus[data.id]?.timestamp (state) => state.flowBuildStatus[data.id]?.timestamp,
); );
const [validationStatus, setValidationStatus] = const [validationStatus, setValidationStatus] =
useState<validationStatusType | null>(null); useState<validationStatusType | null>(null);
@ -116,7 +116,7 @@ export default function GenericNode({
updateNodeInternals(data.id); updateNodeInternals(data.id);
}, },
[data.id, data.node, setNode, setIsOutdated] [data.id, data.node, setNode, setIsOutdated],
); );
if (!data.node!.template) { if (!data.node!.template) {
@ -255,8 +255,17 @@ export default function GenericNode({
const isDark = useDarkStore((state) => state.dark); const isDark = useDarkStore((state) => state.dark);
const renderIconStatus = ( const renderIconStatus = (
buildStatus: BuildStatus | undefined, buildStatus: BuildStatus | undefined,
validationStatus: validationStatusType | null validationStatus: validationStatusType | null,
) => { ) => {
const conditionSuccess = validationStatus && validationStatus.valid;
const conditionInactive =
validationStatus &&
!validationStatus.valid &&
buildStatus === BuildStatus.INACTIVE;
const conditionError =
buildStatus === BuildStatus.ERROR ||
(validationStatus && !validationStatus.valid);
if (buildStatus === BuildStatus.BUILDING) { if (buildStatus === BuildStatus.BUILDING) {
return <Loading className="text-medium-indigo" />; return <Loading className="text-medium-indigo" />;
} else { } else {
@ -264,31 +273,30 @@ export default function GenericNode({
<> <>
<IconComponent <IconComponent
name="Play" name="Play"
className="absolute ml-0.5 h-5 fill-current stroke-2 text-medium-indigo opacity-0 transition-all group-hover:opacity-100" className={cn(
!conditionSuccess && !conditionInactive && !conditionError
? "opacity-100"
: "opacity-0",
"absolute ml-0.5 h-5 fill-current stroke-2 text-muted-foreground transition-all group-hover:text-medium-indigo group-hover/node:opacity-100",
)}
/> />
{validationStatus && validationStatus.valid ? ( {conditionSuccess ? (
<Checkmark <Checkmark
className="absolute ml-0.5 h-5 stroke-2 text-status-green opacity-100 transition-all group-hover:opacity-0" className="absolute ml-0.5 h-5 stroke-2 text-status-green opacity-100 transition-all group-hover/node:opacity-0"
isVisible={true} isVisible={true}
/> />
) : validationStatus && ) : conditionInactive ? (
!validationStatus.valid &&
buildStatus === BuildStatus.INACTIVE ? (
<IconComponent <IconComponent
name="Play" name="Play"
className="absolute ml-0.5 h-5 fill-current stroke-2 text-status-green opacity-30 transition-all group-hover:opacity-0" className="absolute ml-0.5 h-5 fill-current stroke-2 text-status-gray opacity-30 transition-all group-hover/node:opacity-0"
/> />
) : buildStatus === BuildStatus.ERROR || ) : conditionError ? (
(validationStatus && !validationStatus.valid) ? (
<Xmark <Xmark
isVisible={true} isVisible={true}
className="absolute ml-0.5 h-5 fill-current stroke-2 text-status-red opacity-100 transition-all group-hover:opacity-0" className="absolute ml-0.5 h-5 fill-current stroke-2 text-status-red opacity-100 transition-all group-hover/node:opacity-0"
/> />
) : ( ) : (
<IconComponent <></>
name="Play"
className="absolute ml-0.5 h-5 fill-current stroke-2 text-muted-foreground opacity-100 transition-all group-hover:opacity-0"
/>
)} )}
</> </>
); );
@ -296,7 +304,7 @@ export default function GenericNode({
}; };
const getSpecificClassFromBuildStatus = ( const getSpecificClassFromBuildStatus = (
buildStatus: BuildStatus | undefined, buildStatus: BuildStatus | undefined,
validationStatus: validationStatusType | null validationStatus: validationStatusType | null,
) => { ) => {
let isInvalid = validationStatus && !validationStatus.valid; let isInvalid = validationStatus && !validationStatus.valid;
@ -320,11 +328,11 @@ export default function GenericNode({
selected: boolean, selected: boolean,
showNode: boolean, showNode: boolean,
buildStatus: BuildStatus | undefined, buildStatus: BuildStatus | undefined,
validationStatus: validationStatusType | null validationStatus: validationStatusType | null,
) => { ) => {
const specificClassFromBuildStatus = getSpecificClassFromBuildStatus( const specificClassFromBuildStatus = getSpecificClassFromBuildStatus(
buildStatus, buildStatus,
validationStatus validationStatus,
); );
const baseBorderClass = getBaseBorderClass(selected); const baseBorderClass = getBaseBorderClass(selected);
@ -332,8 +340,8 @@ export default function GenericNode({
const names = classNames( const names = classNames(
baseBorderClass, baseBorderClass,
nodeSizeClass, nodeSizeClass,
"generic-node-div", "generic-node-div group/node",
specificClassFromBuildStatus specificClassFromBuildStatus,
); );
return names; return names;
}; };
@ -393,7 +401,7 @@ export default function GenericNode({
selected, selected,
showNode, showNode,
buildStatus, buildStatus,
validationStatus validationStatus,
)} )}
> >
{data.node?.beta && showNode && ( {data.node?.beta && showNode && (
@ -524,7 +532,7 @@ export default function GenericNode({
} }
title={getFieldTitle( title={getFieldTitle(
data.node?.template!, data.node?.template!,
templateField templateField,
)} )}
info={data.node?.template[templateField].info} info={data.node?.template[templateField].info}
name={templateField} name={templateField}
@ -552,7 +560,7 @@ export default function GenericNode({
proxy={data.node?.template[templateField].proxy} proxy={data.node?.template[templateField].proxy}
showNode={showNode} showNode={showNode}
/> />
) ),
)} )}
<ParameterComponent <ParameterComponent
key={scapedJSONStringfy({ key={scapedJSONStringfy({
@ -709,7 +717,7 @@ export default function GenericNode({
!data.node?.description) && !data.node?.description) &&
nameEditable nameEditable
? "font-light italic" ? "font-light italic"
: "" : "",
)} )}
onClick={(e) => { onClick={(e) => {
setInputDescription(true); setInputDescription(true);
@ -771,13 +779,13 @@ export default function GenericNode({
} }
title={getFieldTitle( title={getFieldTitle(
data.node?.template!, data.node?.template!,
templateField templateField,
)} )}
info={data.node?.template[templateField].info} info={data.node?.template[templateField].info}
name={templateField} name={templateField}
tooltipTitle={ tooltipTitle={
data.node?.template[templateField].input_types?.join( data.node?.template[templateField].input_types?.join(
"\n" "\n",
) ?? data.node?.template[templateField].type ) ?? data.node?.template[templateField].type
} }
required={data.node!.template[templateField].required} required={data.node!.template[templateField].required}
@ -804,7 +812,7 @@ export default function GenericNode({
<div <div
className={classNames( className={classNames(
Object.keys(data.node!.template).length < 1 ? "hidden" : "", Object.keys(data.node!.template).length < 1 ? "hidden" : "",
"flex-max-width justify-center" "flex-max-width justify-center",
)} )}
> >
{" "} {" "}

View file

@ -51,13 +51,15 @@ export default function ErrorAlert({
/> />
</div> </div>
<div className="ml-3"> <div className="ml-3">
<h3 className="error-build-foreground">{title}</h3> <h3 className="error-build-foreground line-clamp-2">{title}</h3>
{list?.length !== 0 && {list?.length !== 0 &&
list?.some((item) => item !== null && item !== undefined) ? ( list?.some((item) => item !== null && item !== undefined) ? (
<div className="error-build-message-div"> <div className="error-build-message-div">
<ul className="error-build-message-list"> <ul className="error-build-message-list">
{list.map((item, index) => ( {list.map((item, index) => (
<li key={index}>{item}</li> <li key={index} className="line-clamp-5">
{item}
</li>
))} ))}
</ul> </ul>
</div> </div>

View file

@ -47,7 +47,7 @@ export default function NoticeAlert({
/> />
</div> </div>
<div className="ml-3 flex-1 md:flex md:justify-between"> <div className="ml-3 flex-1 md:flex md:justify-between">
<p className="text-sm text-info-foreground word-break-break-word"> <p className="line-clamp-2 text-sm text-info-foreground word-break-break-word">
{title} {title}
</p> </p>
<p className="mt-3 text-sm md:ml-6 md:mt-0"> <p className="mt-3 text-sm md:ml-6 md:mt-0">

View file

@ -45,7 +45,7 @@ export default function SuccessAlert({
/> />
</div> </div>
<div className="ml-3"> <div className="ml-3">
<p className="success-alert-message">{title}</p> <p className="success-alert-message line-clamp-2">{title}</p>
</div> </div>
</div> </div>
</div> </div>

View file

@ -56,7 +56,7 @@ export default function Header(): JSX.Element {
const lastFlowVisitedIndex = routeHistory const lastFlowVisitedIndex = routeHistory
.reverse() .reverse()
.findIndex( .findIndex(
(path) => path.includes("/flow/") && path !== location.pathname (path) => path.includes("/flow/") && path !== location.pathname,
); );
const lastFlowVisited = routeHistory[lastFlowVisitedIndex]; const lastFlowVisited = routeHistory[lastFlowVisitedIndex];
@ -81,14 +81,16 @@ export default function Header(): JSX.Element {
<span className="ml-4 text-2xl">⛓️</span> <span className="ml-4 text-2xl">⛓️</span>
</Link> </Link>
{showArrowReturnIcon && ( {showArrowReturnIcon && (
<button <Button
variant="none"
size="none"
onClick={() => { onClick={() => {
checkForChanges(); checkForChanges();
redirectToLastLocation(); redirectToLastLocation();
}} }}
> >
<IconComponent name="ChevronLeft" className="w-4" /> <IconComponent name="ChevronLeft" className="w-4" />
</button> </Button>
)} )}
<MenuBar /> <MenuBar />
@ -186,7 +188,9 @@ export default function Header(): JSX.Element {
<Separator orientation="vertical" /> <Separator orientation="vertical" />
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<button <Button
variant="none"
size="none"
data-testid="user-profile-settings" data-testid="user-profile-settings"
className={ className={
"h-7 w-7 rounded-full focus-visible:outline-0 " + "h-7 w-7 rounded-full focus-visible:outline-0 " +
@ -200,6 +204,28 @@ export default function Header(): JSX.Element {
/> />
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent>
{!autoLogin && (
<>
<DropdownMenuLabel>
<div className="flex items-center gap-3">
<div
className={
"h-5 w-5 rounded-full focus-visible:outline-0 " +
(userData?.profile_image ??
(userData?.id
? gradients[
parseInt(userData?.id ?? "", 30) %
gradients.length
]
: "bg-gray-500"))
}
/>
{userData?.username ?? "User"}
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
</>
)}
<DropdownMenuLabel>General</DropdownMenuLabel> <DropdownMenuLabel>General</DropdownMenuLabel>
<DropdownMenuItem <DropdownMenuItem
className="cursor-pointer" className="cursor-pointer"

View file

@ -13,6 +13,7 @@ import IconComponent, {
import { Button, buttonVariants } from "../../../ui/button"; import { Button, buttonVariants } from "../../../ui/button";
import { Input } from "../../../ui/input"; import { Input } from "../../../ui/input";
import useFileDrop from "../../hooks/use-on-file-drop"; import useFileDrop from "../../hooks/use-on-file-drop";
import useAlertStore from "../../../../stores/alertStore";
type SideBarFoldersButtonsComponentProps = { type SideBarFoldersButtonsComponentProps = {
folders: FolderType[]; folders: FolderType[];
@ -33,7 +34,7 @@ const SideBarFoldersButtonsComponent = ({
const [foldersNames, setFoldersNames] = useState({}); const [foldersNames, setFoldersNames] = useState({});
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot); const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
const [editFolders, setEditFolderName] = useState( const [editFolders, setEditFolderName] = useState(
folders.map((obj) => ({ name: obj.name, edit: false })) folders.map((obj) => ({ name: obj.name, edit: false })),
); );
const uploadFolder = useFolderStore((state) => state.uploadFolder); const uploadFolder = useFolderStore((state) => state.uploadFolder);
const currentFolder = pathname.split("/"); const currentFolder = pathname.split("/");
@ -51,6 +52,7 @@ const SideBarFoldersButtonsComponent = ({
const location = useLocation(); const location = useLocation();
const folderId = location?.state?.folderId ?? myCollectionId; const folderId = location?.state?.folderId ?? myCollectionId;
const getFolderById = useFolderStore((state) => state.getFolderById); const getFolderById = useFolderStore((state) => state.getFolderById);
const setErrorData = useAlertStore((state) => state.setErrorData);
const handleFolderChange = (folderId: string) => { const handleFolderChange = (folderId: string) => {
getFolderById(folderId); getFolderById(folderId);
@ -58,11 +60,21 @@ const SideBarFoldersButtonsComponent = ({
const { dragOver, dragEnter, dragLeave, onDrop } = useFileDrop( const { dragOver, dragEnter, dragLeave, onDrop } = useFileDrop(
folderId, folderId,
handleFolderChange handleFolderChange,
); );
const handleUploadFlowsToFolder = () => { const handleUploadFlowsToFolder = () => {
uploadFolder(folderId); uploadFolder(folderId)
.then(() => {
getFolderById(folderId);
})
.catch((err) => {
console.log(err);
setErrorData({
title: `Error on upload`,
list: [err["response"]["data"]],
});
});
}; };
const handleDownloadFolder = (id: string) => { const handleDownloadFolder = (id: string) => {
@ -73,7 +85,7 @@ const SideBarFoldersButtonsComponent = ({
addFolder({ name: "New Folder", parent_id: null, description: "" }).then( addFolder({ name: "New Folder", parent_id: null, description: "" }).then(
(res) => { (res) => {
getFoldersApi(true); getFoldersApi(true);
} },
); );
} }
@ -119,7 +131,7 @@ const SideBarFoldersButtonsComponent = ({
<> <>
{folders.map((item, index) => { {folders.map((item, index) => {
const editFolderName = editFolders?.filter( const editFolderName = editFolders?.filter(
(folder) => folder.name === item.name (folder) => folder.name === item.name,
)[0]; )[0];
return ( return (
<div <div
@ -135,7 +147,7 @@ const SideBarFoldersButtonsComponent = ({
? "border border-border bg-muted hover:bg-muted" ? "border border-border bg-muted hover:bg-muted"
: "border hover:bg-transparent lg:border-transparent lg:hover:border-border", : "border hover:bg-transparent lg:border-transparent lg:hover:border-border",
"group flex w-full shrink-0 cursor-pointer gap-2 opacity-100 lg:min-w-full", "group flex w-full shrink-0 cursor-pointer gap-2 opacity-100 lg:min-w-full",
folderIdDragging === item.id! ? "bg-border" : "" folderIdDragging === item.id! ? "bg-border" : "",
)} )}
onClick={() => handleChangeFolder!(item.id!)} onClick={() => handleChangeFolder!(item.id!)}
> >
@ -205,7 +217,7 @@ const SideBarFoldersButtonsComponent = ({
folders.map((obj) => ({ folders.map((obj) => ({
name: obj.name, name: obj.name,
edit: false, edit: false,
})) })),
); );
} }
if (e.key === "Enter") { if (e.key === "Enter") {
@ -238,10 +250,10 @@ const SideBarFoldersButtonsComponent = ({
}; };
const updatedFolder = await updateFolder( const updatedFolder = await updateFolder(
body, body,
item.id! item.id!,
); );
const updateFolders = folders.filter( const updateFolders = folders.filter(
(f) => f.name !== item.name (f) => f.name !== item.name,
); );
setFolders([...updateFolders, updatedFolder]); setFolders([...updateFolders, updatedFolder]);
setFoldersNames({}); setFoldersNames({});
@ -249,7 +261,7 @@ const SideBarFoldersButtonsComponent = ({
folders.map((obj) => ({ folders.map((obj) => ({
name: obj.name, name: obj.name,
edit: false, edit: false,
})) })),
); );
} else { } else {
setFoldersNames((old) => ({ setFoldersNames((old) => ({

View file

@ -12,7 +12,6 @@ type SidebarNavProps = {
title: string; title: string;
icon: React.ReactNode; icon: React.ReactNode;
}[]; }[];
handleOpenNewFolderModal?: () => void;
handleChangeFolder?: (id: string) => void; handleChangeFolder?: (id: string) => void;
handleEditFolder?: (item: FolderType) => void; handleEditFolder?: (item: FolderType) => void;
handleDeleteFolder?: (item: FolderType) => void; handleDeleteFolder?: (item: FolderType) => void;

View file

@ -13,6 +13,7 @@ import ForwardedIconComponent from "../genericIconComponent";
import { Alert, AlertDescription, AlertTitle } from "../ui/alert"; import { Alert, AlertDescription, AlertTitle } from "../ui/alert";
import ResetColumns from "./components/ResetColumns"; import ResetColumns from "./components/ResetColumns";
import resetGrid from "./utils/reset-grid-columns"; import resetGrid from "./utils/reset-grid-columns";
import { useParams } from "react-router-dom";
interface TableComponentProps extends AgGridReactProps { interface TableComponentProps extends AgGridReactProps {
columnDefs: NonNullable<AgGridReactProps["columnDefs"]>; columnDefs: NonNullable<AgGridReactProps["columnDefs"]>;
@ -32,7 +33,7 @@ const TableComponent = forwardRef<
alertDescription = DEFAULT_TABLE_ALERT_MSG, alertDescription = DEFAULT_TABLE_ALERT_MSG,
...props ...props
}, },
ref ref,
) => { ) => {
let colDef = props.columnDefs.map((col, index) => { let colDef = props.columnDefs.map((col, index) => {
let newCol = { let newCol = {
@ -82,16 +83,16 @@ const TableComponent = forwardRef<
// @ts-ignore // @ts-ignore
realRef.current = params; realRef.current = params;
const updatedColumnDefs = makeLastColumnNonResizable([...colDef]); const updatedColumnDefs = makeLastColumnNonResizable([...colDef]);
params.api.setColumnDefs(updatedColumnDefs); params.api.setGridOption("columnDefs", updatedColumnDefs);
initialColumnDefs.current = params.api.getColumnDefs(); initialColumnDefs.current = params.api.getColumnDefs();
if (props.onGridReady) props.onGridReady(params); if (props.onGridReady) props.onGridReady(params);
}; };
const onColumnMoved = (params) => { const onColumnMoved = (params) => {
const updatedColumnDefs = makeLastColumnNonResizable( const updatedColumnDefs = makeLastColumnNonResizable(
params.columnApi.getAllGridColumns().map((col) => col.getColDef()) params.columnApi.getAllGridColumns().map((col) => col.getColDef()),
); );
params.api.setColumnDefs(updatedColumnDefs); params.api.setGridOption("columnDefs", updatedColumnDefs);
if (props.onColumnMoved) props.onColumnMoved(params); if (props.onColumnMoved) props.onColumnMoved(params);
}; };
@ -114,7 +115,7 @@ const TableComponent = forwardRef<
className={cn( className={cn(
dark ? "ag-theme-quartz-dark" : "ag-theme-quartz", dark ? "ag-theme-quartz-dark" : "ag-theme-quartz",
"ag-theme-shadcn flex h-full flex-col", "ag-theme-shadcn flex h-full flex-col",
"relative" "relative",
)} // applying the grid theme )} // applying the grid theme
> >
<AgGridReact <AgGridReact
@ -125,9 +126,6 @@ const TableComponent = forwardRef<
}} }}
columnDefs={colDef} columnDefs={colDef}
ref={realRef} ref={realRef}
getRowId={(params) => {
return params.data.id;
}}
pagination={true} pagination={true}
onGridReady={onGridReady} onGridReady={onGridReady}
onColumnMoved={onColumnMoved} onColumnMoved={onColumnMoved}
@ -135,7 +133,7 @@ const TableComponent = forwardRef<
<ResetColumns resetGrid={() => resetGrid(realRef, initialColumnDefs)} /> <ResetColumns resetGrid={() => resetGrid(realRef, initialColumnDefs)} />
</div> </div>
); );
} },
); );
export default TableComponent; export default TableComponent;

View file

@ -668,7 +668,7 @@ export const ZERO_NOTIFICATIONS = "No new notifications";
export const SUCCESS_BUILD = "Built sucessfully ✨"; export const SUCCESS_BUILD = "Built sucessfully ✨";
export const ALERT_SAVE_WITH_API = export const ALERT_SAVE_WITH_API =
"Caution: Uncheck this box only removes API keys from fields specifically designated for API keys."; "Caution: Unchecking this box only removes API keys from fields specifically designated for API keys.";
export const SAVE_WITH_API_CHECKBOX = "Save with my API keys"; export const SAVE_WITH_API_CHECKBOX = "Save with my API keys";
export const EDIT_TEXT_MODAL_TITLE = "Edit Text"; export const EDIT_TEXT_MODAL_TITLE = "Edit Text";

View file

@ -6,9 +6,9 @@ const SvgBotMessageSquare = (props) => (
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
stroke-width="2" strokeWidth="2"
stroke-linecap="round" strokeLinecap="round"
stroke-linejoin="round" strokeLinejoin="round"
className="lucide lucide-bot-message-square" className="lucide lucide-bot-message-square"
{...props} {...props}
> >

View file

@ -133,7 +133,7 @@ export default function ChatView({
function updateChat( function updateChat(
chat: ChatMessageType, chat: ChatMessageType,
message: string, message: string,
stream_url?: string stream_url?: string,
) { ) {
// if (message === "") return; // if (message === "") return;
chat.message = message; chat.message = message;
@ -165,14 +165,12 @@ export default function ChatView({
className="flex gap-1" className="flex gap-1"
size="none" size="none"
variant="none" variant="none"
disabled={lockChat}
onClick={() => handleSelectChange("builds")} onClick={() => handleSelectChange("builds")}
> >
<IconComponent <IconComponent
name="Eraser" name="Eraser"
className={classNames( className={classNames("h-5 w-5 text-primary")}
"h-5 w-5 transition-all duration-100",
lockChat ? "animate-pulse text-primary" : "text-primary"
)}
aria-hidden="true" aria-hidden="true"
/> />
</Button> </Button>

View file

@ -44,17 +44,17 @@ export const switchCaseModalSize = (size: string) => {
break; break;
case "large-thin": case "large-thin":
minWidth = "min-w-[65vw]"; minWidth = "min-w-[65vw]";
height = "h-[80vh]"; height = "h-[90vh]";
break; break;
case "md-thin": case "md-thin":
minWidth = "min-w-[85vw]"; minWidth = "min-w-[85vw]";
height = "h-[70vh]"; height = "h-[90vh]";
break; break;
case "sm-thin": case "sm-thin":
minWidth = "min-w-[65vw]"; minWidth = "min-w-[65vw]";
height = "h-[70vh]"; height = "h-[90vh]";
break; break;
case "large-h-full": case "large-h-full":

View file

@ -103,7 +103,7 @@ interface BaseModalProps {
React.ReactElement<ContentProps>, React.ReactElement<ContentProps>,
React.ReactElement<HeaderProps>, React.ReactElement<HeaderProps>,
React.ReactElement<TriggerProps>?, React.ReactElement<TriggerProps>?,
React.ReactElement<FooterProps>? React.ReactElement<FooterProps>?,
]; ];
open?: boolean; open?: boolean;
setOpen?: (open: boolean) => void; setOpen?: (open: boolean) => void;
@ -138,16 +138,16 @@ function BaseModal({
onSubmit, onSubmit,
}: BaseModalProps) { }: BaseModalProps) {
const headerChild = React.Children.toArray(children).find( const headerChild = React.Children.toArray(children).find(
(child) => (child as React.ReactElement).type === Header (child) => (child as React.ReactElement).type === Header,
); );
const triggerChild = React.Children.toArray(children).find( const triggerChild = React.Children.toArray(children).find(
(child) => (child as React.ReactElement).type === Trigger (child) => (child as React.ReactElement).type === Trigger,
); );
const ContentChild = React.Children.toArray(children).find( const ContentChild = React.Children.toArray(children).find(
(child) => (child as React.ReactElement).type === Content (child) => (child as React.ReactElement).type === Content,
); );
const ContentFooter = React.Children.toArray(children).find( const ContentFooter = React.Children.toArray(children).find(
(child) => (child as React.ReactElement).type === Footer (child) => (child as React.ReactElement).type === Footer,
); );
let { minWidth, height } = switchCaseModalSize(size); let { minWidth, height } = switchCaseModalSize(size);
@ -164,17 +164,21 @@ function BaseModal({
{type === "modal" ? ( {type === "modal" ? (
<Modal open={open} onOpenChange={setOpen}> <Modal open={open} onOpenChange={setOpen}>
{triggerChild} {triggerChild}
<ModalContent className={cn(minWidth, "duration-300")}> <ModalContent
<div className="truncate-doubleline word-break-break-word"> className={cn(minWidth, height, "flex flex-col duration-300")}
>
<div className="flex-shrink-0 truncate-doubleline word-break-break-word">
{headerChild} {headerChild}
</div> </div>
<div <div
className={`flex flex-col ${height} w-full transition-all duration-300`} className={`flex w-full flex-1 flex-col transition-all duration-300`}
> >
{ContentChild} {ContentChild}
</div> </div>
{ContentFooter && ( {ContentFooter && (
<div className="flex flex-row-reverse">{ContentFooter}</div> <div className="flex flex-shrink-0 flex-row-reverse">
{ContentFooter}
</div>
)} )}
</ModalContent> </ModalContent>
</Modal> </Modal>
@ -184,7 +188,7 @@ function BaseModal({
<DialogContent <DialogContent
className={cn(minWidth, height, "flex flex-col duration-300")} className={cn(minWidth, height, "flex flex-col duration-300")}
> >
<div className="truncate-doubleline word-break-break-word"> <div className="flex-shrink-0 truncate-doubleline word-break-break-word">
{headerChild} {headerChild}
</div> </div>
{onSubmit ? ( {onSubmit ? (
@ -193,26 +197,30 @@ function BaseModal({
event.preventDefault(); event.preventDefault();
onSubmit(); onSubmit();
}} }}
className="flex flex-1 flex-col gap-6" className="flex min-h-0 flex-1 flex-col gap-6"
> >
<div <div
className={`flex w-full flex-1 flex-col transition-all duration-300`} className={`flex w-full flex-1 flex-col overflow-hidden transition-all duration-300`}
> >
{ContentChild} {ContentChild}
</div> </div>
{ContentFooter && ( {ContentFooter && (
<div className="flex flex-row-reverse">{ContentFooter}</div> <div className="flex flex-shrink-0 flex-row-reverse">
{ContentFooter}
</div>
)} )}
</Form.Root> </Form.Root>
) : ( ) : (
<> <>
<div <div
className={`flex w-full flex-1 flex-col transition-all duration-300`} className={`flex min-h-0 w-full flex-1 flex-col transition-all duration-300`}
> >
{ContentChild} {ContentChild}
</div> </div>
{ContentFooter && ( {ContentFooter && (
<div className="flex flex-row-reverse">{ContentFooter}</div> <div className="flex flex-shrink-0 flex-row-reverse">
{ContentFooter}
</div>
)} )}
</> </>
)} )}

View file

@ -45,7 +45,7 @@ const ExportModal = forwardRef(
is_component: false, is_component: false,
}, },
name!, name!,
description description,
); );
setNoticeData({ setNoticeData({
title: API_WARNING_NOTICE_ALERT, title: API_WARNING_NOTICE_ALERT,
@ -61,7 +61,7 @@ const ExportModal = forwardRef(
is_component: false, is_component: false,
}), }),
name!, name!,
description description,
); );
setOpen(false); setOpen(false);
}} }}
@ -94,7 +94,7 @@ const ExportModal = forwardRef(
{SAVE_WITH_API_CHECKBOX} {SAVE_WITH_API_CHECKBOX}
</label> </label>
</div> </div>
<span className=" text-xs text-destructive "> <span className="mt-1 text-xs text-destructive ">
{ALERT_SAVE_WITH_API} {ALERT_SAVE_WITH_API}
</span> </span>
</BaseModal.Content> </BaseModal.Content>
@ -102,6 +102,6 @@ const ExportModal = forwardRef(
<BaseModal.Footer submit={{ label: "Download Flow" }} /> <BaseModal.Footer submit={{ label: "Download Flow" }} />
</BaseModal> </BaseModal>
); );
} },
); );
export default ExportModal; export default ExportModal;

View file

@ -11,13 +11,13 @@ import ReactFlow, {
SelectionDragHandler, SelectionDragHandler,
updateEdge, updateEdge,
} from "reactflow"; } from "reactflow";
import GenericNode from "../../../../CustomNodes/GenericNode";
import { import {
INVALID_SELECTION_ERROR_ALERT, INVALID_SELECTION_ERROR_ALERT,
UPLOAD_ALERT_LIST, UPLOAD_ALERT_LIST,
UPLOAD_ERROR_ALERT, UPLOAD_ERROR_ALERT,
WRONG_FILE_ERROR_ALERT, WRONG_FILE_ERROR_ALERT,
} from "../../../../constants/alerts_constants"; } from "../../../../constants/alerts_constants";
import GenericNode from "../../../../customNodes/genericNode";
import useAlertStore from "../../../../stores/alertStore"; import useAlertStore from "../../../../stores/alertStore";
import useFlowStore from "../../../../stores/flowStore"; import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; import useFlowsManagerStore from "../../../../stores/flowsManagerStore";

View file

@ -4,7 +4,9 @@ import { FormProvider, useForm, useWatch } from "react-hook-form";
import { Link, useLocation, useNavigate } from "react-router-dom"; import { Link, useLocation, useNavigate } from "react-router-dom";
import CollectionCardComponent from "../../../../components/cardComponent"; import CollectionCardComponent from "../../../../components/cardComponent";
import CardsWrapComponent from "../../../../components/cardsWrapComponent"; import CardsWrapComponent from "../../../../components/cardsWrapComponent";
import IconComponent from "../../../../components/genericIconComponent"; import IconComponent, {
ForwardedIconComponent,
} from "../../../../components/genericIconComponent";
import PaginatorComponent from "../../../../components/paginatorComponent"; import PaginatorComponent from "../../../../components/paginatorComponent";
import { SkeletonCardComponent } from "../../../../components/skeletonCardComponent"; import { SkeletonCardComponent } from "../../../../components/skeletonCardComponent";
import { Button } from "../../../../components/ui/button"; import { Button } from "../../../../components/ui/button";
@ -18,6 +20,9 @@ import { getNameByType } from "../../utils/get-name-by-type";
import { sortFlows } from "../../utils/sort-flows"; import { sortFlows } from "../../utils/sort-flows";
import EmptyComponent from "../emptyComponent"; import EmptyComponent from "../emptyComponent";
import HeaderComponent from "../headerComponent"; import HeaderComponent from "../headerComponent";
import { downloadFlow, removeApiKeys } from "../../../../utils/reactflowUtils";
import { useDarkStore } from "../../../../stores/darkStore";
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
export default function ComponentsComponent({ export default function ComponentsComponent({
type = "all", type = "all",
@ -31,22 +36,22 @@ export default function ComponentsComponent({
const allFlows = useFlowsManagerStore((state) => state.allFlows); const allFlows = useFlowsManagerStore((state) => state.allFlows);
const flowsFromFolder = useFolderStore( const flowsFromFolder = useFolderStore(
(state) => state.selectedFolder?.flows (state) => state.selectedFolder?.flows,
); );
const setSuccessData = useAlertStore((state) => state.setSuccessData); const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const [openDelete, setOpenDelete] = useState(false); const [openDelete, setOpenDelete] = useState(false);
const searchFlowsComponents = useFlowsManagerStore( const searchFlowsComponents = useFlowsManagerStore(
(state) => state.searchFlowsComponents (state) => state.searchFlowsComponents,
); );
const setSelectedFlowsComponentsCards = useFlowsManagerStore( const setSelectedFlowsComponentsCards = useFlowsManagerStore(
(state) => state.setSelectedFlowsComponentsCards (state) => state.setSelectedFlowsComponentsCards,
); );
const selectedFlowsComponentsCards = useFlowsManagerStore( const selectedFlowsComponentsCards = useFlowsManagerStore(
(state) => state.selectedFlowsComponentsCards (state) => state.selectedFlowsComponentsCards,
); );
const [handleFileDrop] = useFileDrop(uploadFlow, type)!; const [handleFileDrop] = useFileDrop(uploadFlow, type)!;
@ -82,7 +87,7 @@ export default function ComponentsComponent({
f.name.toLowerCase().includes(searchFlowsComponents.toLowerCase()) || f.name.toLowerCase().includes(searchFlowsComponents.toLowerCase()) ||
f.description f.description
.toLowerCase() .toLowerCase()
.includes(searchFlowsComponents.toLowerCase()) .includes(searchFlowsComponents.toLowerCase()),
); );
if (searchFlowsComponents === "") { if (searchFlowsComponents === "") {
@ -129,6 +134,8 @@ export default function ComponentsComponent({
setOpenDelete(true); setOpenDelete(true);
} else if (action === "duplicate") { } else if (action === "duplicate") {
handleDuplicate(); handleDuplicate();
} else if (action === "export") {
handleExport();
} }
}; };
@ -137,9 +144,9 @@ export default function ComponentsComponent({
selectedFlowsComponentsCards.map((selectedFlow) => selectedFlowsComponentsCards.map((selectedFlow) =>
addFlow( addFlow(
true, true,
allFlows.find((flow) => flow.id === selectedFlow) allFlows.find((flow) => flow.id === selectedFlow),
) ),
) ),
).then(() => { ).then(() => {
resetFilter(); resetFilter();
getFoldersApi(true); getFoldersApi(true);
@ -152,6 +159,47 @@ export default function ComponentsComponent({
}); });
}; };
const handleImport = () => {
uploadFlow({ newProject: true, isComponent: false })
.then(() => {
resetFilter();
getFoldersApi(true);
if (!folderId || folderId === myCollectionId) {
getFolderById(folderId ? folderId : myCollectionId);
}
setSelectedFlowsComponentsCards([]);
setSuccessData({ title: "Flows imported successfully" });
})
.catch((error) => {
setErrorData({
title: UPLOAD_ERROR_ALERT,
list: [error],
});
});
};
const version = useDarkStore((state) => state.version);
const handleExport = () => {
selectedFlowsComponentsCards.map((selectedFlowId) => {
const selectedFlow = allFlows.find((flow) => flow.id === selectedFlowId);
downloadFlow(
removeApiKeys({
id: selectedFlow!.id,
data: selectedFlow!.data!,
description: selectedFlow!.description,
name: selectedFlow!.name,
last_tested_version: version,
is_component: false,
}),
selectedFlow!.name,
selectedFlow!.description,
);
});
setSuccessData({ title: "Flows exported successfully" });
};
const handleDeleteMultiple = () => { const handleDeleteMultiple = () => {
removeFlow(selectedFlowsComponentsCards) removeFlow(selectedFlowsComponentsCards)
.then(() => { .then(() => {
@ -161,7 +209,7 @@ export default function ComponentsComponent({
getFolderById(folderId ? folderId : myCollectionId); getFolderById(folderId ? folderId : myCollectionId);
} }
setSuccessData({ setSuccessData({
title: "Selected items deleted successfully!", title: "Selected items deleted successfully",
}); });
}) })
.catch(() => { .catch(() => {
@ -180,7 +228,7 @@ export default function ComponentsComponent({
return true; return true;
} }
return false; return false;
} },
); );
setSelectedFlowsComponentsCards(selectedFlows); setSelectedFlowsComponentsCards(selectedFlows);
@ -215,20 +263,23 @@ export default function ComponentsComponent({
if (type === "all") return allFlows?.length; if (type === "all") return allFlows?.length;
return allFlows?.filter( return allFlows?.filter(
(f) => (f.is_component ?? false) === (type === "component") (f) => (f.is_component ?? false) === (type === "component"),
)?.length; )?.length;
}; };
return ( return (
<> <>
{allFlows?.length > 0 && ( <div className="flex w-full gap-4 pb-5">
<HeaderComponent {allFlows?.length > 0 && (
handleDelete={() => handleSelectOptionsChange("delete")} <HeaderComponent
handleSelectAll={handleSelectAll} handleDelete={() => handleSelectOptionsChange("delete")}
handleDuplicate={() => handleSelectOptionsChange("duplicate")} handleSelectAll={handleSelectAll}
disableFunctions={!(selectedFlowsComponentsCards?.length > 0)} handleDuplicate={() => handleSelectOptionsChange("duplicate")}
/> handleExport={() => handleSelectOptionsChange("export")}
)} disableFunctions={!(selectedFlowsComponentsCards?.length > 0)}
/>
)}
</div>
<CardsWrapComponent <CardsWrapComponent
onFileDrop={handleFileDrop} onFileDrop={handleFileDrop}

View file

@ -13,7 +13,7 @@ const EmptyComponent = ({}: EmptyComponentProps) => {
return ( return (
<> <>
<div className="mt-6 flex w-full items-center justify-center text-center"> <div className="mt-2 flex w-full items-center justify-center text-center">
<div className="flex-max-width h-full flex-col"> <div className="flex-max-width h-full flex-col">
<div className="align-center flex w-full justify-center gap-1 "> <div className="align-center flex w-full justify-center gap-1 ">
<span className="text-muted-foreground"> <span className="text-muted-foreground">

View file

@ -1,13 +1,17 @@
import { useState } from "react"; import { useState } from "react";
import IconComponent from "../../../../components/genericIconComponent"; import IconComponent, {
ForwardedIconComponent,
} from "../../../../components/genericIconComponent";
import ShadTooltip from "../../../../components/shadTooltipComponent"; import ShadTooltip from "../../../../components/shadTooltipComponent";
import { Checkbox } from "../../../../components/ui/checkbox"; import { Checkbox } from "../../../../components/ui/checkbox";
import { cn } from "../../../../utils/utils"; import { cn } from "../../../../utils/utils";
import { Button } from "../../../../components/ui/button";
type HeaderComponentProps = { type HeaderComponentProps = {
handleSelectAll: (select) => void; handleSelectAll: (select) => void;
handleDelete: () => void; handleDelete: () => void;
handleDuplicate: () => void; handleDuplicate: () => void;
handleExport: () => void;
disableFunctions: boolean; disableFunctions: boolean;
}; };
@ -15,6 +19,7 @@ const HeaderComponent = ({
handleSelectAll, handleSelectAll,
handleDelete, handleDelete,
handleDuplicate, handleDuplicate,
handleExport,
disableFunctions, disableFunctions,
}: HeaderComponentProps) => { }: HeaderComponentProps) => {
const [shouldSelectAll, setShouldSelectAll] = useState(true); const [shouldSelectAll, setShouldSelectAll] = useState(true);
@ -26,29 +31,41 @@ const HeaderComponent = ({
return ( return (
<> <>
<div className="grid grid-cols-3 pb-5"> <div className="flex w-full items-center justify-between gap-4">
<div className="col-auto grid-cols-1 self-center justify-self-start"> <div className="flex items-center justify-self-start">
<a onClick={handleClick} className="text-sm"> <a onClick={handleClick} className="cursor-pointer text-sm">
<div className="header-menu-bar-display "> <div className="flex items-center space-x-2">
<div <Checkbox checked={!shouldSelectAll} id="terms" />
className="header-menu-flow-name" <span className="label text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
data-testid="select_all_collection" {shouldSelectAll ? "Select All" : "Unselect All"}
> </span>
<div className="flex items-center space-x-2">
<Checkbox checked={!shouldSelectAll} id="terms" />
<label
onClick={handleClick}
htmlFor="terms"
className="label cursor-pointer text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{shouldSelectAll ? "Select All" : "Unselect All"}
</label>
</div>
</div>
</div> </div>
</a> </a>
</div> </div>
<div className="col-span-2 flex grid-cols-1 gap-2 justify-self-end"> <div className="flex items-center gap-2">
<div>
<ShadTooltip
content={
disableFunctions ? (
<span>Select items to export</span>
) : (
<span>Export selected items</span>
)
}
>
<Button
variant="none"
size="none"
onClick={handleExport}
disabled={disableFunctions}
>
<IconComponent
name="FileDown"
className={cn("h-5 w-5 text-primary transition-all")}
/>
</Button>
</ShadTooltip>
</div>
<div> <div>
<ShadTooltip <ShadTooltip
content={ content={
@ -59,12 +76,17 @@ const HeaderComponent = ({
) )
} }
> >
<button onClick={handleDuplicate} disabled={disableFunctions}> <Button
variant="none"
size="none"
onClick={handleDuplicate}
disabled={disableFunctions}
>
<IconComponent <IconComponent
name="Copy" name="Copy"
className={cn("h-5 w-5 text-primary transition-all")} className={cn("h-5 w-5 text-primary transition-all")}
/> />
</button> </Button>
</ShadTooltip> </ShadTooltip>
</div> </div>
<div> <div>
@ -77,15 +99,20 @@ const HeaderComponent = ({
) )
} }
> >
<button onClick={handleDelete} disabled={disableFunctions}> <Button
variant="none"
size="none"
onClick={handleDelete}
disabled={disableFunctions}
>
<IconComponent <IconComponent
name="Trash2" name="Trash2"
className={cn( className={cn(
"h-5 w-5 text-primary transition-all", "h-5 w-5 text-primary transition-all",
disableFunctions ? "" : "hover:text-destructive" disableFunctions ? "" : "hover:text-destructive",
)} )}
/> />
</button> </Button>
</ShadTooltip> </ShadTooltip>
</div> </div>
</div> </div>

View file

@ -2,16 +2,15 @@ import ComponentsComponent from "../componentsComponent";
import HeaderTabsSearchComponent from "./components/headerTabsSearchComponent"; import HeaderTabsSearchComponent from "./components/headerTabsSearchComponent";
type MyCollectionComponentProps = { type MyCollectionComponentProps = {
key: string;
type: string; type: string;
}; };
const MyCollectionComponent = ({ key, type }: MyCollectionComponentProps) => { const MyCollectionComponent = ({ type }: MyCollectionComponentProps) => {
return ( return (
<> <>
<HeaderTabsSearchComponent /> <HeaderTabsSearchComponent />
<div className="mt-5 flex h-full flex-col"> <div className="mt-5 flex h-full flex-col">
<ComponentsComponent key={key} type={type} /> <ComponentsComponent key={type} type={type} />
</div> </div>
</> </>
); );

View file

@ -16,7 +16,7 @@ import useDropdownOptions from "../../hooks/use-dropdown-options";
export default function HomePage(): JSX.Element { export default function HomePage(): JSX.Element {
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow); const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
const setCurrentFlowId = useFlowsManagerStore( const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId (state) => state.setCurrentFlowId,
); );
const location = useLocation(); const location = useLocation();
@ -67,10 +67,6 @@ export default function HomePage(): JSX.Element {
<div className="flex h-full w-full space-y-8 md:flex-col lg:flex-row lg:space-x-8 lg:space-y-0"> <div className="flex h-full w-full space-y-8 md:flex-col lg:flex-row lg:space-x-8 lg:space-y-0">
<aside className="flex h-fit w-fit flex-col space-y-6"> <aside className="flex h-fit w-fit flex-col space-y-6">
<SidebarNav <SidebarNav
handleOpenNewFolderModal={() => {
setFolderToEdit(null);
setOpenFolderModal(true);
}}
items={[]} items={[]}
handleChangeFolder={(id: string) => { handleChangeFolder={(id: string) => {
navigate(`all/folder/${id}`, { state: { folderId: id } }); navigate(`all/folder/${id}`, { state: { folderId: id } });

View file

@ -8,7 +8,7 @@ import useFlowsManagerStore from "../../stores/flowsManagerStore";
export default function SettingsPage(): JSX.Element { export default function SettingsPage(): JSX.Element {
const pathname = location.pathname; const pathname = location.pathname;
const setCurrentFlowId = useFlowsManagerStore( const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId (state) => state.setCurrentFlowId,
); );
useEffect(() => { useEffect(() => {
setCurrentFlowId(""); setCurrentFlowId("");
@ -59,7 +59,10 @@ export default function SettingsPage(): JSX.Element {
title: "Messages", title: "Messages",
href: "/settings/messages", href: "/settings/messages",
icon: ( icon: (
<ForwardedIconComponent name="Keyboard" className="w-5 stroke-[1.5]" /> <ForwardedIconComponent
name="MessagesSquare"
className="w-4 flex-shrink-0 justify-start stroke-[1.5]"
/>
), ),
}, },
]; ];
@ -72,8 +75,10 @@ export default function SettingsPage(): JSX.Element {
<aside className="flex h-full shrink-0 flex-col space-y-6 lg:w-[20vw]"> <aside className="flex h-full shrink-0 flex-col space-y-6 lg:w-[20vw]">
<SidebarNav items={sidebarNavItems} /> <SidebarNav items={sidebarNavItems} />
</aside> </aside>
<div className="h-full w-full flex-1 pb-8"> <div className="flex h-full w-full flex-1 flex-col">
<Outlet /> <div className="flex-1 pb-8">
<Outlet />
</div>
</div> </div>
</div> </div>
</PageLayout> </PageLayout>

View file

@ -52,23 +52,17 @@ export default function ApiKeysPage() {
/> />
<div className="flex h-full w-full flex-col justify-between"> <div className="flex h-full w-full flex-col justify-between">
<Card x-chunk="dashboard-04-chunk-2" className="h-full pt-4"> <TableComponent
<CardContent className="h-full"> overlayNoRowsTemplate="No data available"
<TableComponent onSelectionChanged={(event: SelectionChangedEvent) => {
overlayNoRowsTemplate="No data available" setSelectedRows(event.api.getSelectedRows().map((row) => row.id));
onSelectionChanged={(event: SelectionChangedEvent) => { }}
setSelectedRows( rowSelection="multiple"
event.api.getSelectedRows().map((row) => row.id), suppressRowClickSelection={true}
); pagination={true}
}} columnDefs={columnDefs}
rowSelection="multiple" rowData={keysList.current}
suppressRowClickSelection={true} />
pagination={true}
columnDefs={columnDefs}
rowData={keysList.current}
/>
</CardContent>
</Card>
</div> </div>
</div> </div>
); );

View file

@ -16,13 +16,13 @@ import { cn } from "../../../../utils/utils";
export default function GlobalVariablesPage() { export default function GlobalVariablesPage() {
const globalVariablesEntries = useGlobalVariablesStore( const globalVariablesEntries = useGlobalVariablesStore(
(state) => state.globalVariablesEntries (state) => state.globalVariablesEntries,
); );
const removeGlobalVariable = useGlobalVariablesStore( const removeGlobalVariable = useGlobalVariablesStore(
(state) => state.removeGlobalVariable (state) => state.removeGlobalVariable,
); );
const globalVariables = useGlobalVariablesStore( const globalVariables = useGlobalVariablesStore(
(state) => state.globalVariables (state) => state.globalVariables,
); );
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const getVariableId = useGlobalVariablesStore((state) => state.getVariableId); const getVariableId = useGlobalVariablesStore((state) => state.getVariableId);
@ -154,7 +154,7 @@ export default function GlobalVariablesPage() {
<IconComponent <IconComponent
name="Trash2" name="Trash2"
className={cn( className={cn(
"h-5 w-5 text-destructive group-disabled:text-primary" "h-5 w-5 text-destructive group-disabled:text-primary",
)} )}
/> />
</Button> </Button>
@ -168,23 +168,17 @@ export default function GlobalVariablesPage() {
</div> </div>
<div className="flex h-full w-full flex-col justify-between"> <div className="flex h-full w-full flex-col justify-between">
<Card x-chunk="dashboard-04-chunk-2" className="h-full pt-4"> <TableComponent
<CardContent className="h-full"> overlayNoRowsTemplate="No data available"
<TableComponent onSelectionChanged={(event: SelectionChangedEvent) => {
overlayNoRowsTemplate="No data available" setSelectedRows(event.api.getSelectedRows().map((row) => row.name));
onSelectionChanged={(event: SelectionChangedEvent) => { }}
setSelectedRows( rowSelection="multiple"
event.api.getSelectedRows().map((row) => row.name) suppressRowClickSelection={true}
); pagination={true}
}} columnDefs={colDefs}
rowSelection="multiple" rowData={rowData}
suppressRowClickSelection={true} />
pagination={true}
columnDefs={colDefs}
rowData={rowData}
/>
</CardContent>
</Card>
</div> </div>
</div> </div>
); );

View file

@ -20,7 +20,7 @@ export default function ShortcutsPage() {
const redoShortcut = `${isMac ? "Cmd" : "Ctrl"} + Y`; const redoShortcut = `${isMac ? "Cmd" : "Ctrl"} + Y`;
// Column Definitions: Defines the columns to be displayed. // Column Definitions: Defines the columns to be displayed.
const [colDefs, setColDefs] = useState<(ColDef<any> | ColGroupDef<any>)[]>([ const colDefs = [
{ {
headerName: "Functionality", headerName: "Functionality",
field: "name", field: "name",
@ -33,9 +33,9 @@ export default function ShortcutsPage() {
editable: false, editable: false,
resizable: false, resizable: false,
}, },
]); ];
const [nodesRowData, setNodesRowData] = useState([ const nodesRowData = [
{ {
name: "Advanced Settings Component", name: "Advanced Settings Component",
shortcut: advancedShortcut, shortcut: advancedShortcut,
@ -96,7 +96,7 @@ export default function ShortcutsPage() {
shortcut: redoShortcut, shortcut: redoShortcut,
resizable: false, resizable: false,
}, },
]); ];
return ( return (
<div className="flex h-full w-full flex-col gap-6"> <div className="flex h-full w-full flex-col gap-6">
@ -114,17 +114,12 @@ export default function ShortcutsPage() {
</p> </p>
</div> </div>
</div> </div>
<div className="grid gap-6"> <div className="flex h-full w-full flex-col justify-between">
<Card x-chunk="dashboard-04-chunk-2" className="h-full pt-4"> <TableComponent
<CardContent className="h-full"> pagination={false}
<TableComponent columnDefs={colDefs}
domLayout="autoHeight" rowData={nodesRowData}
pagination={false} />
columnDefs={colDefs}
rowData={nodesRowData}
/>
</CardContent>
</Card>
</div> </div>
</div> </div>
); );

View file

@ -27,7 +27,7 @@ export default function MessagesPage() {
setSelectedRows, setSelectedRows,
setSuccessData, setSuccessData,
setErrorData, setErrorData,
selectedRows selectedRows,
); );
const { handleUpdate } = useUpdateMessage(setSuccessData, setErrorData); const { handleUpdate } = useUpdateMessage(setSuccessData, setErrorData);
@ -52,29 +52,25 @@ export default function MessagesPage() {
handleRemoveMessages={handleRemoveMessages} handleRemoveMessages={handleRemoveMessages}
/> />
<div className="flex h-full w-full flex-col justify-between pb-8"> <div className="flex h-full w-full flex-col justify-between">
<Card x-chunk="dashboard-04-chunk-2" className="h-full pt-4"> <TableComponent
<CardContent className="h-full"> readOnlyEdit
<TableComponent onCellEditRequest={(event) => {
readOnlyEdit handleUpdateMessage(event);
onCellEditRequest={(event) => { }}
handleUpdateMessage(event); editable={["Sender Name", "Message"]}
}} overlayNoRowsTemplate="No data available"
editable={["Sender Name", "Message"]} onSelectionChanged={(event: SelectionChangedEvent) => {
overlayNoRowsTemplate="No data available" setSelectedRows(
onSelectionChanged={(event: SelectionChangedEvent) => { event.api.getSelectedRows().map((row) => row.index),
setSelectedRows( );
event.api.getSelectedRows().map((row) => row.index) }}
); rowSelection="multiple"
}} suppressRowClickSelection={true}
rowSelection="multiple" pagination={true}
suppressRowClickSelection={true} columnDefs={columns}
pagination={true} rowData={messages}
columnDefs={columns} />
rowData={messages}
/>
</CardContent>
</Card>
</div> </div>
</div> </div>
); );

View file

@ -1,5 +1,5 @@
import * as debounce from "debounce-promise";
import { cloneDeep } from "lodash"; import { cloneDeep } from "lodash";
import pDebounce from "p-debounce";
import { Edge, Node, Viewport, XYPosition } from "reactflow"; import { Edge, Node, Viewport, XYPosition } from "reactflow";
import { create } from "zustand"; import { create } from "zustand";
import { SAVE_DEBOUNCE_TIME } from "../constants/constants"; import { SAVE_DEBOUNCE_TIME } from "../constants/constants";
@ -128,7 +128,7 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
set({ saveLoading: true }); // set saveLoading true immediately set({ saveLoading: true }); // set saveLoading true immediately
return get().saveFlowDebounce(flow, silent); // call the debounced function directly return get().saveFlowDebounce(flow, silent); // call the debounced function directly
}, },
saveFlowDebounce: debounce((flow: FlowType, silent?: boolean) => { saveFlowDebounce: pDebounce((flow: FlowType, silent?: boolean) => {
set({ saveLoading: true }); set({ saveLoading: true });
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
updateFlowInDatabase(flow) updateFlowInDatabase(flow)

View file

@ -7,6 +7,7 @@ import {
} from "../pages/MainPage/services"; } from "../pages/MainPage/services";
import { FoldersStoreType } from "../types/zustand/folders"; import { FoldersStoreType } from "../types/zustand/folders";
import useFlowsManagerStore from "./flowsManagerStore"; import useFlowsManagerStore from "./flowsManagerStore";
import { uploadFlowsToDatabase } from "../controllers/API";
export const useFolderStore = create<FoldersStoreType>((set, get) => ({ export const useFolderStore = create<FoldersStoreType>((set, get) => ({
folders: [], folders: [],
@ -17,18 +18,18 @@ export const useFolderStore = create<FoldersStoreType>((set, get) => ({
getFolders().then( getFolders().then(
(res) => { (res) => {
const foldersWithoutStarterProjects = res.filter( const foldersWithoutStarterProjects = res.filter(
(folder) => folder.name !== STARTER_FOLDER_NAME (folder) => folder.name !== STARTER_FOLDER_NAME,
); );
const starterProjects = res.find( const starterProjects = res.find(
(folder) => folder.name === STARTER_FOLDER_NAME (folder) => folder.name === STARTER_FOLDER_NAME,
); );
set({ starterProjectId: starterProjects!.id ?? "" }); set({ starterProjectId: starterProjects!.id ?? "" });
set({ folders: foldersWithoutStarterProjects }); set({ folders: foldersWithoutStarterProjects });
const myCollectionId = res?.find( const myCollectionId = res?.find(
(f) => f.name === DEFAULT_FOLDER (f) => f.name === DEFAULT_FOLDER,
)?.id; )?.id;
set({ myCollectionId }); set({ myCollectionId });
@ -41,11 +42,11 @@ export const useFolderStore = create<FoldersStoreType>((set, get) => ({
get().setLoading(false); get().setLoading(false);
resolve(); resolve();
}, },
() => { (error) => {
set({ folders: [] }); set({ folders: [] });
get().setLoading(false); get().setLoading(false);
reject(); reject(error);
} },
); );
} }
}); });
@ -65,7 +66,7 @@ export const useFolderStore = create<FoldersStoreType>((set, get) => ({
}, },
() => { () => {
get().setLoadingById(false); get().setLoadingById(false);
} },
); );
} }
}, },
@ -100,7 +101,7 @@ export const useFolderStore = create<FoldersStoreType>((set, get) => ({
folderIdDragging: "", folderIdDragging: "",
setFolderIdDragging: (id) => set(() => ({ folderIdDragging: id })), setFolderIdDragging: (id) => set(() => ({ folderIdDragging: id })),
uploadFolder: () => { uploadFolder: () => {
return new Promise<void>(() => { return new Promise<void>((resolve, reject) => {
const input = document.createElement("input"); const input = document.createElement("input");
input.type = "file"; input.type = "file";
input.onchange = (event: Event) => { input.onchange = (event: Event) => {
@ -111,8 +112,31 @@ export const useFolderStore = create<FoldersStoreType>((set, get) => ({
const file = (event.target as HTMLInputElement).files![0]; const file = (event.target as HTMLInputElement).files![0];
const formData = new FormData(); const formData = new FormData();
formData.append("file", file); formData.append("file", file);
uploadFlowsFromFolders(formData).then(() => { file.text().then((text) => {
get().getFoldersApi(true); const data = JSON.parse(text);
if (data.data?.nodes) {
useFlowsManagerStore
.getState()
.addFlow(true, data)
.then(() => {
resolve();
})
.catch((error) => {
reject(error);
});
} else {
uploadFlowsFromFolders(formData)
.then(() => {
get()
.getFoldersApi(true)
.then(() => {
resolve();
});
})
.catch((error) => {
reject(error);
});
}
}); });
} }
}; };

View file

@ -20,7 +20,7 @@ export type FoldersStoreType = {
setFolderUrl: (folderUrl: string) => void; setFolderUrl: (folderUrl: string) => void;
folderDragging: boolean; folderDragging: boolean;
setFolderDragging: (set: boolean) => void; setFolderDragging: (set: boolean) => void;
uploadFolder: (folderId: string) => void; uploadFolder: (folderId: string) => Promise<void>;
folderIdDragging: string; folderIdDragging: string;
setFolderIdDragging: (id: string) => void; setFolderIdDragging: (id: string) => void;
starterProjectId: string; starterProjectId: string;

View file

@ -8,6 +8,7 @@ import {
XYPosition, XYPosition,
} from "reactflow"; } from "reactflow";
import ShortUniqueId from "short-unique-id"; import ShortUniqueId from "short-unique-id";
import getFieldTitle from "../CustomNodes/utils/get-field-title";
import { import {
INPUT_TYPES, INPUT_TYPES,
LANGFLOW_SUPPORTED_TYPES, LANGFLOW_SUPPORTED_TYPES,
@ -16,7 +17,6 @@ import {
specialCharsRegex, specialCharsRegex,
} from "../constants/constants"; } from "../constants/constants";
import { downloadFlowsFromDatabase } from "../controllers/API"; import { downloadFlowsFromDatabase } from "../controllers/API";
import getFieldTitle from "../customNodes/utils/get-field-title";
import { DESCRIPTIONS } from "../flow_constants"; import { DESCRIPTIONS } from "../flow_constants";
import { import {
APIClassType, APIClassType,

View file

@ -56,6 +56,7 @@ import {
FolderIcon, FolderIcon,
FolderPlus, FolderPlus,
FolderPlusIcon, FolderPlusIcon,
FolderUp,
FormInput, FormInput,
Forward, Forward,
Gift, Gift,
@ -431,6 +432,7 @@ export const nodeIconsLucide: iconsType = {
ChevronLeft, ChevronLeft,
SlidersHorizontal, SlidersHorizontal,
Palette, Palette,
FolderUp,
Blocks, Blocks,
ChevronDown, ChevronDown,
ArrowLeft, ArrowLeft,