merge dev
This commit is contained in:
parent
11d02d7246
commit
d89078271c
100 changed files with 2296 additions and 1972 deletions
3
.github/workflows/docker-build.yml
vendored
3
.github/workflows/docker-build.yml
vendored
|
|
@ -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:
|
||||||
|
|
|
||||||
4
.github/workflows/pre-release.yml
vendored
4
.github/workflows/pre-release.yml
vendored
|
|
@ -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: |
|
||||||
|
|
|
||||||
1
Makefile
1
Makefile
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,14 @@
|
||||||
# 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
|
||||||
|
|
||||||
|
FROM node:20-bookworm-slim as builder-node
|
||||||
|
WORKDIR /app
|
||||||
|
COPY src/frontend/package.json src/frontend/package-lock.json ./
|
||||||
|
RUN npm install
|
||||||
|
COPY src/frontend/ ./
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
|
||||||
################################
|
################################
|
||||||
# BUILDER-BASE
|
# BUILDER-BASE
|
||||||
# Used to build deps + create our virtual environment
|
# Used to build deps + create our virtual environment
|
||||||
|
|
@ -47,12 +55,11 @@ 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
|
||||||
|
COPY --from=builder-node /app/build ./src/backend/base/langflow/frontend
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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. |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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**.
|
||||||
|
|
||||||
|
|
|
||||||
113
docs/docs/deployment/backend-only.md
Normal file
113
docs/docs/deployment/backend-only.md
Normal 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.
|
||||||
65
docs/docs/deployment/docker.md
Normal file
65
docs/docs/deployment/docker.md
Normal 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`.
|
||||||
|
|
@ -9,7 +9,7 @@ The `NotionUserList` component retrieves users from Notion. It provides a conven
|
||||||
|
|
||||||
[Notion Reference](https://developers.notion.com/reference/get-users)
|
[Notion Reference](https://developers.notion.com/reference/get-users)
|
||||||
|
|
||||||
The `NotionUserList` component enables you to:
|
The `NotionUserList` component enables you to:
|
||||||
|
|
||||||
- Retrieve user data from Notion
|
- Retrieve user data from Notion
|
||||||
- Access user information such as ID, type, name, and avatar URL
|
- Access user information such as ID, type, name, and avatar URL
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
4
docs/static/logos/twitter.svg
vendored
4
docs/static/logos/twitter.svg
vendored
|
|
@ -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 |
1055
poetry.lock
generated
1055
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "langflow"
|
name = "langflow"
|
||||||
version = "1.0.0a43"
|
version = "1.0.0a48"
|
||||||
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]
|
||||||
|
|
|
||||||
|
|
@ -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",
|
|
||||||
]
|
|
||||||
|
|
@ -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",
|
|
||||||
]
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
"list": false,
|
"list": false,
|
||||||
"show": true,
|
"show": true,
|
||||||
"multiline": true,
|
"multiline": true,
|
||||||
"value": "from itertools import chain\n\n\nfrom langchain_core.prompts import ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n",
|
"value": "from langchain_core.prompts import ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n",
|
||||||
"fileTypes": [],
|
"fileTypes": [],
|
||||||
"file_path": "",
|
"file_path": "",
|
||||||
"password": false,
|
"password": false,
|
||||||
|
|
@ -151,8 +151,7 @@
|
||||||
"load_from_db": false,
|
"load_from_db": false,
|
||||||
"title_case": false,
|
"title_case": false,
|
||||||
"input_types": [
|
"input_types": [
|
||||||
"Text",
|
"Text"
|
||||||
"Record"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"code": {
|
"code": {
|
||||||
|
|
@ -162,7 +161,7 @@
|
||||||
"list": false,
|
"list": false,
|
||||||
"show": true,
|
"show": true,
|
||||||
"multiline": true,
|
"multiline": true,
|
||||||
"value": "from typing import Optional\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import MODEL_NAMES\nfrom langflow.field_typing import NestedDict, Text\nfrom langflow.schema.schema import Record\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n field_order = [\n \"max_tokens\",\n \"model_kwargs\",\n \"model_name\",\n \"openai_api_base\",\n \"openai_api_key\",\n \"temperature\",\n \"input_value\",\n \"system_message\",\n \"stream\",\n ]\n\n def build_config(self):\n return {\n \"input_value\": {\"display_name\": \"Input\", \"input_types\": [\"Text\", \"Record\"]},\n \"max_tokens\": {\n \"display_name\": \"Max Tokens\",\n \"advanced\": True,\n \"info\": \"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n },\n \"model_kwargs\": {\n \"display_name\": \"Model Kwargs\",\n \"advanced\": True,\n },\n \"model_name\": {\n \"display_name\": \"Model Name\",\n \"advanced\": False,\n \"options\": MODEL_NAMES,\n },\n \"openai_api_base\": {\n \"display_name\": \"OpenAI API Base\",\n \"advanced\": True,\n \"info\": (\n \"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\\n\\n\"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\"\n ),\n },\n \"openai_api_key\": {\n \"display_name\": \"OpenAI API Key\",\n \"info\": \"The OpenAI API Key to use for the OpenAI model.\",\n \"advanced\": False,\n \"password\": True,\n },\n \"temperature\": {\n \"display_name\": \"Temperature\",\n \"advanced\": False,\n \"value\": 0.1,\n },\n \"stream\": {\n \"display_name\": \"Stream\",\n \"info\": STREAM_INFO_TEXT,\n \"advanced\": True,\n },\n \"system_message\": {\n \"display_name\": \"System Message\",\n \"info\": \"System message to pass to the model.\",\n \"advanced\": True,\n },\n }\n\n def build(\n self,\n input_value: Text | Record,\n openai_api_key: str,\n temperature: float = 0.1,\n model_name: str = \"gpt-4o\",\n max_tokens: Optional[int] = 256,\n model_kwargs: NestedDict = {},\n openai_api_base: Optional[str] = None,\n stream: bool = False,\n system_message: Optional[str] = None,\n ) -> Text:\n if not openai_api_base:\n openai_api_base = \"https://api.openai.com/v1\"\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature,\n )\n\n return self.get_chat_result(output, stream, input_value, system_message)\n",
|
"value": "from typing import Optional\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import MODEL_NAMES\nfrom langflow.field_typing import NestedDict, Text\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n field_order = [\n \"max_tokens\",\n \"model_kwargs\",\n \"model_name\",\n \"openai_api_base\",\n \"openai_api_key\",\n \"temperature\",\n \"input_value\",\n \"system_message\",\n \"stream\",\n ]\n\n def build_config(self):\n return {\n \"input_value\": {\"display_name\": \"Input\"},\n \"max_tokens\": {\n \"display_name\": \"Max Tokens\",\n \"advanced\": True,\n \"info\": \"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n },\n \"model_kwargs\": {\n \"display_name\": \"Model Kwargs\",\n \"advanced\": True,\n },\n \"model_name\": {\n \"display_name\": \"Model Name\",\n \"advanced\": False,\n \"options\": MODEL_NAMES,\n },\n \"openai_api_base\": {\n \"display_name\": \"OpenAI API Base\",\n \"advanced\": True,\n \"info\": (\n \"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\\n\\n\"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\"\n ),\n },\n \"openai_api_key\": {\n \"display_name\": \"OpenAI API Key\",\n \"info\": \"The OpenAI API Key to use for the OpenAI model.\",\n \"advanced\": False,\n \"password\": True,\n },\n \"temperature\": {\n \"display_name\": \"Temperature\",\n \"advanced\": False,\n \"value\": 0.1,\n },\n \"stream\": {\n \"display_name\": \"Stream\",\n \"info\": STREAM_INFO_TEXT,\n \"advanced\": True,\n },\n \"system_message\": {\n \"display_name\": \"System Message\",\n \"info\": \"System message to pass to the model.\",\n \"advanced\": True,\n },\n }\n\n def build(\n self,\n input_value: Text,\n openai_api_key: str,\n temperature: float = 0.1,\n model_name: str = \"gpt-4o\",\n max_tokens: Optional[int] = 256,\n model_kwargs: NestedDict = {},\n openai_api_base: Optional[str] = None,\n stream: bool = False,\n system_message: Optional[str] = None,\n ) -> Text:\n if not openai_api_base:\n openai_api_base = \"https://api.openai.com/v1\"\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature,\n )\n\n return self.get_chat_result(output, stream, input_value, system_message)\n",
|
||||||
"fileTypes": [],
|
"fileTypes": [],
|
||||||
"file_path": "",
|
"file_path": "",
|
||||||
"password": false,
|
"password": false,
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
"list": false,
|
"list": false,
|
||||||
"show": true,
|
"show": true,
|
||||||
"multiline": true,
|
"multiline": true,
|
||||||
"value": "from itertools import chain\n\n\nfrom langchain_core.prompts import ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n",
|
"value": "from langchain_core.prompts import ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n",
|
||||||
"fileTypes": [],
|
"fileTypes": [],
|
||||||
"file_path": "",
|
"file_path": "",
|
||||||
"password": false,
|
"password": false,
|
||||||
|
|
@ -484,8 +484,7 @@
|
||||||
"load_from_db": false,
|
"load_from_db": false,
|
||||||
"title_case": false,
|
"title_case": false,
|
||||||
"input_types": [
|
"input_types": [
|
||||||
"Text",
|
"Text"
|
||||||
"Record"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"code": {
|
"code": {
|
||||||
|
|
@ -495,7 +494,7 @@
|
||||||
"list": false,
|
"list": false,
|
||||||
"show": true,
|
"show": true,
|
||||||
"multiline": true,
|
"multiline": true,
|
||||||
"value": "from typing import Optional\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import MODEL_NAMES\nfrom langflow.field_typing import NestedDict, Text\nfrom langflow.schema.schema import Record\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n field_order = [\n \"max_tokens\",\n \"model_kwargs\",\n \"model_name\",\n \"openai_api_base\",\n \"openai_api_key\",\n \"temperature\",\n \"input_value\",\n \"system_message\",\n \"stream\",\n ]\n\n def build_config(self):\n return {\n \"input_value\": {\"display_name\": \"Input\", \"input_types\": [\"Text\", \"Record\"]},\n \"max_tokens\": {\n \"display_name\": \"Max Tokens\",\n \"advanced\": True,\n \"info\": \"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n },\n \"model_kwargs\": {\n \"display_name\": \"Model Kwargs\",\n \"advanced\": True,\n },\n \"model_name\": {\n \"display_name\": \"Model Name\",\n \"advanced\": False,\n \"options\": MODEL_NAMES,\n },\n \"openai_api_base\": {\n \"display_name\": \"OpenAI API Base\",\n \"advanced\": True,\n \"info\": (\n \"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\\n\\n\"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\"\n ),\n },\n \"openai_api_key\": {\n \"display_name\": \"OpenAI API Key\",\n \"info\": \"The OpenAI API Key to use for the OpenAI model.\",\n \"advanced\": False,\n \"password\": True,\n },\n \"temperature\": {\n \"display_name\": \"Temperature\",\n \"advanced\": False,\n \"value\": 0.1,\n },\n \"stream\": {\n \"display_name\": \"Stream\",\n \"info\": STREAM_INFO_TEXT,\n \"advanced\": True,\n },\n \"system_message\": {\n \"display_name\": \"System Message\",\n \"info\": \"System message to pass to the model.\",\n \"advanced\": True,\n },\n }\n\n def build(\n self,\n input_value: Text | Record,\n openai_api_key: str,\n temperature: float = 0.1,\n model_name: str = \"gpt-4o\",\n max_tokens: Optional[int] = 256,\n model_kwargs: NestedDict = {},\n openai_api_base: Optional[str] = None,\n stream: bool = False,\n system_message: Optional[str] = None,\n ) -> Text:\n if not openai_api_base:\n openai_api_base = \"https://api.openai.com/v1\"\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature,\n )\n\n return self.get_chat_result(output, stream, input_value, system_message)\n",
|
"value": "from typing import Optional\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import MODEL_NAMES\nfrom langflow.field_typing import NestedDict, Text\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n field_order = [\n \"max_tokens\",\n \"model_kwargs\",\n \"model_name\",\n \"openai_api_base\",\n \"openai_api_key\",\n \"temperature\",\n \"input_value\",\n \"system_message\",\n \"stream\",\n ]\n\n def build_config(self):\n return {\n \"input_value\": {\"display_name\": \"Input\"},\n \"max_tokens\": {\n \"display_name\": \"Max Tokens\",\n \"advanced\": True,\n \"info\": \"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n },\n \"model_kwargs\": {\n \"display_name\": \"Model Kwargs\",\n \"advanced\": True,\n },\n \"model_name\": {\n \"display_name\": \"Model Name\",\n \"advanced\": False,\n \"options\": MODEL_NAMES,\n },\n \"openai_api_base\": {\n \"display_name\": \"OpenAI API Base\",\n \"advanced\": True,\n \"info\": (\n \"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\\n\\n\"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\"\n ),\n },\n \"openai_api_key\": {\n \"display_name\": \"OpenAI API Key\",\n \"info\": \"The OpenAI API Key to use for the OpenAI model.\",\n \"advanced\": False,\n \"password\": True,\n },\n \"temperature\": {\n \"display_name\": \"Temperature\",\n \"advanced\": False,\n \"value\": 0.1,\n },\n \"stream\": {\n \"display_name\": \"Stream\",\n \"info\": STREAM_INFO_TEXT,\n \"advanced\": True,\n },\n \"system_message\": {\n \"display_name\": \"System Message\",\n \"info\": \"System message to pass to the model.\",\n \"advanced\": True,\n },\n }\n\n def build(\n self,\n input_value: Text,\n openai_api_key: str,\n temperature: float = 0.1,\n model_name: str = \"gpt-4o\",\n max_tokens: Optional[int] = 256,\n model_kwargs: NestedDict = {},\n openai_api_base: Optional[str] = None,\n stream: bool = False,\n system_message: Optional[str] = None,\n ) -> Text:\n if not openai_api_base:\n openai_api_base = \"https://api.openai.com/v1\"\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature,\n )\n\n return self.get_chat_result(output, stream, input_value, system_message)\n",
|
||||||
"fileTypes": [],
|
"fileTypes": [],
|
||||||
"file_path": "",
|
"file_path": "",
|
||||||
"password": false,
|
"password": false,
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
"list": false,
|
"list": false,
|
||||||
"show": true,
|
"show": true,
|
||||||
"multiline": true,
|
"multiline": true,
|
||||||
"value": "from itertools import chain\n\n\nfrom langchain_core.prompts import ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n",
|
"value": "from langchain_core.prompts import ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n",
|
||||||
"fileTypes": [],
|
"fileTypes": [],
|
||||||
"file_path": "",
|
"file_path": "",
|
||||||
"password": false,
|
"password": false,
|
||||||
|
|
@ -641,8 +641,7 @@
|
||||||
"load_from_db": false,
|
"load_from_db": false,
|
||||||
"title_case": false,
|
"title_case": false,
|
||||||
"input_types": [
|
"input_types": [
|
||||||
"Text",
|
"Text"
|
||||||
"Record"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"code": {
|
"code": {
|
||||||
|
|
@ -652,7 +651,7 @@
|
||||||
"list": false,
|
"list": false,
|
||||||
"show": true,
|
"show": true,
|
||||||
"multiline": true,
|
"multiline": true,
|
||||||
"value": "from typing import Optional\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import MODEL_NAMES\nfrom langflow.field_typing import NestedDict, Text\nfrom langflow.schema.schema import Record\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n field_order = [\n \"max_tokens\",\n \"model_kwargs\",\n \"model_name\",\n \"openai_api_base\",\n \"openai_api_key\",\n \"temperature\",\n \"input_value\",\n \"system_message\",\n \"stream\",\n ]\n\n def build_config(self):\n return {\n \"input_value\": {\"display_name\": \"Input\", \"input_types\": [\"Text\", \"Record\"]},\n \"max_tokens\": {\n \"display_name\": \"Max Tokens\",\n \"advanced\": True,\n \"info\": \"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n },\n \"model_kwargs\": {\n \"display_name\": \"Model Kwargs\",\n \"advanced\": True,\n },\n \"model_name\": {\n \"display_name\": \"Model Name\",\n \"advanced\": False,\n \"options\": MODEL_NAMES,\n },\n \"openai_api_base\": {\n \"display_name\": \"OpenAI API Base\",\n \"advanced\": True,\n \"info\": (\n \"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\\n\\n\"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\"\n ),\n },\n \"openai_api_key\": {\n \"display_name\": \"OpenAI API Key\",\n \"info\": \"The OpenAI API Key to use for the OpenAI model.\",\n \"advanced\": False,\n \"password\": True,\n },\n \"temperature\": {\n \"display_name\": \"Temperature\",\n \"advanced\": False,\n \"value\": 0.1,\n },\n \"stream\": {\n \"display_name\": \"Stream\",\n \"info\": STREAM_INFO_TEXT,\n \"advanced\": True,\n },\n \"system_message\": {\n \"display_name\": \"System Message\",\n \"info\": \"System message to pass to the model.\",\n \"advanced\": True,\n },\n }\n\n def build(\n self,\n input_value: Text | Record,\n openai_api_key: str,\n temperature: float = 0.1,\n model_name: str = \"gpt-4o\",\n max_tokens: Optional[int] = 256,\n model_kwargs: NestedDict = {},\n openai_api_base: Optional[str] = None,\n stream: bool = False,\n system_message: Optional[str] = None,\n ) -> Text:\n if not openai_api_base:\n openai_api_base = \"https://api.openai.com/v1\"\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature,\n )\n\n return self.get_chat_result(output, stream, input_value, system_message)\n",
|
"value": "from typing import Optional\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import MODEL_NAMES\nfrom langflow.field_typing import NestedDict, Text\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n field_order = [\n \"max_tokens\",\n \"model_kwargs\",\n \"model_name\",\n \"openai_api_base\",\n \"openai_api_key\",\n \"temperature\",\n \"input_value\",\n \"system_message\",\n \"stream\",\n ]\n\n def build_config(self):\n return {\n \"input_value\": {\"display_name\": \"Input\"},\n \"max_tokens\": {\n \"display_name\": \"Max Tokens\",\n \"advanced\": True,\n \"info\": \"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n },\n \"model_kwargs\": {\n \"display_name\": \"Model Kwargs\",\n \"advanced\": True,\n },\n \"model_name\": {\n \"display_name\": \"Model Name\",\n \"advanced\": False,\n \"options\": MODEL_NAMES,\n },\n \"openai_api_base\": {\n \"display_name\": \"OpenAI API Base\",\n \"advanced\": True,\n \"info\": (\n \"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\\n\\n\"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\"\n ),\n },\n \"openai_api_key\": {\n \"display_name\": \"OpenAI API Key\",\n \"info\": \"The OpenAI API Key to use for the OpenAI model.\",\n \"advanced\": False,\n \"password\": True,\n },\n \"temperature\": {\n \"display_name\": \"Temperature\",\n \"advanced\": False,\n \"value\": 0.1,\n },\n \"stream\": {\n \"display_name\": \"Stream\",\n \"info\": STREAM_INFO_TEXT,\n \"advanced\": True,\n },\n \"system_message\": {\n \"display_name\": \"System Message\",\n \"info\": \"System message to pass to the model.\",\n \"advanced\": True,\n },\n }\n\n def build(\n self,\n input_value: Text,\n openai_api_key: str,\n temperature: float = 0.1,\n model_name: str = \"gpt-4o\",\n max_tokens: Optional[int] = 256,\n model_kwargs: NestedDict = {},\n openai_api_base: Optional[str] = None,\n stream: bool = False,\n system_message: Optional[str] = None,\n ) -> Text:\n if not openai_api_base:\n openai_api_base = \"https://api.openai.com/v1\"\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature,\n )\n\n return self.get_chat_result(output, stream, input_value, system_message)\n",
|
||||||
"fileTypes": [],
|
"fileTypes": [],
|
||||||
"file_path": "",
|
"file_path": "",
|
||||||
"password": false,
|
"password": false,
|
||||||
|
|
|
||||||
|
|
@ -583,7 +583,7 @@
|
||||||
"list": false,
|
"list": false,
|
||||||
"show": true,
|
"show": true,
|
||||||
"multiline": true,
|
"multiline": true,
|
||||||
"value": "from itertools import chain\n\n\nfrom langchain_core.prompts import ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n",
|
"value": "from langchain_core.prompts import ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n",
|
||||||
"fileTypes": [],
|
"fileTypes": [],
|
||||||
"file_path": "",
|
"file_path": "",
|
||||||
"password": false,
|
"password": false,
|
||||||
|
|
@ -741,8 +741,7 @@
|
||||||
"load_from_db": false,
|
"load_from_db": false,
|
||||||
"title_case": false,
|
"title_case": false,
|
||||||
"input_types": [
|
"input_types": [
|
||||||
"Text",
|
"Text"
|
||||||
"Record"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"code": {
|
"code": {
|
||||||
|
|
@ -752,7 +751,7 @@
|
||||||
"list": false,
|
"list": false,
|
||||||
"show": true,
|
"show": true,
|
||||||
"multiline": true,
|
"multiline": true,
|
||||||
"value": "from typing import Optional\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import MODEL_NAMES\nfrom langflow.field_typing import NestedDict, Text\nfrom langflow.schema.schema import Record\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n field_order = [\n \"max_tokens\",\n \"model_kwargs\",\n \"model_name\",\n \"openai_api_base\",\n \"openai_api_key\",\n \"temperature\",\n \"input_value\",\n \"system_message\",\n \"stream\",\n ]\n\n def build_config(self):\n return {\n \"input_value\": {\"display_name\": \"Input\", \"input_types\": [\"Text\", \"Record\"]},\n \"max_tokens\": {\n \"display_name\": \"Max Tokens\",\n \"advanced\": True,\n \"info\": \"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n },\n \"model_kwargs\": {\n \"display_name\": \"Model Kwargs\",\n \"advanced\": True,\n },\n \"model_name\": {\n \"display_name\": \"Model Name\",\n \"advanced\": False,\n \"options\": MODEL_NAMES,\n },\n \"openai_api_base\": {\n \"display_name\": \"OpenAI API Base\",\n \"advanced\": True,\n \"info\": (\n \"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\\n\\n\"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\"\n ),\n },\n \"openai_api_key\": {\n \"display_name\": \"OpenAI API Key\",\n \"info\": \"The OpenAI API Key to use for the OpenAI model.\",\n \"advanced\": False,\n \"password\": True,\n },\n \"temperature\": {\n \"display_name\": \"Temperature\",\n \"advanced\": False,\n \"value\": 0.1,\n },\n \"stream\": {\n \"display_name\": \"Stream\",\n \"info\": STREAM_INFO_TEXT,\n \"advanced\": True,\n },\n \"system_message\": {\n \"display_name\": \"System Message\",\n \"info\": \"System message to pass to the model.\",\n \"advanced\": True,\n },\n }\n\n def build(\n self,\n input_value: Text | Record,\n openai_api_key: str,\n temperature: float = 0.1,\n model_name: str = \"gpt-4o\",\n max_tokens: Optional[int] = 256,\n model_kwargs: NestedDict = {},\n openai_api_base: Optional[str] = None,\n stream: bool = False,\n system_message: Optional[str] = None,\n ) -> Text:\n if not openai_api_base:\n openai_api_base = \"https://api.openai.com/v1\"\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature,\n )\n\n return self.get_chat_result(output, stream, input_value, system_message)\n",
|
"value": "from typing import Optional\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import MODEL_NAMES\nfrom langflow.field_typing import NestedDict, Text\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n field_order = [\n \"max_tokens\",\n \"model_kwargs\",\n \"model_name\",\n \"openai_api_base\",\n \"openai_api_key\",\n \"temperature\",\n \"input_value\",\n \"system_message\",\n \"stream\",\n ]\n\n def build_config(self):\n return {\n \"input_value\": {\"display_name\": \"Input\"},\n \"max_tokens\": {\n \"display_name\": \"Max Tokens\",\n \"advanced\": True,\n \"info\": \"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n },\n \"model_kwargs\": {\n \"display_name\": \"Model Kwargs\",\n \"advanced\": True,\n },\n \"model_name\": {\n \"display_name\": \"Model Name\",\n \"advanced\": False,\n \"options\": MODEL_NAMES,\n },\n \"openai_api_base\": {\n \"display_name\": \"OpenAI API Base\",\n \"advanced\": True,\n \"info\": (\n \"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\\n\\n\"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\"\n ),\n },\n \"openai_api_key\": {\n \"display_name\": \"OpenAI API Key\",\n \"info\": \"The OpenAI API Key to use for the OpenAI model.\",\n \"advanced\": False,\n \"password\": True,\n },\n \"temperature\": {\n \"display_name\": \"Temperature\",\n \"advanced\": False,\n \"value\": 0.1,\n },\n \"stream\": {\n \"display_name\": \"Stream\",\n \"info\": STREAM_INFO_TEXT,\n \"advanced\": True,\n },\n \"system_message\": {\n \"display_name\": \"System Message\",\n \"info\": \"System message to pass to the model.\",\n \"advanced\": True,\n },\n }\n\n def build(\n self,\n input_value: Text,\n openai_api_key: str,\n temperature: float = 0.1,\n model_name: str = \"gpt-4o\",\n max_tokens: Optional[int] = 256,\n model_kwargs: NestedDict = {},\n openai_api_base: Optional[str] = None,\n stream: bool = False,\n system_message: Optional[str] = None,\n ) -> Text:\n if not openai_api_base:\n openai_api_base = \"https://api.openai.com/v1\"\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature,\n )\n\n return self.get_chat_result(output, stream, input_value, system_message)\n",
|
||||||
"fileTypes": [],
|
"fileTypes": [],
|
||||||
"file_path": "",
|
"file_path": "",
|
||||||
"password": false,
|
"password": false,
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
"list": false,
|
"list": false,
|
||||||
"show": true,
|
"show": true,
|
||||||
"multiline": true,
|
"multiline": true,
|
||||||
"value": "from itertools import chain\n\n\nfrom langchain_core.prompts import ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n",
|
"value": "from langchain_core.prompts import ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n",
|
||||||
"fileTypes": [],
|
"fileTypes": [],
|
||||||
"file_path": "",
|
"file_path": "",
|
||||||
"password": false,
|
"password": false,
|
||||||
|
|
@ -140,7 +140,7 @@
|
||||||
"list": false,
|
"list": false,
|
||||||
"show": true,
|
"show": true,
|
||||||
"multiline": true,
|
"multiline": true,
|
||||||
"value": "from itertools import chain\n\n\nfrom langchain_core.prompts import ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n",
|
"value": "from langchain_core.prompts import ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n",
|
||||||
"fileTypes": [],
|
"fileTypes": [],
|
||||||
"file_path": "",
|
"file_path": "",
|
||||||
"password": false,
|
"password": false,
|
||||||
|
|
@ -874,8 +874,7 @@
|
||||||
"load_from_db": false,
|
"load_from_db": false,
|
||||||
"title_case": false,
|
"title_case": false,
|
||||||
"input_types": [
|
"input_types": [
|
||||||
"Text",
|
"Text"
|
||||||
"Record"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"code": {
|
"code": {
|
||||||
|
|
@ -885,7 +884,7 @@
|
||||||
"list": false,
|
"list": false,
|
||||||
"show": true,
|
"show": true,
|
||||||
"multiline": true,
|
"multiline": true,
|
||||||
"value": "from typing import Optional\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import MODEL_NAMES\nfrom langflow.field_typing import NestedDict, Text\nfrom langflow.schema.schema import Record\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n field_order = [\n \"max_tokens\",\n \"model_kwargs\",\n \"model_name\",\n \"openai_api_base\",\n \"openai_api_key\",\n \"temperature\",\n \"input_value\",\n \"system_message\",\n \"stream\",\n ]\n\n def build_config(self):\n return {\n \"input_value\": {\"display_name\": \"Input\", \"input_types\": [\"Text\", \"Record\"]},\n \"max_tokens\": {\n \"display_name\": \"Max Tokens\",\n \"advanced\": True,\n \"info\": \"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n },\n \"model_kwargs\": {\n \"display_name\": \"Model Kwargs\",\n \"advanced\": True,\n },\n \"model_name\": {\n \"display_name\": \"Model Name\",\n \"advanced\": False,\n \"options\": MODEL_NAMES,\n },\n \"openai_api_base\": {\n \"display_name\": \"OpenAI API Base\",\n \"advanced\": True,\n \"info\": (\n \"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\\n\\n\"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\"\n ),\n },\n \"openai_api_key\": {\n \"display_name\": \"OpenAI API Key\",\n \"info\": \"The OpenAI API Key to use for the OpenAI model.\",\n \"advanced\": False,\n \"password\": True,\n },\n \"temperature\": {\n \"display_name\": \"Temperature\",\n \"advanced\": False,\n \"value\": 0.1,\n },\n \"stream\": {\n \"display_name\": \"Stream\",\n \"info\": STREAM_INFO_TEXT,\n \"advanced\": True,\n },\n \"system_message\": {\n \"display_name\": \"System Message\",\n \"info\": \"System message to pass to the model.\",\n \"advanced\": True,\n },\n }\n\n def build(\n self,\n input_value: Text | Record,\n openai_api_key: str,\n temperature: float = 0.1,\n model_name: str = \"gpt-4o\",\n max_tokens: Optional[int] = 256,\n model_kwargs: NestedDict = {},\n openai_api_base: Optional[str] = None,\n stream: bool = False,\n system_message: Optional[str] = None,\n ) -> Text:\n if not openai_api_base:\n openai_api_base = \"https://api.openai.com/v1\"\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature,\n )\n\n return self.get_chat_result(output, stream, input_value, system_message)\n",
|
"value": "from typing import Optional\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import MODEL_NAMES\nfrom langflow.field_typing import NestedDict, Text\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n field_order = [\n \"max_tokens\",\n \"model_kwargs\",\n \"model_name\",\n \"openai_api_base\",\n \"openai_api_key\",\n \"temperature\",\n \"input_value\",\n \"system_message\",\n \"stream\",\n ]\n\n def build_config(self):\n return {\n \"input_value\": {\"display_name\": \"Input\"},\n \"max_tokens\": {\n \"display_name\": \"Max Tokens\",\n \"advanced\": True,\n \"info\": \"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n },\n \"model_kwargs\": {\n \"display_name\": \"Model Kwargs\",\n \"advanced\": True,\n },\n \"model_name\": {\n \"display_name\": \"Model Name\",\n \"advanced\": False,\n \"options\": MODEL_NAMES,\n },\n \"openai_api_base\": {\n \"display_name\": \"OpenAI API Base\",\n \"advanced\": True,\n \"info\": (\n \"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\\n\\n\"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\"\n ),\n },\n \"openai_api_key\": {\n \"display_name\": \"OpenAI API Key\",\n \"info\": \"The OpenAI API Key to use for the OpenAI model.\",\n \"advanced\": False,\n \"password\": True,\n },\n \"temperature\": {\n \"display_name\": \"Temperature\",\n \"advanced\": False,\n \"value\": 0.1,\n },\n \"stream\": {\n \"display_name\": \"Stream\",\n \"info\": STREAM_INFO_TEXT,\n \"advanced\": True,\n },\n \"system_message\": {\n \"display_name\": \"System Message\",\n \"info\": \"System message to pass to the model.\",\n \"advanced\": True,\n },\n }\n\n def build(\n self,\n input_value: Text,\n openai_api_key: str,\n temperature: float = 0.1,\n model_name: str = \"gpt-4o\",\n max_tokens: Optional[int] = 256,\n model_kwargs: NestedDict = {},\n openai_api_base: Optional[str] = None,\n stream: bool = False,\n system_message: Optional[str] = None,\n ) -> Text:\n if not openai_api_base:\n openai_api_base = \"https://api.openai.com/v1\"\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature,\n )\n\n return self.get_chat_result(output, stream, input_value, system_message)\n",
|
||||||
"fileTypes": [],
|
"fileTypes": [],
|
||||||
"file_path": "",
|
"file_path": "",
|
||||||
"password": false,
|
"password": false,
|
||||||
|
|
@ -1259,8 +1258,7 @@
|
||||||
"load_from_db": false,
|
"load_from_db": false,
|
||||||
"title_case": false,
|
"title_case": false,
|
||||||
"input_types": [
|
"input_types": [
|
||||||
"Text",
|
"Text"
|
||||||
"Record"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"code": {
|
"code": {
|
||||||
|
|
@ -1270,7 +1268,7 @@
|
||||||
"list": false,
|
"list": false,
|
||||||
"show": true,
|
"show": true,
|
||||||
"multiline": true,
|
"multiline": true,
|
||||||
"value": "from typing import Optional\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import MODEL_NAMES\nfrom langflow.field_typing import NestedDict, Text\nfrom langflow.schema.schema import Record\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n field_order = [\n \"max_tokens\",\n \"model_kwargs\",\n \"model_name\",\n \"openai_api_base\",\n \"openai_api_key\",\n \"temperature\",\n \"input_value\",\n \"system_message\",\n \"stream\",\n ]\n\n def build_config(self):\n return {\n \"input_value\": {\"display_name\": \"Input\", \"input_types\": [\"Text\", \"Record\"]},\n \"max_tokens\": {\n \"display_name\": \"Max Tokens\",\n \"advanced\": True,\n \"info\": \"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n },\n \"model_kwargs\": {\n \"display_name\": \"Model Kwargs\",\n \"advanced\": True,\n },\n \"model_name\": {\n \"display_name\": \"Model Name\",\n \"advanced\": False,\n \"options\": MODEL_NAMES,\n },\n \"openai_api_base\": {\n \"display_name\": \"OpenAI API Base\",\n \"advanced\": True,\n \"info\": (\n \"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\\n\\n\"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\"\n ),\n },\n \"openai_api_key\": {\n \"display_name\": \"OpenAI API Key\",\n \"info\": \"The OpenAI API Key to use for the OpenAI model.\",\n \"advanced\": False,\n \"password\": True,\n },\n \"temperature\": {\n \"display_name\": \"Temperature\",\n \"advanced\": False,\n \"value\": 0.1,\n },\n \"stream\": {\n \"display_name\": \"Stream\",\n \"info\": STREAM_INFO_TEXT,\n \"advanced\": True,\n },\n \"system_message\": {\n \"display_name\": \"System Message\",\n \"info\": \"System message to pass to the model.\",\n \"advanced\": True,\n },\n }\n\n def build(\n self,\n input_value: Text | Record,\n openai_api_key: str,\n temperature: float = 0.1,\n model_name: str = \"gpt-4o\",\n max_tokens: Optional[int] = 256,\n model_kwargs: NestedDict = {},\n openai_api_base: Optional[str] = None,\n stream: bool = False,\n system_message: Optional[str] = None,\n ) -> Text:\n if not openai_api_base:\n openai_api_base = \"https://api.openai.com/v1\"\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature,\n )\n\n return self.get_chat_result(output, stream, input_value, system_message)\n",
|
"value": "from typing import Optional\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import MODEL_NAMES\nfrom langflow.field_typing import NestedDict, Text\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n field_order = [\n \"max_tokens\",\n \"model_kwargs\",\n \"model_name\",\n \"openai_api_base\",\n \"openai_api_key\",\n \"temperature\",\n \"input_value\",\n \"system_message\",\n \"stream\",\n ]\n\n def build_config(self):\n return {\n \"input_value\": {\"display_name\": \"Input\"},\n \"max_tokens\": {\n \"display_name\": \"Max Tokens\",\n \"advanced\": True,\n \"info\": \"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n },\n \"model_kwargs\": {\n \"display_name\": \"Model Kwargs\",\n \"advanced\": True,\n },\n \"model_name\": {\n \"display_name\": \"Model Name\",\n \"advanced\": False,\n \"options\": MODEL_NAMES,\n },\n \"openai_api_base\": {\n \"display_name\": \"OpenAI API Base\",\n \"advanced\": True,\n \"info\": (\n \"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\\n\\n\"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\"\n ),\n },\n \"openai_api_key\": {\n \"display_name\": \"OpenAI API Key\",\n \"info\": \"The OpenAI API Key to use for the OpenAI model.\",\n \"advanced\": False,\n \"password\": True,\n },\n \"temperature\": {\n \"display_name\": \"Temperature\",\n \"advanced\": False,\n \"value\": 0.1,\n },\n \"stream\": {\n \"display_name\": \"Stream\",\n \"info\": STREAM_INFO_TEXT,\n \"advanced\": True,\n },\n \"system_message\": {\n \"display_name\": \"System Message\",\n \"info\": \"System message to pass to the model.\",\n \"advanced\": True,\n },\n }\n\n def build(\n self,\n input_value: Text,\n openai_api_key: str,\n temperature: float = 0.1,\n model_name: str = \"gpt-4o\",\n max_tokens: Optional[int] = 256,\n model_kwargs: NestedDict = {},\n openai_api_base: Optional[str] = None,\n stream: bool = False,\n system_message: Optional[str] = None,\n ) -> Text:\n if not openai_api_base:\n openai_api_base = \"https://api.openai.com/v1\"\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature,\n )\n\n return self.get_chat_result(output, stream, input_value, system_message)\n",
|
||||||
"fileTypes": [],
|
"fileTypes": [],
|
||||||
"file_path": "",
|
"file_path": "",
|
||||||
"password": false,
|
"password": false,
|
||||||
|
|
|
||||||
|
|
@ -841,8 +841,7 @@
|
||||||
"load_from_db": false,
|
"load_from_db": false,
|
||||||
"title_case": false,
|
"title_case": false,
|
||||||
"input_types": [
|
"input_types": [
|
||||||
"Text",
|
"Text"
|
||||||
"Record"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"code": {
|
"code": {
|
||||||
|
|
@ -852,7 +851,7 @@
|
||||||
"list": false,
|
"list": false,
|
||||||
"show": true,
|
"show": true,
|
||||||
"multiline": true,
|
"multiline": true,
|
||||||
"value": "from typing import Optional\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import MODEL_NAMES\nfrom langflow.field_typing import NestedDict, Text\nfrom langflow.schema.schema import Record\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n field_order = [\n \"max_tokens\",\n \"model_kwargs\",\n \"model_name\",\n \"openai_api_base\",\n \"openai_api_key\",\n \"temperature\",\n \"input_value\",\n \"system_message\",\n \"stream\",\n ]\n\n def build_config(self):\n return {\n \"input_value\": {\"display_name\": \"Input\", \"input_types\": [\"Text\", \"Record\"]},\n \"max_tokens\": {\n \"display_name\": \"Max Tokens\",\n \"advanced\": True,\n \"info\": \"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n },\n \"model_kwargs\": {\n \"display_name\": \"Model Kwargs\",\n \"advanced\": True,\n },\n \"model_name\": {\n \"display_name\": \"Model Name\",\n \"advanced\": False,\n \"options\": MODEL_NAMES,\n },\n \"openai_api_base\": {\n \"display_name\": \"OpenAI API Base\",\n \"advanced\": True,\n \"info\": (\n \"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\\n\\n\"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\"\n ),\n },\n \"openai_api_key\": {\n \"display_name\": \"OpenAI API Key\",\n \"info\": \"The OpenAI API Key to use for the OpenAI model.\",\n \"advanced\": False,\n \"password\": True,\n },\n \"temperature\": {\n \"display_name\": \"Temperature\",\n \"advanced\": False,\n \"value\": 0.1,\n },\n \"stream\": {\n \"display_name\": \"Stream\",\n \"info\": STREAM_INFO_TEXT,\n \"advanced\": True,\n },\n \"system_message\": {\n \"display_name\": \"System Message\",\n \"info\": \"System message to pass to the model.\",\n \"advanced\": True,\n },\n }\n\n def build(\n self,\n input_value: Text | Record,\n openai_api_key: str,\n temperature: float = 0.1,\n model_name: str = \"gpt-4o\",\n max_tokens: Optional[int] = 256,\n model_kwargs: NestedDict = {},\n openai_api_base: Optional[str] = None,\n stream: bool = False,\n system_message: Optional[str] = None,\n ) -> Text:\n if not openai_api_base:\n openai_api_base = \"https://api.openai.com/v1\"\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature,\n )\n\n return self.get_chat_result(output, stream, input_value, system_message)\n",
|
"value": "from typing import Optional\n\nfrom langchain_openai import ChatOpenAI\nfrom pydantic.v1 import SecretStr\n\nfrom langflow.base.constants import STREAM_INFO_TEXT\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.base.models.openai_constants import MODEL_NAMES\nfrom langflow.field_typing import NestedDict, Text\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI LLMs.\"\n icon = \"OpenAI\"\n\n field_order = [\n \"max_tokens\",\n \"model_kwargs\",\n \"model_name\",\n \"openai_api_base\",\n \"openai_api_key\",\n \"temperature\",\n \"input_value\",\n \"system_message\",\n \"stream\",\n ]\n\n def build_config(self):\n return {\n \"input_value\": {\"display_name\": \"Input\"},\n \"max_tokens\": {\n \"display_name\": \"Max Tokens\",\n \"advanced\": True,\n \"info\": \"The maximum number of tokens to generate. Set to 0 for unlimited tokens.\",\n },\n \"model_kwargs\": {\n \"display_name\": \"Model Kwargs\",\n \"advanced\": True,\n },\n \"model_name\": {\n \"display_name\": \"Model Name\",\n \"advanced\": False,\n \"options\": MODEL_NAMES,\n },\n \"openai_api_base\": {\n \"display_name\": \"OpenAI API Base\",\n \"advanced\": True,\n \"info\": (\n \"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\\n\\n\"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\"\n ),\n },\n \"openai_api_key\": {\n \"display_name\": \"OpenAI API Key\",\n \"info\": \"The OpenAI API Key to use for the OpenAI model.\",\n \"advanced\": False,\n \"password\": True,\n },\n \"temperature\": {\n \"display_name\": \"Temperature\",\n \"advanced\": False,\n \"value\": 0.1,\n },\n \"stream\": {\n \"display_name\": \"Stream\",\n \"info\": STREAM_INFO_TEXT,\n \"advanced\": True,\n },\n \"system_message\": {\n \"display_name\": \"System Message\",\n \"info\": \"System message to pass to the model.\",\n \"advanced\": True,\n },\n }\n\n def build(\n self,\n input_value: Text,\n openai_api_key: str,\n temperature: float = 0.1,\n model_name: str = \"gpt-4o\",\n max_tokens: Optional[int] = 256,\n model_kwargs: NestedDict = {},\n openai_api_base: Optional[str] = None,\n stream: bool = False,\n system_message: Optional[str] = None,\n ) -> Text:\n if not openai_api_base:\n openai_api_base = \"https://api.openai.com/v1\"\n if openai_api_key:\n api_key = SecretStr(openai_api_key)\n else:\n api_key = None\n\n output = ChatOpenAI(\n max_tokens=max_tokens or None,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=api_key,\n temperature=temperature,\n )\n\n return self.get_chat_result(output, stream, input_value, system_message)\n",
|
||||||
"fileTypes": [],
|
"fileTypes": [],
|
||||||
"file_path": "",
|
"file_path": "",
|
||||||
"password": false,
|
"password": false,
|
||||||
|
|
@ -1107,7 +1106,7 @@
|
||||||
"list": false,
|
"list": false,
|
||||||
"show": true,
|
"show": true,
|
||||||
"multiline": true,
|
"multiline": true,
|
||||||
"value": "from itertools import chain\n\n\nfrom langchain_core.prompts import ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n",
|
"value": "from langchain_core.prompts import ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n",
|
||||||
"fileTypes": [],
|
"fileTypes": [],
|
||||||
"file_path": "",
|
"file_path": "",
|
||||||
"password": false,
|
"password": false,
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ class ApiKeyRead(ApiKeyBase):
|
||||||
id: UUID
|
id: UUID
|
||||||
api_key: str = Field(schema_extra={"validate_default": True})
|
api_key: str = Field(schema_extra={"validate_default": True})
|
||||||
user_id: UUID = Field()
|
user_id: UUID = Field()
|
||||||
|
created_at: datetime = Field()
|
||||||
|
|
||||||
@field_validator("api_key")
|
@field_validator("api_key")
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
||||||
12
src/backend/base/poetry.lock
generated
12
src/backend/base/poetry.lock
generated
|
|
@ -1296,13 +1296,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 +1600,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]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "langflow-base"
|
name = "langflow-base"
|
||||||
version = "0.0.57"
|
version = "0.0.59"
|
||||||
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 = [
|
||||||
|
|
|
||||||
33
src/frontend/package-lock.json
generated
33
src/frontend/package-lock.json
generated
|
|
@ -41,9 +41,9 @@
|
||||||
"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",
|
||||||
"esbuild": "^0.17.19",
|
"esbuild": "^0.17.19",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"framer-motion": "^11.0.6",
|
"framer-motion": "^11.0.6",
|
||||||
|
|
@ -52,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",
|
||||||
|
|
@ -5697,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",
|
||||||
|
|
@ -5997,9 +5993,9 @@
|
||||||
"integrity": "sha512-NPtACGFe7vunRYzvYqVRhQvsDrTevxpgDKxG/Vcbe0BTNOY+5+/2mOXSw2ls7ToNbE5Bf/+uQbjTxcmwMozpCw=="
|
"integrity": "sha512-NPtACGFe7vunRYzvYqVRhQvsDrTevxpgDKxG/Vcbe0BTNOY+5+/2mOXSw2ls7ToNbE5Bf/+uQbjTxcmwMozpCw=="
|
||||||
},
|
},
|
||||||
"node_modules/emoji-regex": {
|
"node_modules/emoji-regex": {
|
||||||
"version": "8.0.0",
|
"version": "10.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz",
|
||||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
"integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw=="
|
||||||
},
|
},
|
||||||
"node_modules/end-of-stream": {
|
"node_modules/end-of-stream": {
|
||||||
"version": "1.4.4",
|
"version": "1.4.4",
|
||||||
|
|
@ -10050,6 +10046,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",
|
||||||
|
|
@ -12205,6 +12210,16 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/string-width-cjs/node_modules/emoji-regex": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||||
|
},
|
||||||
|
"node_modules/string-width/node_modules/emoji-regex": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||||
|
},
|
||||||
"node_modules/strip-ansi": {
|
"node_modules/strip-ansi": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,9 @@
|
||||||
"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",
|
||||||
"esbuild": "^0.17.19",
|
"esbuild": "^0.17.19",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"framer-motion": "^11.0.6",
|
"framer-motion": "^11.0.6",
|
||||||
|
|
@ -47,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",
|
||||||
|
|
|
||||||
|
|
@ -164,3 +164,13 @@ body {
|
||||||
.ag-body-vertical-scroll-viewport::-webkit-scrollbar-thumb:hover {
|
.ag-body-vertical-scroll-viewport::-webkit-scrollbar-thumb:hover {
|
||||||
background-color: #bbb;
|
background-color: #bbb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* This CSS is to not apply the border for the column having 'no-border' class */
|
||||||
|
.no-border.ag-cell:focus {
|
||||||
|
border: none !important;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.no-border.ag-cell {
|
||||||
|
border: none !important;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,6 @@ export default function ParameterComponent({
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const refHtml = useRef<HTMLDivElement & ReactNode>(null);
|
const refHtml = useRef<HTMLDivElement & ReactNode>(null);
|
||||||
const infoHtml = useRef<HTMLDivElement & ReactNode>(null);
|
const infoHtml = useRef<HTMLDivElement & ReactNode>(null);
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
|
||||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||||
const nodes = useFlowStore((state) => state.nodes);
|
const nodes = useFlowStore((state) => state.nodes);
|
||||||
const edges = useFlowStore((state) => state.edges);
|
const edges = useFlowStore((state) => state.edges);
|
||||||
|
|
@ -392,7 +391,7 @@ export default function ParameterComponent({
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
name={name}
|
name={name}
|
||||||
data={data}
|
data={data.node?.template[name]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{data.node?.template[name]?.refresh_button && (
|
{data.node?.template[name]?.refresh_button && (
|
||||||
|
|
@ -448,8 +447,8 @@ export default function ParameterComponent({
|
||||||
data.node?.template[name]?.real_time_refresh)
|
data.node?.template[name]?.real_time_refresh)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="mt-2 flex w-full items-center">
|
<div className="mt-2 flex w-full items-center gap-2">
|
||||||
<div className="w-5/6 flex-grow">
|
<div className="flex-1">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
|
@ -467,7 +466,6 @@ export default function ParameterComponent({
|
||||||
name={name}
|
name={name}
|
||||||
data={data}
|
data={data}
|
||||||
button_text={data.node?.template[name]?.refresh_button_text}
|
button_text={data.node?.template[name]?.refresh_button_text}
|
||||||
className="extra-side-bar-buttons ml-2 mt-1"
|
|
||||||
handleUpdateValues={handleRefreshButtonPress}
|
handleUpdateValues={handleRefreshButtonPress}
|
||||||
id={"refresh-button-" + name}
|
id={"refresh-button-" + name}
|
||||||
/>
|
/>
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import emojiRegex from "emoji-regex";
|
||||||
import { cloneDeep } from "lodash";
|
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";
|
||||||
|
|
@ -225,8 +226,7 @@ export default function GenericNode({
|
||||||
|
|
||||||
const nameEditable = true;
|
const nameEditable = true;
|
||||||
|
|
||||||
const emojiRegex = /\p{Emoji}/u;
|
const isEmoji = emojiRegex().test(data?.node?.icon!);
|
||||||
const isEmoji = emojiRegex.test(data?.node?.icon!);
|
|
||||||
|
|
||||||
const iconNodeRender = useCallback(() => {
|
const iconNodeRender = useCallback(() => {
|
||||||
const iconElement = data?.node?.icon;
|
const iconElement = data?.node?.icon;
|
||||||
|
|
@ -262,6 +262,15 @@ export default function GenericNode({
|
||||||
buildStatus: BuildStatus | undefined,
|
buildStatus: BuildStatus | undefined,
|
||||||
validationStatus: VertexBuildTypeAPI | null
|
validationStatus: VertexBuildTypeAPI | 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 {
|
||||||
|
|
@ -269,31 +278,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"
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
@ -337,7 +345,7 @@ 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;
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,8 @@ export default function Dropdown({
|
||||||
|
|
||||||
const refButton = useRef<HTMLButtonElement>(null);
|
const refButton = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
const PopoverContentDropdown = children
|
const PopoverContentDropdown =
|
||||||
? PopoverContent
|
children || editNode ? PopoverContent : PopoverContentWithoutPortal;
|
||||||
: PopoverContentWithoutPortal;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -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 />
|
||||||
|
|
@ -181,24 +183,14 @@ export default function Header(): JSX.Element {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</AlertDropdown>
|
</AlertDropdown>
|
||||||
{autoLogin && (
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
navigate("/account/api-keys");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconComponent
|
|
||||||
name="Key"
|
|
||||||
className="side-bar-button-size text-muted-foreground hover:text-accent-foreground"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<>
|
<>
|
||||||
<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 " +
|
||||||
|
|
@ -212,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"
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,11 @@ import {
|
||||||
CommandList,
|
CommandList,
|
||||||
} from "../../../ui/command";
|
} from "../../../ui/command";
|
||||||
import { Input } from "../../../ui/input";
|
import { Input } from "../../../ui/input";
|
||||||
import { Popover, PopoverContentWithoutPortal } from "../../../ui/popover";
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverContentWithoutPortal,
|
||||||
|
} from "../../../ui/popover";
|
||||||
const CustomInputPopover = ({
|
const CustomInputPopover = ({
|
||||||
id,
|
id,
|
||||||
refInput,
|
refInput,
|
||||||
|
|
@ -39,6 +43,9 @@ const CustomInputPopover = ({
|
||||||
showOptions,
|
showOptions,
|
||||||
}) => {
|
}) => {
|
||||||
const setErrorData = useAlertStore.getState().setErrorData;
|
const setErrorData = useAlertStore.getState().setErrorData;
|
||||||
|
const PopoverContentInput = editNode
|
||||||
|
? PopoverContent
|
||||||
|
: PopoverContentWithoutPortal;
|
||||||
|
|
||||||
const handleInputChange = (e) => {
|
const handleInputChange = (e) => {
|
||||||
if (password) {
|
if (password) {
|
||||||
|
|
@ -107,7 +114,7 @@ const CustomInputPopover = ({
|
||||||
data-testid={editNode ? id + "-edit" : id}
|
data-testid={editNode ? id + "-edit" : id}
|
||||||
/>
|
/>
|
||||||
</PopoverAnchor>
|
</PopoverAnchor>
|
||||||
<PopoverContentWithoutPortal
|
<PopoverContentInput
|
||||||
className="nocopy nowheel nopan nodelete nodrag noundo p-0"
|
className="nocopy nowheel nopan nodelete nodrag noundo p-0"
|
||||||
style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }}
|
style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }}
|
||||||
side="bottom"
|
side="bottom"
|
||||||
|
|
@ -184,7 +191,7 @@ const CustomInputPopover = ({
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
</CommandList>
|
</CommandList>
|
||||||
</Command>
|
</Command>
|
||||||
</PopoverContentWithoutPortal>
|
</PopoverContentInput>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,11 @@ import {
|
||||||
CommandList,
|
CommandList,
|
||||||
} from "../../../ui/command";
|
} from "../../../ui/command";
|
||||||
import { Input } from "../../../ui/input";
|
import { Input } from "../../../ui/input";
|
||||||
import { Popover, PopoverContentWithoutPortal } from "../../../ui/popover";
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverContentWithoutPortal,
|
||||||
|
} from "../../../ui/popover";
|
||||||
const CustomInputPopoverObject = ({
|
const CustomInputPopoverObject = ({
|
||||||
id,
|
id,
|
||||||
refInput,
|
refInput,
|
||||||
|
|
@ -23,6 +27,7 @@ const CustomInputPopoverObject = ({
|
||||||
disabled,
|
disabled,
|
||||||
setShowOptions,
|
setShowOptions,
|
||||||
required,
|
required,
|
||||||
|
editNode,
|
||||||
className,
|
className,
|
||||||
placeholder,
|
placeholder,
|
||||||
onChange,
|
onChange,
|
||||||
|
|
@ -34,6 +39,10 @@ const CustomInputPopoverObject = ({
|
||||||
handleKeyDown,
|
handleKeyDown,
|
||||||
showOptions,
|
showOptions,
|
||||||
}) => {
|
}) => {
|
||||||
|
const PopoverContentInput = editNode
|
||||||
|
? PopoverContent
|
||||||
|
: PopoverContentWithoutPortal;
|
||||||
|
|
||||||
const handleInputChange = (e) => {
|
const handleInputChange = (e) => {
|
||||||
onChange && onChange(e.target.value);
|
onChange && onChange(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
@ -79,7 +88,7 @@ const CustomInputPopoverObject = ({
|
||||||
data-testid={id}
|
data-testid={id}
|
||||||
/>
|
/>
|
||||||
</PopoverAnchor>
|
</PopoverAnchor>
|
||||||
<PopoverContentWithoutPortal
|
<PopoverContentInput
|
||||||
className="nocopy nowheel nopan nodelete nodrag noundo p-0"
|
className="nocopy nowheel nopan nodelete nodrag noundo p-0"
|
||||||
style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }}
|
style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }}
|
||||||
side="bottom"
|
side="bottom"
|
||||||
|
|
@ -159,7 +168,7 @@ const CustomInputPopoverObject = ({
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
</CommandList>
|
</CommandList>
|
||||||
</Command>
|
</Command>
|
||||||
</PopoverContentWithoutPortal>
|
</PopoverContentInput>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,7 @@ export default function InputComponent({
|
||||||
setSelectedOptions={setSelectedOptions}
|
setSelectedOptions={setSelectedOptions}
|
||||||
options={objectOptions}
|
options={objectOptions}
|
||||||
value={value}
|
value={value}
|
||||||
|
editNode={editNode}
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
setShowOptions={setShowOptions}
|
setShowOptions={setShowOptions}
|
||||||
|
|
|
||||||
|
|
@ -32,11 +32,11 @@ export default function InputGlobalComponent({
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data.node?.template[name])
|
if (data)
|
||||||
if (
|
if (
|
||||||
globalVariablesEntries &&
|
globalVariablesEntries &&
|
||||||
!globalVariablesEntries.includes(data.node?.template[name].value) &&
|
!globalVariablesEntries.includes(data.value) &&
|
||||||
data.node?.template[name].load_from_db
|
data.load_from_db
|
||||||
) {
|
) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
onChange("", true);
|
onChange("", true);
|
||||||
|
|
@ -46,17 +46,11 @@ export default function InputGlobalComponent({
|
||||||
}, [globalVariablesEntries]);
|
}, [globalVariablesEntries]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (!data.value && data.display_name) {
|
||||||
!data.node?.template[name].value &&
|
if (unavaliableFields[data.display_name!] && !disabled) {
|
||||||
data.node?.template[name].display_name
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
unavaliableFields[data.node?.template[name].display_name!] &&
|
|
||||||
!disabled
|
|
||||||
) {
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setDb(true);
|
setDb(true);
|
||||||
onChange(unavaliableFields[data.node?.template[name].display_name!]);
|
onChange(unavaliableFields[data.display_name!]);
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -68,10 +62,7 @@ export default function InputGlobalComponent({
|
||||||
await deleteGlobalVariable(id)
|
await deleteGlobalVariable(id)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
removeGlobalVariable(key);
|
removeGlobalVariable(key);
|
||||||
if (
|
if (data?.value === key && data?.load_from_db) {
|
||||||
data?.node?.template[name].value === key &&
|
|
||||||
data?.node?.template[name].load_from_db
|
|
||||||
) {
|
|
||||||
onChange("");
|
onChange("");
|
||||||
setDb(false);
|
setDb(false);
|
||||||
}
|
}
|
||||||
|
|
@ -94,8 +85,8 @@ export default function InputGlobalComponent({
|
||||||
id={"input-" + name}
|
id={"input-" + name}
|
||||||
editNode={editNode}
|
editNode={editNode}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
password={data.node?.template[name].password ?? false}
|
password={data.password ?? false}
|
||||||
value={data.node?.template[name].value ?? ""}
|
value={data.value ?? ""}
|
||||||
options={globalVariablesEntries}
|
options={globalVariablesEntries}
|
||||||
optionsPlaceholder={"Global Variables"}
|
optionsPlaceholder={"Global Variables"}
|
||||||
optionsIcon="Globe"
|
optionsIcon="Globe"
|
||||||
|
|
@ -138,10 +129,10 @@ export default function InputGlobalComponent({
|
||||||
</DeleteConfirmationModal>
|
</DeleteConfirmationModal>
|
||||||
)}
|
)}
|
||||||
selectedOption={
|
selectedOption={
|
||||||
data?.node?.template[name].load_from_db &&
|
data?.load_from_db &&
|
||||||
globalVariablesEntries &&
|
globalVariablesEntries &&
|
||||||
globalVariablesEntries.includes(data?.node?.template[name].value ?? "")
|
globalVariablesEntries.includes(data?.value ?? "")
|
||||||
? data?.node?.template[name].value
|
? data?.value
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
setSelectedOption={(value) => {
|
setSelectedOption={(value) => {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export default function ShadTooltip({
|
||||||
delayDuration = 500,
|
delayDuration = 500,
|
||||||
}: ShadToolTipType): JSX.Element {
|
}: ShadToolTipType): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Tooltip delayDuration={delayDuration}>
|
<Tooltip defaultOpen={!children} delayDuration={delayDuration}>
|
||||||
<TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>
|
<TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>
|
||||||
<TooltipContent
|
<TooltipContent
|
||||||
className={cn(styleClasses, "max-w-96")}
|
className={cn(styleClasses, "max-w-96")}
|
||||||
|
|
|
||||||
|
|
@ -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[];
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -62,7 +64,17 @@ const SideBarFoldersButtonsComponent = ({
|
||||||
);
|
);
|
||||||
|
|
||||||
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) => {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { CustomCellRendererProps } from "ag-grid-react";
|
import { CustomCellRendererProps } from "ag-grid-react";
|
||||||
import { cn, isTimeStampString } from "../../utils/utils";
|
import { cn, isTimeStampString } from "../../../../utils/utils";
|
||||||
import ArrayReader from "../arrayReaderComponent";
|
import ArrayReader from "../../../arrayReaderComponent";
|
||||||
import DateReader from "../dateReaderComponent";
|
import DateReader from "../../../dateReaderComponent";
|
||||||
import NumberReader from "../numberReader";
|
import NumberReader from "../../../numberReader";
|
||||||
import ObjectRender from "../objectRender";
|
import ObjectRender from "../../../objectRender";
|
||||||
import StringReader from "../stringReaderComponent";
|
import StringReader from "../../../stringReaderComponent";
|
||||||
import { Badge } from "../ui/badge";
|
import { Badge } from "../../../ui/badge";
|
||||||
|
|
||||||
export default function TableAutoCellRender({
|
export default function TableAutoCellRender({
|
||||||
value,
|
value,
|
||||||
|
|
@ -43,7 +43,6 @@ export default function TableAutoCellRender({
|
||||||
} else {
|
} else {
|
||||||
return <StringReader string={value} />;
|
return <StringReader string={value} />;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case "number":
|
case "number":
|
||||||
return <NumberReader number={value} />;
|
return <NumberReader number={value} />;
|
||||||
default:
|
default:
|
||||||
|
|
@ -0,0 +1,266 @@
|
||||||
|
import { CustomCellRendererProps } from "ag-grid-react";
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
import { useState } from "react";
|
||||||
|
import CodeAreaComponent from "../../../codeAreaComponent";
|
||||||
|
import DictComponent from "../../../dictComponent";
|
||||||
|
import Dropdown from "../../../dropdownComponent";
|
||||||
|
import FloatComponent from "../../../floatComponent";
|
||||||
|
import InputFileComponent from "../../../inputFileComponent";
|
||||||
|
import InputGlobalComponent from "../../../inputGlobalComponent";
|
||||||
|
import InputListComponent from "../../../inputListComponent";
|
||||||
|
import IntComponent from "../../../intComponent";
|
||||||
|
import KeypairListComponent from "../../../keypairListComponent";
|
||||||
|
import PromptAreaComponent from "../../../promptComponent";
|
||||||
|
import TextAreaComponent from "../../../textAreaComponent";
|
||||||
|
import ToggleShadComponent from "../../../toggleShadComponent";
|
||||||
|
import useFlowStore from "../../../../stores/flowStore";
|
||||||
|
import {
|
||||||
|
convertObjToArray,
|
||||||
|
convertValuesToNumbers,
|
||||||
|
hasDuplicateKeys,
|
||||||
|
scapedJSONStringfy,
|
||||||
|
} from "../../../../utils/reactflowUtils";
|
||||||
|
import { classNames } from "../../../../utils/utils";
|
||||||
|
|
||||||
|
export default function TableNodeCellRender({
|
||||||
|
node: { data },
|
||||||
|
value: {
|
||||||
|
value,
|
||||||
|
nodeClass,
|
||||||
|
handleOnNewValue: handleOnNewValueNode,
|
||||||
|
handleOnChangeDb,
|
||||||
|
},
|
||||||
|
}: CustomCellRendererProps) {
|
||||||
|
const handleOnNewValue = (newValue: any, name: string) => {
|
||||||
|
handleOnNewValueNode(newValue, name);
|
||||||
|
setTemplateData((old) => {
|
||||||
|
let newData = cloneDeep(old);
|
||||||
|
newData.value = newValue;
|
||||||
|
return newData;
|
||||||
|
});
|
||||||
|
setTemplateValue(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const [templateValue, setTemplateValue] = useState(value);
|
||||||
|
const [templateData, setTemplateData] = useState(data);
|
||||||
|
|
||||||
|
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
|
||||||
|
const edges = useFlowStore((state) => state.edges);
|
||||||
|
|
||||||
|
const id = {
|
||||||
|
inputTypes: templateData.input_types,
|
||||||
|
type: templateData.type,
|
||||||
|
id: nodeClass.id,
|
||||||
|
fieldName: templateData.key,
|
||||||
|
};
|
||||||
|
const disabled =
|
||||||
|
edges.some(
|
||||||
|
(edge) =>
|
||||||
|
edge.targetHandle ===
|
||||||
|
scapedJSONStringfy(
|
||||||
|
templateData.proxy
|
||||||
|
? {
|
||||||
|
...id,
|
||||||
|
proxy: templateData.proxy,
|
||||||
|
}
|
||||||
|
: id,
|
||||||
|
),
|
||||||
|
) ?? false;
|
||||||
|
function getCellType() {
|
||||||
|
switch (templateData.type) {
|
||||||
|
case "str":
|
||||||
|
if (!templateData.options) {
|
||||||
|
return templateData?.list ? (
|
||||||
|
<InputListComponent
|
||||||
|
componentName={templateData.key ?? undefined}
|
||||||
|
editNode={true}
|
||||||
|
disabled={disabled}
|
||||||
|
value={
|
||||||
|
!templateValue || templateValue === "" ? [""] : templateValue
|
||||||
|
}
|
||||||
|
onChange={(value: string[]) => {
|
||||||
|
handleOnNewValue(value, templateData.key);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : templateData.multiline ? (
|
||||||
|
<TextAreaComponent
|
||||||
|
id={"textarea-edit-" + templateData.name}
|
||||||
|
data-testid={"textarea-edit-" + templateData.name}
|
||||||
|
disabled={disabled}
|
||||||
|
editNode={true}
|
||||||
|
value={templateValue ?? ""}
|
||||||
|
onChange={(value: string | string[]) => {
|
||||||
|
handleOnNewValue(value, templateData.key);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<InputGlobalComponent
|
||||||
|
disabled={disabled}
|
||||||
|
editNode={true}
|
||||||
|
onChange={(value) => handleOnNewValue(value, templateData.key)}
|
||||||
|
setDb={(value) => {
|
||||||
|
handleOnChangeDb(value, templateData.key);
|
||||||
|
}}
|
||||||
|
name={templateData.key}
|
||||||
|
data={templateData}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Dropdown
|
||||||
|
editNode={true}
|
||||||
|
options={templateData.options}
|
||||||
|
onSelect={(value) => handleOnNewValue(value, templateData.key)}
|
||||||
|
value={templateValue ?? "Choose an option"}
|
||||||
|
id={"dropdown-edit-" + templateData.name}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
case "NestedDict":
|
||||||
|
return (
|
||||||
|
<DictComponent
|
||||||
|
disabled={disabled}
|
||||||
|
editNode={true}
|
||||||
|
value={templateValue.toString() === "{}" ? {} : templateValue}
|
||||||
|
onChange={(newValue) => {
|
||||||
|
handleOnNewValue(newValue, templateData.key);
|
||||||
|
}}
|
||||||
|
id="editnode-div-dict-input"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case "dict":
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
"max-h-48 w-full overflow-auto custom-scroll",
|
||||||
|
templateValue?.length > 1 ? "my-3" : "",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<KeypairListComponent
|
||||||
|
disabled={disabled}
|
||||||
|
editNode={true}
|
||||||
|
value={
|
||||||
|
templateValue?.length === 0 || !templateValue
|
||||||
|
? [{ "": "" }]
|
||||||
|
: convertObjToArray(templateValue, templateData.type)
|
||||||
|
}
|
||||||
|
duplicateKey={errorDuplicateKey}
|
||||||
|
onChange={(newValue) => {
|
||||||
|
const valueToNumbers = convertValuesToNumbers(newValue);
|
||||||
|
setErrorDuplicateKey(hasDuplicateKeys(valueToNumbers));
|
||||||
|
handleOnNewValue(valueToNumbers, templateData.key);
|
||||||
|
}}
|
||||||
|
isList={templateData.list ?? false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
case "bool":
|
||||||
|
return (
|
||||||
|
<ToggleShadComponent
|
||||||
|
id={"toggle-edit-" + templateData.name}
|
||||||
|
disabled={disabled}
|
||||||
|
enabled={templateValue}
|
||||||
|
setEnabled={(isEnabled) => {
|
||||||
|
handleOnNewValue(isEnabled, templateData.key);
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
editNode={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case "float":
|
||||||
|
return (
|
||||||
|
<FloatComponent
|
||||||
|
disabled={disabled}
|
||||||
|
editNode={true}
|
||||||
|
rangeSpec={templateData.rangeSpec}
|
||||||
|
value={templateValue ?? ""}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleOnNewValue(value, templateData.key);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case "int":
|
||||||
|
return (
|
||||||
|
<IntComponent
|
||||||
|
rangeSpec={templateData.rangeSpec}
|
||||||
|
id={"edit-int-input-" + templateData.name}
|
||||||
|
disabled={disabled}
|
||||||
|
editNode={true}
|
||||||
|
value={templateValue ?? ""}
|
||||||
|
onChange={(value) => {
|
||||||
|
handleOnNewValue(value, templateData.key);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case "file":
|
||||||
|
return (
|
||||||
|
<InputFileComponent
|
||||||
|
editNode={true}
|
||||||
|
disabled={disabled}
|
||||||
|
value={templateValue ?? ""}
|
||||||
|
onChange={(value: string | string[]) => {
|
||||||
|
handleOnNewValue(value, templateData.key);
|
||||||
|
}}
|
||||||
|
fileTypes={templateData.fileTypes}
|
||||||
|
onFileChange={(filePath: string) => {
|
||||||
|
templateData.file_path = filePath;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case "prompt":
|
||||||
|
return (
|
||||||
|
<PromptAreaComponent
|
||||||
|
readonly={nodeClass.flow ? true : false}
|
||||||
|
field_name={templateData.key}
|
||||||
|
editNode={true}
|
||||||
|
disabled={disabled}
|
||||||
|
nodeClass={nodeClass}
|
||||||
|
setNodeClass={(value) => {
|
||||||
|
nodeClass = value;
|
||||||
|
}}
|
||||||
|
value={templateValue ?? ""}
|
||||||
|
onChange={(value: string | string[]) => {
|
||||||
|
handleOnNewValue(value, templateData.key);
|
||||||
|
}}
|
||||||
|
id={"prompt-area-edit-" + templateData.name}
|
||||||
|
data-testid={"modal-prompt-input-" + templateData.name}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case "code":
|
||||||
|
return (
|
||||||
|
<CodeAreaComponent
|
||||||
|
readonly={nodeClass.flow && templateData.dynamic ? true : false}
|
||||||
|
dynamic={templateData.dynamic ?? false}
|
||||||
|
setNodeClass={(value) => {
|
||||||
|
nodeClass = value;
|
||||||
|
}}
|
||||||
|
nodeClass={nodeClass}
|
||||||
|
disabled={disabled}
|
||||||
|
editNode={true}
|
||||||
|
value={templateValue ?? ""}
|
||||||
|
onChange={(value: string | string[]) => {
|
||||||
|
handleOnNewValue(value, templateData.key);
|
||||||
|
}}
|
||||||
|
id={"code-area-edit" + templateData.name}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
case "Any":
|
||||||
|
return <>-</>;
|
||||||
|
default:
|
||||||
|
return String(templateValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="group flex h-full w-[300px] items-center justify-center py-2.5">
|
||||||
|
{getCellType()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { CustomCellRendererProps } from "ag-grid-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import ToggleShadComponent from "../../../toggleShadComponent";
|
||||||
|
|
||||||
|
export default function TableToggleCellRender({
|
||||||
|
value: { name, enabled, setEnabled },
|
||||||
|
}: CustomCellRendererProps) {
|
||||||
|
const [value, setValue] = useState(enabled);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-full items-center">
|
||||||
|
<ToggleShadComponent
|
||||||
|
id={"show" + name}
|
||||||
|
enabled={value}
|
||||||
|
setEnabled={(e) => {
|
||||||
|
setValue(e);
|
||||||
|
setEnabled(e);
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
editNode={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { CustomTooltipProps } from "ag-grid-react";
|
||||||
|
|
||||||
|
export default function TableTooltipRender({ value }: CustomTooltipProps) {
|
||||||
|
return (
|
||||||
|
<div className="z-45 overflow-y-auto rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1">
|
||||||
|
{value}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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"]>;
|
||||||
|
|
@ -82,7 +83,7 @@ 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);
|
||||||
};
|
};
|
||||||
|
|
@ -91,7 +92,7 @@ const TableComponent = forwardRef<
|
||||||
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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,20 +29,18 @@ export default function ToggleShadComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={disabled ? "pointer-events-none cursor-not-allowed " : ""}>
|
<Switch
|
||||||
<Switch
|
id={id}
|
||||||
id={id}
|
data-testid={id}
|
||||||
data-testid={id}
|
style={{
|
||||||
style={{
|
transform: `scaleX(${scaleX}) scaleY(${scaleY})`,
|
||||||
transform: `scaleX(${scaleX}) scaleY(${scaleY})`,
|
}}
|
||||||
}}
|
disabled={disabled}
|
||||||
disabled={disabled}
|
className=""
|
||||||
className=""
|
checked={enabled}
|
||||||
checked={enabled}
|
onCheckedChange={(isEnabled: boolean) => {
|
||||||
onCheckedChange={(isEnabled: boolean) => {
|
setEnabled(isEnabled);
|
||||||
setEnabled(isEnabled);
|
}}
|
||||||
}}
|
></Switch>
|
||||||
></Switch>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,11 +31,7 @@ function RefreshButton({
|
||||||
|
|
||||||
// icon class name should take into account the disabled state and the loading state
|
// icon class name should take into account the disabled state and the loading state
|
||||||
const disabledIconTextClass = disabled ? "text-muted-foreground" : "";
|
const disabledIconTextClass = disabled ? "text-muted-foreground" : "";
|
||||||
const iconClassName = cn(
|
const iconClassName = cn("h-4 w-4 animate-wiggle", disabledIconTextClass);
|
||||||
"h-4 w-4",
|
|
||||||
isLoading ? "animate-spin" : "animate-wiggle",
|
|
||||||
disabledIconTextClass
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -44,10 +40,11 @@ function RefreshButton({
|
||||||
className={classNames}
|
className={classNames}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
id={id}
|
id={id}
|
||||||
|
loading={isLoading}
|
||||||
>
|
>
|
||||||
{button_text && <span className="mr-1">{button_text}</span>}
|
{button_text && <span className="mr-1">{button_text}</span>}
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name={isLoading ? "Loader2" : "RefreshCcw"}
|
name={"RefreshCcw"}
|
||||||
className={iconClassName}
|
className={iconClassName}
|
||||||
id={id + "-icon"}
|
id={id + "-icon"}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ export const USER_EDIT_ERROR_ALERT = "Error on edit user";
|
||||||
export const USER_ADD_ERROR_ALERT = "Error when adding new user";
|
export const USER_ADD_ERROR_ALERT = "Error when adding new user";
|
||||||
export const SIGNIN_ERROR_ALERT = "Error signing in";
|
export const SIGNIN_ERROR_ALERT = "Error signing in";
|
||||||
export const DEL_KEY_ERROR_ALERT = "Error on delete key";
|
export const DEL_KEY_ERROR_ALERT = "Error on delete key";
|
||||||
|
export const DEL_KEY_ERROR_ALERT_PLURAL = "Error on delete keys";
|
||||||
export const UPLOAD_ERROR_ALERT = "Error uploading file";
|
export const UPLOAD_ERROR_ALERT = "Error uploading file";
|
||||||
export const WRONG_FILE_ERROR_ALERT = "Invalid file type";
|
export const WRONG_FILE_ERROR_ALERT = "Invalid file type";
|
||||||
export const UPLOAD_ALERT_LIST = "Please upload a JSON file";
|
export const UPLOAD_ALERT_LIST = "Please upload a JSON file";
|
||||||
|
|
@ -54,6 +55,7 @@ export const USER_DEL_SUCCESS_ALERT = "Success! User deleted!";
|
||||||
export const USER_EDIT_SUCCESS_ALERT = "Success! User edited!";
|
export const USER_EDIT_SUCCESS_ALERT = "Success! User edited!";
|
||||||
export const USER_ADD_SUCCESS_ALERT = "Success! New user added!";
|
export const USER_ADD_SUCCESS_ALERT = "Success! New user added!";
|
||||||
export const DEL_KEY_SUCCESS_ALERT = "Success! Key deleted!";
|
export const DEL_KEY_SUCCESS_ALERT = "Success! Key deleted!";
|
||||||
|
export const DEL_KEY_SUCCESS_ALERT_PLURAL = "Success! Keys deleted!";
|
||||||
export const FLOW_BUILD_SUCCESS_ALERT = `Flow built successfully`;
|
export const FLOW_BUILD_SUCCESS_ALERT = `Flow built successfully`;
|
||||||
export const SAVE_SUCCESS_ALERT = "Changes saved successfully!";
|
export const SAVE_SUCCESS_ALERT = "Changes saved successfully!";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -613,11 +613,8 @@ export const FETCH_ERROR_DESCRIPION =
|
||||||
|
|
||||||
export const SIGN_UP_SUCCESS = "Account created! Await admin activation. ";
|
export const SIGN_UP_SUCCESS = "Account created! Await admin activation. ";
|
||||||
|
|
||||||
export const API_PAGE_PARAGRAPH_1 =
|
export const API_PAGE_PARAGRAPH =
|
||||||
"Your secret API keys are listed below. Please note that we do not display your secret API keys again after you generate them.";
|
"Your secret API keys are listed below. Do not share your API key with others, or expose it in the browser or other client-side code.";
|
||||||
|
|
||||||
export const API_PAGE_PARAGRAPH_2 =
|
|
||||||
"Do not share your API key with others, or expose it in the browser or other client-side code.";
|
|
||||||
|
|
||||||
export const API_PAGE_USER_KEYS =
|
export const API_PAGE_USER_KEYS =
|
||||||
"This user does not have any keys assigned at the moment.";
|
"This user does not have any keys assigned at the moment.";
|
||||||
|
|
@ -671,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";
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -177,16 +177,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",
|
|
||||||
lockChat
|
|
||||||
? "animate-pulse text-primary"
|
|
||||||
: "text-primary hover:text-gray-600"
|
|
||||||
)}
|
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,6 @@ const ApiModal = forwardRef(
|
||||||
);
|
);
|
||||||
const pythonCode = getPythonCode(flow?.name, tweak);
|
const pythonCode = getPythonCode(flow?.name, tweak);
|
||||||
const widgetCode = getWidgetCode(flow?.id, flow?.name, autoLogin);
|
const widgetCode = getWidgetCode(flow?.id, flow?.name, autoLogin);
|
||||||
console.log("flow", flow);
|
|
||||||
const includeWebhook = flow.webhook;
|
const includeWebhook = flow.webhook;
|
||||||
const tweaksCode = buildTweaks(flow);
|
const tweaksCode = buildTweaks(flow);
|
||||||
const codesArray = [
|
const codesArray = [
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ export const switchCaseModalSize = (size: string) => {
|
||||||
switch (size) {
|
switch (size) {
|
||||||
case "x-small":
|
case "x-small":
|
||||||
minWidth = "min-w-[20vw]";
|
minWidth = "min-w-[20vw]";
|
||||||
height = "h-full";
|
height = "";
|
||||||
break;
|
break;
|
||||||
case "smaller":
|
case "smaller":
|
||||||
minWidth = "min-w-[40vw]";
|
minWidth = "min-w-[40vw]";
|
||||||
|
|
@ -12,7 +12,7 @@ export const switchCaseModalSize = (size: string) => {
|
||||||
break;
|
break;
|
||||||
case "smaller-h-full":
|
case "smaller-h-full":
|
||||||
minWidth = "min-w-[40vw]";
|
minWidth = "min-w-[40vw]";
|
||||||
height = "h-full";
|
height = "";
|
||||||
break;
|
break;
|
||||||
case "small":
|
case "small":
|
||||||
minWidth = "min-w-[40vw]";
|
minWidth = "min-w-[40vw]";
|
||||||
|
|
@ -20,16 +20,19 @@ export const switchCaseModalSize = (size: string) => {
|
||||||
break;
|
break;
|
||||||
case "small-h-full":
|
case "small-h-full":
|
||||||
minWidth = "min-w-[40vw]";
|
minWidth = "min-w-[40vw]";
|
||||||
height = "h-full";
|
height = "";
|
||||||
break;
|
break;
|
||||||
case "medium":
|
case "medium":
|
||||||
minWidth = "min-w-[60vw]";
|
minWidth = "min-w-[60vw]";
|
||||||
height = "h-[60vh]";
|
height = "h-[60vh]";
|
||||||
break;
|
break;
|
||||||
|
case "medium-tall":
|
||||||
|
minWidth = "min-w-[60vw]";
|
||||||
|
height = "h-[90vh]";
|
||||||
|
break;
|
||||||
case "medium-h-full":
|
case "medium-h-full":
|
||||||
minWidth = "min-w-[60vw]";
|
minWidth = "min-w-[60vw]";
|
||||||
height = "h-full";
|
height = "";
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case "large":
|
case "large":
|
||||||
minWidth = "min-w-[85vw]";
|
minWidth = "min-w-[85vw]";
|
||||||
|
|
@ -41,26 +44,26 @@ 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":
|
||||||
minWidth = "min-w-[80vw]";
|
minWidth = "min-w-[80vw]";
|
||||||
height = "h-full";
|
height = "";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
minWidth = "min-w-[80vw]";
|
minWidth = "min-w-[80vw]";
|
||||||
height = "h-[80vh]";
|
height = "h-[90vh]";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return { minWidth, height };
|
return { minWidth, height };
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import { Button } from "../../components/ui/button";
|
||||||
import { modalHeaderType } from "../../types/components";
|
import { modalHeaderType } from "../../types/components";
|
||||||
import { cn } from "../../utils/utils";
|
import { cn } from "../../utils/utils";
|
||||||
import { switchCaseModalSize } from "./helpers/switch-case-size";
|
import { switchCaseModalSize } from "./helpers/switch-case-size";
|
||||||
|
import * as Form from "@radix-ui/react-form";
|
||||||
|
|
||||||
type ContentProps = { children: ReactNode };
|
type ContentProps = { children: ReactNode };
|
||||||
type HeaderProps = { children: ReactNode; description: string };
|
type HeaderProps = { children: ReactNode; description: string };
|
||||||
|
|
@ -52,10 +53,10 @@ const Trigger: React.FC<TriggerProps> = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Header: React.FC<{ children: ReactNode; description: string | null }> = ({
|
const Header: React.FC<{
|
||||||
children,
|
children: ReactNode;
|
||||||
description,
|
description: string | JSX.Element | null;
|
||||||
}: modalHeaderType): JSX.Element => {
|
}> = ({ children, description }: modalHeaderType): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center">{children}</DialogTitle>
|
<DialogTitle className="flex items-center">{children}</DialogTitle>
|
||||||
|
|
@ -102,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;
|
||||||
|
|
@ -111,6 +112,7 @@ interface BaseModalProps {
|
||||||
| "smaller"
|
| "smaller"
|
||||||
| "small"
|
| "small"
|
||||||
| "medium"
|
| "medium"
|
||||||
|
| "medium-tall"
|
||||||
| "large"
|
| "large"
|
||||||
| "three-cards"
|
| "three-cards"
|
||||||
| "large-thin"
|
| "large-thin"
|
||||||
|
|
@ -136,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);
|
||||||
|
|
@ -162,53 +164,63 @@ 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>
|
||||||
) : (
|
) : (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
{triggerChild}
|
{triggerChild}
|
||||||
<DialogContent className={cn(minWidth, "duration-300")}>
|
<DialogContent
|
||||||
<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>
|
||||||
{onSubmit ? (
|
{onSubmit ? (
|
||||||
<form
|
<Form.Root
|
||||||
onSubmit={(event) => {
|
onSubmit={(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
onSubmit();
|
onSubmit();
|
||||||
}}
|
}}
|
||||||
className="flex flex-col gap-6"
|
className="flex min-h-0 flex-1 flex-col gap-6"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`flex flex-col ${height} w-full 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>
|
</Form.Root>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={`flex flex-col ${height} w-full 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>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
import { ColDef, ValueGetterParams } from "ag-grid-community";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import TableAutoCellRender from "../../../components/tableComponent/components/tableAutoCellRender";
|
||||||
|
import TableNodeCellRender from "../../../components/tableComponent/components/tableNodeCellRender";
|
||||||
|
import TableToggleCellRender from "../../../components/tableComponent/components/tableToggleCellRender";
|
||||||
|
import TableTooltipRender from "../../../components/tableComponent/components/tableTooltipRender";
|
||||||
|
|
||||||
|
const useColumnDefs = (
|
||||||
|
myData: any,
|
||||||
|
handleOnNewValue: (newValue: any, name: string) => void,
|
||||||
|
changeAdvanced: (n: string) => void,
|
||||||
|
open: boolean,
|
||||||
|
) => {
|
||||||
|
const columnDefs: ColDef[] = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
headerName: "Name",
|
||||||
|
field: "display_name",
|
||||||
|
valueGetter: (params) => {
|
||||||
|
const templateParam = params.data;
|
||||||
|
return (
|
||||||
|
(templateParam.display_name
|
||||||
|
? templateParam.display_name
|
||||||
|
: templateParam.name) ?? params.data.key
|
||||||
|
);
|
||||||
|
},
|
||||||
|
tooltipField: "display_name",
|
||||||
|
tooltipComponent: TableTooltipRender,
|
||||||
|
wrapText: true,
|
||||||
|
autoHeight: true,
|
||||||
|
flex: 1,
|
||||||
|
resizable: false,
|
||||||
|
cellClass: "no-border",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: "Description",
|
||||||
|
field: "info",
|
||||||
|
tooltipField: "info",
|
||||||
|
tooltipComponent: TableTooltipRender,
|
||||||
|
wrapText: true,
|
||||||
|
autoHeight: true,
|
||||||
|
flex: 2,
|
||||||
|
resizable: false,
|
||||||
|
cellClass: "no-border",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: "Value",
|
||||||
|
field: "value",
|
||||||
|
cellRenderer: TableNodeCellRender,
|
||||||
|
valueGetter: (params: ValueGetterParams) => {
|
||||||
|
return {
|
||||||
|
value: params.data.value,
|
||||||
|
nodeClass: myData.node,
|
||||||
|
handleOnNewValue: handleOnNewValue,
|
||||||
|
handleOnChangeDb: (value, key) => {
|
||||||
|
myData.node!.template[key].load_from_db = value;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
minWidth: 330,
|
||||||
|
autoHeight: true,
|
||||||
|
flex: 1,
|
||||||
|
resizable: false,
|
||||||
|
cellClass: "no-border",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: "Show",
|
||||||
|
field: "advanced",
|
||||||
|
cellRenderer: TableToggleCellRender,
|
||||||
|
valueGetter: (params: ValueGetterParams) => {
|
||||||
|
return {
|
||||||
|
name: params.data.name,
|
||||||
|
enabled: !params.data.advanced,
|
||||||
|
setEnabled: () => {
|
||||||
|
changeAdvanced(params.data.key);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
editable: false,
|
||||||
|
maxWidth: 80,
|
||||||
|
resizable: false,
|
||||||
|
cellClass: "no-border",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[open, myData],
|
||||||
|
);
|
||||||
|
|
||||||
|
return columnDefs;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useColumnDefs;
|
||||||
37
src/frontend/src/modals/editNodeModal/hooks/use-row-data.tsx
Normal file
37
src/frontend/src/modals/editNodeModal/hooks/use-row-data.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import { LANGFLOW_SUPPORTED_TYPES } from "../../../constants/constants";
|
||||||
|
import { TemplateVariableType } from "../../../types/api";
|
||||||
|
|
||||||
|
const useRowData = (myData, open) => {
|
||||||
|
const rowData = useMemo(() => {
|
||||||
|
return Object.keys(myData.node!.template)
|
||||||
|
.filter((key: string) => {
|
||||||
|
const templateParam = myData.node!.template[
|
||||||
|
key
|
||||||
|
] as TemplateVariableType;
|
||||||
|
return (
|
||||||
|
key.charAt(0) !== "_" &&
|
||||||
|
templateParam.show &&
|
||||||
|
LANGFLOW_SUPPORTED_TYPES.has(templateParam.type) &&
|
||||||
|
!(
|
||||||
|
(key === "code" && templateParam.type === "code") ||
|
||||||
|
(key.includes("code") && templateParam.proxy)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.map((key: string) => {
|
||||||
|
const templateParam = myData.node!.template[
|
||||||
|
key
|
||||||
|
] as TemplateVariableType;
|
||||||
|
return {
|
||||||
|
...templateParam,
|
||||||
|
key: key,
|
||||||
|
id: key,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [open, myData]);
|
||||||
|
|
||||||
|
return rowData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useRowData;
|
||||||
|
|
@ -1,43 +1,13 @@
|
||||||
import { cloneDeep } from "lodash";
|
import { ColDef, GridApi } from "ag-grid-community";
|
||||||
import { forwardRef, useEffect, useState } from "react";
|
import { forwardRef, useEffect, useRef, useState } from "react";
|
||||||
import CodeAreaComponent from "../../components/codeAreaComponent";
|
|
||||||
import DictComponent from "../../components/dictComponent";
|
|
||||||
import Dropdown from "../../components/dropdownComponent";
|
|
||||||
import FloatComponent from "../../components/floatComponent";
|
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
import InputFileComponent from "../../components/inputFileComponent";
|
import TableComponent from "../../components/tableComponent";
|
||||||
import InputGlobalComponent from "../../components/inputGlobalComponent";
|
|
||||||
import InputListComponent from "../../components/inputListComponent";
|
|
||||||
import IntComponent from "../../components/intComponent";
|
|
||||||
import KeypairListComponent from "../../components/keypairListComponent";
|
|
||||||
import PromptAreaComponent from "../../components/promptComponent";
|
|
||||||
import ShadTooltip from "../../components/shadTooltipComponent";
|
|
||||||
import TextAreaComponent from "../../components/textAreaComponent";
|
|
||||||
import ToggleShadComponent from "../../components/toggleShadComponent";
|
|
||||||
import { Badge } from "../../components/ui/badge";
|
import { Badge } from "../../components/ui/badge";
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "../../components/ui/table";
|
|
||||||
import {
|
|
||||||
LANGFLOW_SUPPORTED_TYPES,
|
|
||||||
limitScrollFieldsModal,
|
|
||||||
} from "../../constants/constants";
|
|
||||||
import { Case } from "../../shared/components/caseComponent";
|
|
||||||
import useFlowStore from "../../stores/flowStore";
|
import useFlowStore from "../../stores/flowStore";
|
||||||
import { NodeDataType } from "../../types/flow";
|
import { NodeDataType } from "../../types/flow";
|
||||||
import {
|
|
||||||
convertObjToArray,
|
|
||||||
convertValuesToNumbers,
|
|
||||||
hasDuplicateKeys,
|
|
||||||
scapedJSONStringfy,
|
|
||||||
} from "../../utils/reactflowUtils";
|
|
||||||
import { classNames } from "../../utils/utils";
|
|
||||||
import BaseModal from "../baseModal";
|
import BaseModal from "../baseModal";
|
||||||
|
import useColumnDefs from "./hooks/use-column-defs";
|
||||||
|
import useRowData from "./hooks/use-row-data";
|
||||||
|
|
||||||
const EditNodeModal = forwardRef(
|
const EditNodeModal = forwardRef(
|
||||||
(
|
(
|
||||||
|
|
@ -54,59 +24,49 @@ const EditNodeModal = forwardRef(
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
const nodes = useFlowStore((state) => state.nodes);
|
const myData = useRef(data);
|
||||||
|
|
||||||
const dataFromStore = nodes.find((node) => node.id === node.id)?.data;
|
|
||||||
|
|
||||||
const [myData, setMyData] = useState(dataFromStore ?? data);
|
|
||||||
|
|
||||||
const edges = useFlowStore((state) => state.edges);
|
|
||||||
const setNode = useFlowStore((state) => state.setNode);
|
const setNode = useFlowStore((state) => state.setNode);
|
||||||
|
|
||||||
function changeAdvanced(n) {
|
function changeAdvanced(n) {
|
||||||
setMyData((old) => {
|
myData.current.node!.template[n].advanced =
|
||||||
let newData = cloneDeep(old);
|
!myData.current.node!.template[n]?.advanced;
|
||||||
newData.node!.template[n].advanced =
|
|
||||||
!newData.node!.template[n].advanced;
|
|
||||||
return newData;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleOnNewValue = (newValue: any, name) => {
|
const handleOnNewValue = (newValue: any, name) => {
|
||||||
setMyData((old) => {
|
myData.current.node!.template[name].value = newValue;
|
||||||
let newData = cloneDeep(old);
|
|
||||||
newData.node!.template[name].value = newValue;
|
|
||||||
return newData;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const rowData = useRowData(data, open);
|
||||||
|
|
||||||
|
const columnDefs: ColDef[] = useColumnDefs(
|
||||||
|
data,
|
||||||
|
handleOnNewValue,
|
||||||
|
changeAdvanced,
|
||||||
|
open
|
||||||
|
);
|
||||||
|
|
||||||
|
const [gridApi, setGridApi] = useState<GridApi | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (gridApi && open) {
|
||||||
setMyData(data); // reset data to what it is on node when opening modal
|
myData.current = data;
|
||||||
|
gridApi.refreshCells();
|
||||||
}
|
}
|
||||||
}, [open]);
|
}, [gridApi, open]);
|
||||||
|
|
||||||
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
|
|
||||||
|
|
||||||
const type = (templateParam) => {
|
|
||||||
return myData.node?.template[templateParam].type;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseModal
|
<BaseModal
|
||||||
key={data.id}
|
key={data.id}
|
||||||
size="large-h-full"
|
size="medium-tall"
|
||||||
open={open}
|
open={open}
|
||||||
setOpen={setOpen}
|
setOpen={setOpen}
|
||||||
onChangeOpenModal={(open) => {
|
|
||||||
setMyData(data);
|
|
||||||
}}
|
|
||||||
onSubmit={() => {
|
onSubmit={() => {
|
||||||
setNode(data.id, (old) => ({
|
setNode(data.id, (old) => ({
|
||||||
...old,
|
...old,
|
||||||
data: {
|
data: {
|
||||||
...old.data,
|
...old.data,
|
||||||
node: myData.node,
|
node: myData.current.node,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
|
|
@ -115,526 +75,30 @@ const EditNodeModal = forwardRef(
|
||||||
<BaseModal.Trigger>
|
<BaseModal.Trigger>
|
||||||
<></>
|
<></>
|
||||||
</BaseModal.Trigger>
|
</BaseModal.Trigger>
|
||||||
<BaseModal.Header description={myData.node?.description!}>
|
<BaseModal.Header description={data.node?.description!}>
|
||||||
<span className="pr-2">{myData.type}</span>
|
<span className="pr-2">{data.type}</span>
|
||||||
<Badge variant="secondary">ID: {myData.id}</Badge>
|
<Badge variant="secondary">ID: {data.id}</Badge>
|
||||||
</BaseModal.Header>
|
</BaseModal.Header>
|
||||||
<BaseModal.Content>
|
<BaseModal.Content>
|
||||||
<div className="flex pb-2">
|
<div className="flex h-full flex-col">
|
||||||
<IconComponent
|
<div className="flex pb-2">
|
||||||
name="Variable"
|
<IconComponent
|
||||||
className="edit-node-modal-variable "
|
name="Variable"
|
||||||
/>
|
className="edit-node-modal-variable "
|
||||||
<span className="edit-node-modal-span">Parameters</span>
|
/>
|
||||||
</div>
|
<span className="edit-node-modal-span">Parameters</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="edit-node-modal-arrangement">
|
<div className="h-full">
|
||||||
<div
|
|
||||||
className={classNames(
|
|
||||||
"edit-node-modal-box",
|
|
||||||
nodeLength > limitScrollFieldsModal
|
|
||||||
? "overflow-scroll overflow-x-hidden custom-scroll"
|
|
||||||
: ""
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{nodeLength > 0 && (
|
{nodeLength > 0 && (
|
||||||
<div className="edit-node-modal-table">
|
<TableComponent
|
||||||
<Table className="table-fixed bg-muted outline-1">
|
onGridReady={(params) => {
|
||||||
<TableHeader className="edit-node-modal-table-header">
|
setGridApi(params.api);
|
||||||
<TableRow className="">
|
}}
|
||||||
<TableHead className="h-7 text-center">PARAM</TableHead>
|
tooltipShowDelay={0.5}
|
||||||
<TableHead className="h-7 text-center">DESC</TableHead>
|
columnDefs={columnDefs}
|
||||||
<TableHead className="h-7 p-0 text-center">
|
rowData={rowData}
|
||||||
VALUE
|
/>
|
||||||
</TableHead>
|
|
||||||
<TableHead className="h-7 text-center">SHOW</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody className="p-0">
|
|
||||||
{Object.keys(myData.node!.template)
|
|
||||||
.filter(
|
|
||||||
(templateParam) =>
|
|
||||||
templateParam.charAt(0) !== "_" &&
|
|
||||||
myData.node?.template[templateParam].show &&
|
|
||||||
LANGFLOW_SUPPORTED_TYPES.has(
|
|
||||||
myData.node!.template[templateParam].type
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.map((templateParam, index) => {
|
|
||||||
let id = {
|
|
||||||
inputTypes:
|
|
||||||
myData.node!.template[templateParam].input_types,
|
|
||||||
type: myData.node!.template[templateParam].type,
|
|
||||||
id: myData.id,
|
|
||||||
fieldName: templateParam,
|
|
||||||
};
|
|
||||||
let disabled =
|
|
||||||
edges.some(
|
|
||||||
(edge) =>
|
|
||||||
edge.targetHandle ===
|
|
||||||
scapedJSONStringfy(
|
|
||||||
myData.node!.template[templateParam].proxy
|
|
||||||
? {
|
|
||||||
...id,
|
|
||||||
proxy:
|
|
||||||
myData.node?.template[templateParam]
|
|
||||||
.proxy,
|
|
||||||
}
|
|
||||||
: id
|
|
||||||
)
|
|
||||||
) ?? false;
|
|
||||||
return (
|
|
||||||
<TableRow
|
|
||||||
key={index}
|
|
||||||
className={
|
|
||||||
"h-10 " +
|
|
||||||
((templateParam === "code" &&
|
|
||||||
type(templateParam) === "code") ||
|
|
||||||
(templateParam.includes("code") &&
|
|
||||||
myData.node?.template[templateParam].proxy)
|
|
||||||
? " hidden "
|
|
||||||
: "")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<TableCell className="truncate p-0 text-center text-sm text-foreground sm:px-3">
|
|
||||||
<ShadTooltip
|
|
||||||
styleClasses="z-50"
|
|
||||||
content={
|
|
||||||
myData.node?.template[templateParam].proxy
|
|
||||||
? myData.node?.template[templateParam]
|
|
||||||
.proxy?.id
|
|
||||||
: myData.node?.template[templateParam]
|
|
||||||
.display_name
|
|
||||||
? myData.node!.template[templateParam]
|
|
||||||
.display_name
|
|
||||||
: myData.node?.template[templateParam]
|
|
||||||
.name
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
{myData.node?.template[templateParam]
|
|
||||||
.display_name
|
|
||||||
? myData.node!.template[templateParam]
|
|
||||||
.display_name
|
|
||||||
: myData.node?.template[templateParam]
|
|
||||||
.name}
|
|
||||||
</span>
|
|
||||||
</ShadTooltip>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="truncate p-0 text-center text-sm text-foreground sm:px-3">
|
|
||||||
<ShadTooltip
|
|
||||||
styleClasses="z-50"
|
|
||||||
content={
|
|
||||||
data.node?.template[templateParam]?.info ??
|
|
||||||
null
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
{data.node?.template[templateParam]?.info ??
|
|
||||||
""}
|
|
||||||
</span>
|
|
||||||
</ShadTooltip>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="w-[300px] p-0 text-center text-xs text-foreground ">
|
|
||||||
<Case
|
|
||||||
condition={
|
|
||||||
type(templateParam) === "str" &&
|
|
||||||
!myData.node!.template[templateParam]
|
|
||||||
.options
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="mx-auto">
|
|
||||||
{myData.node!.template[templateParam]
|
|
||||||
?.list ? (
|
|
||||||
<InputListComponent
|
|
||||||
componentName={templateParam}
|
|
||||||
editNode={true}
|
|
||||||
disabled={disabled}
|
|
||||||
value={
|
|
||||||
!myData.node!.template[templateParam]
|
|
||||||
.value ||
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.value === ""
|
|
||||||
? [""]
|
|
||||||
: myData.node!.template[
|
|
||||||
templateParam
|
|
||||||
].value
|
|
||||||
}
|
|
||||||
onChange={(value: string[]) => {
|
|
||||||
handleOnNewValue(
|
|
||||||
value,
|
|
||||||
templateParam
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : myData.node!.template[templateParam]
|
|
||||||
.multiline ? (
|
|
||||||
<TextAreaComponent
|
|
||||||
id={
|
|
||||||
"textarea-edit-" +
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.name
|
|
||||||
}
|
|
||||||
data-testid={
|
|
||||||
"textarea-edit-" +
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.name
|
|
||||||
}
|
|
||||||
disabled={disabled}
|
|
||||||
editNode={true}
|
|
||||||
value={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.value ?? ""
|
|
||||||
}
|
|
||||||
onChange={(
|
|
||||||
value: string | string[]
|
|
||||||
) => {
|
|
||||||
handleOnNewValue(
|
|
||||||
value,
|
|
||||||
templateParam
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<InputGlobalComponent
|
|
||||||
disabled={disabled}
|
|
||||||
editNode={true}
|
|
||||||
onChange={(value) =>
|
|
||||||
handleOnNewValue(value, templateParam)
|
|
||||||
}
|
|
||||||
setDb={(value) => {
|
|
||||||
setMyData((oldData) => {
|
|
||||||
let newData = cloneDeep(oldData);
|
|
||||||
newData.node!.template[
|
|
||||||
templateParam
|
|
||||||
].load_from_db = value;
|
|
||||||
return newData;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
name={templateParam}
|
|
||||||
data={myData}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Case>
|
|
||||||
|
|
||||||
<Case
|
|
||||||
condition={
|
|
||||||
type(templateParam) === "NestedDict"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className=" w-full">
|
|
||||||
<DictComponent
|
|
||||||
disabled={disabled}
|
|
||||||
editNode={true}
|
|
||||||
value={
|
|
||||||
myData.node!.template[
|
|
||||||
templateParam
|
|
||||||
]?.value?.toString() === "{}"
|
|
||||||
? {}
|
|
||||||
: myData.node!.template[templateParam]
|
|
||||||
.value
|
|
||||||
}
|
|
||||||
onChange={(newValue) => {
|
|
||||||
myData.node!.template[
|
|
||||||
templateParam
|
|
||||||
].value = newValue;
|
|
||||||
handleOnNewValue(
|
|
||||||
newValue,
|
|
||||||
templateParam
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
id="editnode-div-dict-input"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Case>
|
|
||||||
|
|
||||||
<Case
|
|
||||||
condition={type(templateParam) === "dict"}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={classNames(
|
|
||||||
"max-h-48 w-full overflow-auto custom-scroll",
|
|
||||||
myData.node!.template[templateParam].value
|
|
||||||
?.length > 1
|
|
||||||
? "my-3"
|
|
||||||
: ""
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<KeypairListComponent
|
|
||||||
disabled={disabled}
|
|
||||||
editNode={true}
|
|
||||||
value={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.value?.length === 0 ||
|
|
||||||
!myData.node!.template[templateParam]
|
|
||||||
.value
|
|
||||||
? [{ "": "" }]
|
|
||||||
: convertObjToArray(
|
|
||||||
myData.node!.template[
|
|
||||||
templateParam
|
|
||||||
].value,
|
|
||||||
type(templateParam)!
|
|
||||||
)
|
|
||||||
}
|
|
||||||
duplicateKey={errorDuplicateKey}
|
|
||||||
onChange={(newValue) => {
|
|
||||||
const valueToNumbers =
|
|
||||||
convertValuesToNumbers(newValue);
|
|
||||||
myData.node!.template[
|
|
||||||
templateParam
|
|
||||||
].value = valueToNumbers;
|
|
||||||
setErrorDuplicateKey(
|
|
||||||
hasDuplicateKeys(valueToNumbers)
|
|
||||||
);
|
|
||||||
handleOnNewValue(
|
|
||||||
valueToNumbers,
|
|
||||||
templateParam
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
isList={
|
|
||||||
data.node?.template[templateParam]
|
|
||||||
?.list ?? false
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Case>
|
|
||||||
|
|
||||||
<Case
|
|
||||||
condition={type(templateParam) === "bool"}
|
|
||||||
>
|
|
||||||
<div className="ml-auto">
|
|
||||||
{" "}
|
|
||||||
<ToggleShadComponent
|
|
||||||
id={
|
|
||||||
"toggle-edit-" +
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.name
|
|
||||||
}
|
|
||||||
disabled={disabled}
|
|
||||||
enabled={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.value
|
|
||||||
}
|
|
||||||
setEnabled={(isEnabled) => {
|
|
||||||
handleOnNewValue(
|
|
||||||
isEnabled,
|
|
||||||
templateParam
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
size="small"
|
|
||||||
editNode={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Case>
|
|
||||||
|
|
||||||
<Case
|
|
||||||
condition={type(templateParam) === "float"}
|
|
||||||
>
|
|
||||||
<div className="mx-auto">
|
|
||||||
<FloatComponent
|
|
||||||
disabled={disabled}
|
|
||||||
editNode={true}
|
|
||||||
rangeSpec={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.rangeSpec
|
|
||||||
}
|
|
||||||
value={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.value ?? ""
|
|
||||||
}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleOnNewValue(value, templateParam);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Case>
|
|
||||||
|
|
||||||
<Case
|
|
||||||
condition={
|
|
||||||
type(templateParam) === "str" &&
|
|
||||||
myData.node!.template[templateParam].options
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="mx-auto">
|
|
||||||
<Dropdown
|
|
||||||
editNode={true}
|
|
||||||
options={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.options
|
|
||||||
}
|
|
||||||
onSelect={(value) =>
|
|
||||||
handleOnNewValue(value, templateParam)
|
|
||||||
}
|
|
||||||
value={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.value ?? "Choose an option"
|
|
||||||
}
|
|
||||||
id={
|
|
||||||
"dropdown-edit-" +
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.name
|
|
||||||
}
|
|
||||||
></Dropdown>
|
|
||||||
</div>
|
|
||||||
</Case>
|
|
||||||
|
|
||||||
<Case condition={type(templateParam) === "int"}>
|
|
||||||
<div className="mx-auto">
|
|
||||||
<IntComponent
|
|
||||||
rangeSpec={
|
|
||||||
data.node?.template[templateParam]
|
|
||||||
?.rangeSpec
|
|
||||||
}
|
|
||||||
id={
|
|
||||||
"edit-int-input-" +
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.name
|
|
||||||
}
|
|
||||||
disabled={disabled}
|
|
||||||
editNode={true}
|
|
||||||
value={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.value ?? ""
|
|
||||||
}
|
|
||||||
onChange={(value) => {
|
|
||||||
handleOnNewValue(value, templateParam);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Case>
|
|
||||||
|
|
||||||
<Case
|
|
||||||
condition={type(templateParam) === "file"}
|
|
||||||
>
|
|
||||||
<div className="mx-auto">
|
|
||||||
<InputFileComponent
|
|
||||||
editNode={true}
|
|
||||||
disabled={disabled}
|
|
||||||
value={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.value ?? ""
|
|
||||||
}
|
|
||||||
onChange={(value: string | string[]) => {
|
|
||||||
handleOnNewValue(value, templateParam);
|
|
||||||
}}
|
|
||||||
fileTypes={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.fileTypes
|
|
||||||
}
|
|
||||||
onFileChange={(filePath: string) => {
|
|
||||||
data.node!.template[
|
|
||||||
templateParam
|
|
||||||
].file_path = filePath;
|
|
||||||
}}
|
|
||||||
></InputFileComponent>
|
|
||||||
</div>
|
|
||||||
</Case>
|
|
||||||
|
|
||||||
<Case
|
|
||||||
condition={type(templateParam) === "prompt"}
|
|
||||||
>
|
|
||||||
<div className="mx-auto">
|
|
||||||
<PromptAreaComponent
|
|
||||||
readonly={
|
|
||||||
myData.node?.flow ? true : false
|
|
||||||
}
|
|
||||||
field_name={templateParam}
|
|
||||||
editNode={true}
|
|
||||||
disabled={disabled}
|
|
||||||
nodeClass={myData.node}
|
|
||||||
setNodeClass={(nodeClass) => {
|
|
||||||
myData.node = nodeClass;
|
|
||||||
}}
|
|
||||||
value={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.value ?? ""
|
|
||||||
}
|
|
||||||
onChange={(value: string | string[]) => {
|
|
||||||
handleOnNewValue(value, templateParam);
|
|
||||||
}}
|
|
||||||
id={
|
|
||||||
"prompt-area-edit-" +
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.name
|
|
||||||
}
|
|
||||||
data-testid={
|
|
||||||
"modal-prompt-input-" +
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.name
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Case>
|
|
||||||
|
|
||||||
<Case
|
|
||||||
condition={type(templateParam) === "code"}
|
|
||||||
>
|
|
||||||
<div className="mx-auto">
|
|
||||||
<CodeAreaComponent
|
|
||||||
readonly={
|
|
||||||
myData.node?.flow &&
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.dynamic
|
|
||||||
? true
|
|
||||||
: false
|
|
||||||
}
|
|
||||||
dynamic={
|
|
||||||
data.node!.template[templateParam]
|
|
||||||
?.dynamic ?? false
|
|
||||||
}
|
|
||||||
setNodeClass={(nodeClass) => {
|
|
||||||
data.node = nodeClass;
|
|
||||||
}}
|
|
||||||
nodeClass={data.node}
|
|
||||||
disabled={disabled}
|
|
||||||
editNode={true}
|
|
||||||
value={
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.value ?? ""
|
|
||||||
}
|
|
||||||
onChange={(value: string | string[]) => {
|
|
||||||
handleOnNewValue(value, templateParam);
|
|
||||||
}}
|
|
||||||
id={
|
|
||||||
"code-area-edit" +
|
|
||||||
myData.node!.template[templateParam]
|
|
||||||
.name
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Case>
|
|
||||||
|
|
||||||
<Case condition={type(templateParam) === "Any"}>
|
|
||||||
<>-</>
|
|
||||||
</Case>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="p-0 text-right">
|
|
||||||
<div className="items-center text-center">
|
|
||||||
<ToggleShadComponent
|
|
||||||
id={
|
|
||||||
"show" +
|
|
||||||
myData.node?.template[templateParam].name
|
|
||||||
}
|
|
||||||
enabled={
|
|
||||||
!myData.node?.template[templateParam]
|
|
||||||
.advanced
|
|
||||||
}
|
|
||||||
setEnabled={(e) => {
|
|
||||||
changeAdvanced(templateParam);
|
|
||||||
}}
|
|
||||||
disabled={disabled}
|
|
||||||
size="small"
|
|
||||||
editNode={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,10 @@ import { nodeIconsLucide } from "../../utils/styleUtils";
|
||||||
import BaseModal from "../baseModal";
|
import BaseModal from "../baseModal";
|
||||||
|
|
||||||
export default function SecretKeyModal({
|
export default function SecretKeyModal({
|
||||||
title,
|
|
||||||
cancelText,
|
|
||||||
confirmationText,
|
|
||||||
children,
|
children,
|
||||||
icon,
|
|
||||||
data,
|
data,
|
||||||
onCloseModal,
|
onCloseModal,
|
||||||
}: ApiKeyType) {
|
}: ApiKeyType) {
|
||||||
const Icon: any = nodeIconsLucide[icon];
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [apiKeyName, setApiKeyName] = useState(data?.apikeyname ?? "");
|
const [apiKeyName, setApiKeyName] = useState(data?.apikeyname ?? "");
|
||||||
const [apiKeyValue, setApiKeyValue] = useState("");
|
const [apiKeyValue, setApiKeyValue] = useState("");
|
||||||
|
|
@ -66,118 +61,91 @@ export default function SecretKeyModal({
|
||||||
.catch((err) => {});
|
.catch((err) => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleSubmitForm() {
|
||||||
|
if (!renderKey) {
|
||||||
|
setRenderKey(true);
|
||||||
|
handleAddNewKey();
|
||||||
|
} else {
|
||||||
|
setOpen(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseModal size="small-h-full" open={open} setOpen={setOpen}>
|
<BaseModal
|
||||||
|
onSubmit={handleSubmitForm}
|
||||||
|
size="small-h-full"
|
||||||
|
open={open}
|
||||||
|
setOpen={setOpen}
|
||||||
|
>
|
||||||
<BaseModal.Trigger>{children}</BaseModal.Trigger>
|
<BaseModal.Trigger>{children}</BaseModal.Trigger>
|
||||||
<BaseModal.Header description={""}>
|
<BaseModal.Header
|
||||||
<span className="pr-2">{title}</span>
|
description={
|
||||||
<Icon
|
renderKey ? (
|
||||||
name="icon"
|
<>
|
||||||
className="h-6 w-6 pl-1 text-foreground"
|
{" "}
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</BaseModal.Header>
|
|
||||||
<BaseModal.Content>
|
|
||||||
{renderKey === true && (
|
|
||||||
<>
|
|
||||||
<span className="text-xs">
|
|
||||||
Please save this secret key somewhere safe and accessible. For
|
Please save this secret key somewhere safe and accessible. For
|
||||||
security reasons,{" "}
|
security reasons,{" "}
|
||||||
<strong>you won't be able to view it again</strong> through your
|
<strong>you won't be able to view it again</strong> through your
|
||||||
account. If you lose this secret key, you'll need to generate a
|
account. If you lose this secret key, you'll need to generate a
|
||||||
new one.
|
new one.
|
||||||
</span>
|
</>
|
||||||
<div className="flex pt-3">
|
) : (
|
||||||
|
<>Create a secret API Key to use Langflow API.</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span className="pr-2">Create API Key</span>
|
||||||
|
<IconComponent
|
||||||
|
name="Key"
|
||||||
|
className="h-6 w-6 pl-1 text-foreground"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</BaseModal.Header>
|
||||||
|
<BaseModal.Content>
|
||||||
|
{renderKey ? (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<Input ref={inputRef} readOnly={true} value={apiKeyValue} />
|
<Input ref={inputRef} readOnly={true} value={apiKeyValue} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<Button
|
||||||
<Button
|
onClick={() => {
|
||||||
className="ml-3"
|
handleCopyClick();
|
||||||
onClick={() => {
|
}}
|
||||||
handleCopyClick();
|
variant="none"
|
||||||
}}
|
size="none"
|
||||||
>
|
>
|
||||||
{textCopied ? (
|
{textCopied ? (
|
||||||
<IconComponent name="Copy" className="h-4 w-4" />
|
<IconComponent name="Copy" className="h-4 w-4" />
|
||||||
) : (
|
) : (
|
||||||
<IconComponent name="Check" className="h-4 w-4" />
|
<IconComponent name="Check" className="h-4 w-4" />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
) : (
|
||||||
|
<Form.Field name="apikey">
|
||||||
<Form.Root
|
<div className="flex items-center justify-between gap-2">
|
||||||
onSubmit={(event) => {
|
<Form.Control asChild>
|
||||||
setRenderKey(true);
|
<Input
|
||||||
handleAddNewKey();
|
//fake api key
|
||||||
event.preventDefault();
|
id="primary-input"
|
||||||
}}
|
value={apiKeyName}
|
||||||
>
|
ref={inputRef}
|
||||||
{renderKey === false && (
|
onChange={({ target: { value } }) => {
|
||||||
<div className="grid gap-5">
|
setApiKeyName(value);
|
||||||
<Form.Field name="username">
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "baseline",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
}}
|
}}
|
||||||
>
|
placeholder="Insert a name for your API Key"
|
||||||
<Form.Label className="data-[invalid]:label-invalid">
|
/>
|
||||||
Name (optional){" "}
|
</Form.Control>
|
||||||
</Form.Label>
|
|
||||||
</div>
|
|
||||||
<Form.Control asChild>
|
|
||||||
<input
|
|
||||||
onChange={({ target: { value } }) => {
|
|
||||||
setApiKeyName(value);
|
|
||||||
}}
|
|
||||||
value={apiKeyName}
|
|
||||||
className="primary-input"
|
|
||||||
placeholder="My key name"
|
|
||||||
/>
|
|
||||||
</Form.Control>
|
|
||||||
</Form.Field>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</Form.Field>
|
||||||
{renderKey === false && (
|
)}
|
||||||
<div className="float-right">
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
className="mr-3"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => {
|
|
||||||
setOpen(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{cancelText}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Form.Submit asChild>
|
|
||||||
<Button className="mt-8">{confirmationText}</Button>
|
|
||||||
</Form.Submit>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{renderKey === true && (
|
|
||||||
<div className="float-right">
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setOpen(false);
|
|
||||||
setRenderKey(false);
|
|
||||||
}}
|
|
||||||
className="mt-8"
|
|
||||||
>
|
|
||||||
Done
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Form.Root>
|
|
||||||
</BaseModal.Content>
|
</BaseModal.Content>
|
||||||
|
<BaseModal.Footer
|
||||||
|
submit={{ label: renderKey ? "Done" : "Create Secret Key" }}
|
||||||
|
/>
|
||||||
</BaseModal>
|
</BaseModal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,291 +0,0 @@
|
||||||
import { useContext, useEffect, useRef, useState } from "react";
|
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
|
||||||
import ShadTooltip from "../../components/shadTooltipComponent";
|
|
||||||
import { Button } from "../../components/ui/button";
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "../../components/ui/table";
|
|
||||||
import { AuthContext } from "../../contexts/authContext";
|
|
||||||
import { deleteApiKey, getApiKey } from "../../controllers/API";
|
|
||||||
import ConfirmationModal from "../../modals/confirmationModal";
|
|
||||||
import SecretKeyModal from "../../modals/secretKeyModal";
|
|
||||||
|
|
||||||
import moment from "moment";
|
|
||||||
import Header from "../../components/headerComponent";
|
|
||||||
import {
|
|
||||||
DEL_KEY_ERROR_ALERT,
|
|
||||||
DEL_KEY_SUCCESS_ALERT,
|
|
||||||
} from "../../constants/alerts_constants";
|
|
||||||
import {
|
|
||||||
API_PAGE_PARAGRAPH_1,
|
|
||||||
API_PAGE_PARAGRAPH_2,
|
|
||||||
API_PAGE_USER_KEYS,
|
|
||||||
LAST_USED_SPAN_1,
|
|
||||||
LAST_USED_SPAN_2,
|
|
||||||
} from "../../constants/constants";
|
|
||||||
import useAlertStore from "../../stores/alertStore";
|
|
||||||
import { ApiKey } from "../../types/components";
|
|
||||||
|
|
||||||
export default function ApiKeysPage() {
|
|
||||||
const [loadingKeys, setLoadingKeys] = useState(true);
|
|
||||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
|
||||||
const { userData } = useContext(AuthContext);
|
|
||||||
const [userId, setUserId] = useState("");
|
|
||||||
const keysList = useRef([]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getKeys();
|
|
||||||
}, [userData]);
|
|
||||||
|
|
||||||
function getKeys() {
|
|
||||||
setLoadingKeys(true);
|
|
||||||
if (userData) {
|
|
||||||
getApiKey()
|
|
||||||
.then((keys: [ApiKey]) => {
|
|
||||||
keysList.current = keys["api_keys"];
|
|
||||||
setUserId(keys["user_id"]);
|
|
||||||
setLoadingKeys(false);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setLoadingKeys(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetFilter() {
|
|
||||||
getKeys();
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDeleteKey(keys) {
|
|
||||||
deleteApiKey(keys)
|
|
||||||
.then((res) => {
|
|
||||||
resetFilter();
|
|
||||||
setSuccessData({
|
|
||||||
title: DEL_KEY_SUCCESS_ALERT,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setErrorData({
|
|
||||||
title: DEL_KEY_ERROR_ALERT,
|
|
||||||
list: [error["response"]["data"]["detail"]],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function lastUsedMessage() {
|
|
||||||
return (
|
|
||||||
<div className="text-xs">
|
|
||||||
<span>
|
|
||||||
{LAST_USED_SPAN_1}
|
|
||||||
<br></br> {LAST_USED_SPAN_2}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Header></Header>
|
|
||||||
{userData && (
|
|
||||||
<div className="main-page-panel">
|
|
||||||
<div className="m-auto flex h-full flex-row justify-center">
|
|
||||||
<div className="basis-5/6">
|
|
||||||
<div className="m-auto flex h-full flex-col space-y-8 p-8 ">
|
|
||||||
<div className="flex items-center justify-between space-y-2">
|
|
||||||
<div>
|
|
||||||
<h2 className="text-2xl font-bold tracking-tight">
|
|
||||||
API keys
|
|
||||||
</h2>
|
|
||||||
<p className="text-muted-foreground">
|
|
||||||
{API_PAGE_PARAGRAPH_1}
|
|
||||||
<br />
|
|
||||||
{API_PAGE_PARAGRAPH_2}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center space-x-2"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{keysList.current &&
|
|
||||||
keysList.current.length === 0 &&
|
|
||||||
!loadingKeys && (
|
|
||||||
<>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<h2>{API_PAGE_USER_KEYS}</h2>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<>
|
|
||||||
{loadingKeys && (
|
|
||||||
<div>
|
|
||||||
<strong>Loading...</strong>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
"max-h-[15rem] overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted custom-scroll" +
|
|
||||||
(loadingKeys ? " border-0" : "")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{keysList.current &&
|
|
||||||
keysList.current.length > 0 &&
|
|
||||||
!loadingKeys && (
|
|
||||||
<Table className={"table-fixed bg-muted outline-1"}>
|
|
||||||
<TableHeader
|
|
||||||
className={
|
|
||||||
loadingKeys
|
|
||||||
? "hidden"
|
|
||||||
: "table-fixed bg-muted outline-1"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead className="h-10">Name</TableHead>
|
|
||||||
<TableHead className="h-10">Key</TableHead>
|
|
||||||
<TableHead className="h-10">Created</TableHead>
|
|
||||||
<TableHead className="flex h-10 items-center">
|
|
||||||
Last Used
|
|
||||||
<ShadTooltip
|
|
||||||
side="top"
|
|
||||||
content={lastUsedMessage()}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<IconComponent
|
|
||||||
name="Info"
|
|
||||||
className="ml-1 h-3 w-3"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ShadTooltip>
|
|
||||||
</TableHead>
|
|
||||||
<TableHead className="h-10">Total Uses</TableHead>
|
|
||||||
<TableHead className="h-10 w-[100px] text-right"></TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
{!loadingKeys && (
|
|
||||||
<TableBody>
|
|
||||||
{keysList.current.map(
|
|
||||||
(api_keys: ApiKey, index: number) => (
|
|
||||||
<TableRow key={index}>
|
|
||||||
<TableCell className="truncate py-2">
|
|
||||||
<ShadTooltip content={api_keys.name}>
|
|
||||||
<span className="cursor-default">
|
|
||||||
{api_keys.name ? api_keys.name : "-"}
|
|
||||||
</span>
|
|
||||||
</ShadTooltip>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="truncate py-2">
|
|
||||||
<span className="cursor-default">
|
|
||||||
{api_keys.api_key}
|
|
||||||
</span>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="truncate py-2 ">
|
|
||||||
<ShadTooltip
|
|
||||||
side="top"
|
|
||||||
content={moment(
|
|
||||||
api_keys.created_at
|
|
||||||
).format("YYYY-MM-DD HH:mm")}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
{moment(api_keys.created_at).format(
|
|
||||||
"YYYY-MM-DD HH:mm"
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</ShadTooltip>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="truncate py-2">
|
|
||||||
<ShadTooltip
|
|
||||||
side="top"
|
|
||||||
content={
|
|
||||||
moment(api_keys.last_used_at).format(
|
|
||||||
"YYYY-MM-DD HH:mm"
|
|
||||||
) === "Invalid date"
|
|
||||||
? "Never"
|
|
||||||
: moment(
|
|
||||||
api_keys.last_used_at
|
|
||||||
).format("YYYY-MM-DD HH:mm")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
{moment(api_keys.last_used_at).format(
|
|
||||||
"YYYY-MM-DD HH:mm"
|
|
||||||
) === "Invalid date"
|
|
||||||
? "Never"
|
|
||||||
: moment(
|
|
||||||
api_keys.last_used_at
|
|
||||||
).format("YYYY-MM-DD HH:mm")}
|
|
||||||
</div>
|
|
||||||
</ShadTooltip>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="truncate py-2">
|
|
||||||
{api_keys.total_uses}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="flex w-[100px] py-2 text-right">
|
|
||||||
<div className="flex">
|
|
||||||
<ConfirmationModal
|
|
||||||
title="Delete"
|
|
||||||
titleHeader="Delete User"
|
|
||||||
modalContentTitle="Attention!"
|
|
||||||
cancelText="Cancel"
|
|
||||||
confirmationText="Delete"
|
|
||||||
icon={"UserMinus2"}
|
|
||||||
data={api_keys.id}
|
|
||||||
index={index}
|
|
||||||
onConfirm={(index, keys) => {
|
|
||||||
handleDeleteKey(keys);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ConfirmationModal.Content>
|
|
||||||
<span>
|
|
||||||
Are you sure you want to delete
|
|
||||||
this key? This action cannot be
|
|
||||||
undone.
|
|
||||||
</span>
|
|
||||||
</ConfirmationModal.Content>
|
|
||||||
<ConfirmationModal.Trigger>
|
|
||||||
<IconComponent
|
|
||||||
name="Trash2"
|
|
||||||
className="ml-2 h-4 w-4 cursor-pointer"
|
|
||||||
/>
|
|
||||||
</ConfirmationModal.Trigger>
|
|
||||||
</ConfirmationModal>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</TableBody>
|
|
||||||
)}
|
|
||||||
</Table>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<SecretKeyModal
|
|
||||||
title="Create new secret key"
|
|
||||||
cancelText="Cancel"
|
|
||||||
confirmationText="Create secret key"
|
|
||||||
icon={"Key"}
|
|
||||||
data={userId}
|
|
||||||
onCloseModal={getKeys}
|
|
||||||
>
|
|
||||||
<Button>
|
|
||||||
<IconComponent name="Plus" className="mr-1 h-5 w-5" />
|
|
||||||
Create new secret key
|
|
||||||
</Button>
|
|
||||||
</SecretKeyModal>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -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";
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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 } });
|
||||||
|
|
|
||||||
|
|
@ -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("");
|
||||||
|
|
@ -25,7 +25,6 @@ export default function SettingsPage(): JSX.Element {
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "Global Variables",
|
title: "Global Variables",
|
||||||
href: "/settings/global-variables",
|
href: "/settings/global-variables",
|
||||||
|
|
@ -36,6 +35,16 @@ export default function SettingsPage(): JSX.Element {
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "API Keys",
|
||||||
|
href: "/settings/api-keys",
|
||||||
|
icon: (
|
||||||
|
<ForwardedIconComponent
|
||||||
|
name="Key"
|
||||||
|
className="w-4 flex-shrink-0 justify-start stroke-[1.5]"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Shortcuts",
|
title: "Shortcuts",
|
||||||
href: "/settings/shortcuts",
|
href: "/settings/shortcuts",
|
||||||
|
|
@ -50,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]"
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
@ -63,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>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
import ForwardedIconComponent from "../../../../../../components/genericIconComponent";
|
||||||
|
import { Button } from "../../../../../../components/ui/button";
|
||||||
|
import { API_PAGE_PARAGRAPH } from "../../../../../../constants/constants";
|
||||||
|
import SecretKeyModal from "../../../../../../modals/secretKeyModal";
|
||||||
|
import { cn } from "../../../../../../utils/utils";
|
||||||
|
|
||||||
|
type ApiKeyHeaderComponentProps = {
|
||||||
|
selectedRows: string[];
|
||||||
|
handleDeleteKey: () => void;
|
||||||
|
fetchApiKeys: () => void;
|
||||||
|
userId: string;
|
||||||
|
};
|
||||||
|
const ApiKeyHeaderComponent = ({
|
||||||
|
selectedRows,
|
||||||
|
handleDeleteKey,
|
||||||
|
fetchApiKeys,
|
||||||
|
userId,
|
||||||
|
}: ApiKeyHeaderComponentProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex w-full items-center justify-between gap-4 space-y-0.5">
|
||||||
|
<div className="flex w-full flex-col">
|
||||||
|
<h2 className="flex items-center text-lg font-semibold tracking-tight">
|
||||||
|
API Keys
|
||||||
|
<ForwardedIconComponent
|
||||||
|
name="Key"
|
||||||
|
className="ml-2 h-5 w-5 text-primary"
|
||||||
|
/>
|
||||||
|
</h2>
|
||||||
|
<p className="text-sm text-muted-foreground">{API_PAGE_PARAGRAPH}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-shrink-0 items-center gap-2">
|
||||||
|
<Button
|
||||||
|
data-testid="api-key-button-store"
|
||||||
|
variant="primary"
|
||||||
|
className="group px-2"
|
||||||
|
disabled={selectedRows.length === 0}
|
||||||
|
onClick={handleDeleteKey}
|
||||||
|
>
|
||||||
|
<ForwardedIconComponent
|
||||||
|
name="Trash2"
|
||||||
|
className={cn(
|
||||||
|
"h-5 w-5 text-destructive group-disabled:text-primary",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
<SecretKeyModal data={userId} onCloseModal={fetchApiKeys}>
|
||||||
|
<Button data-testid="api-key-button-store" variant="primary">
|
||||||
|
<ForwardedIconComponent name="Plus" className="mr-2 w-4" />
|
||||||
|
Add New
|
||||||
|
</Button>
|
||||||
|
</SecretKeyModal>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default ApiKeyHeaderComponent;
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import TableAutoCellRender from "../../../../../components/tableComponent/components/tableAutoCellRender";
|
||||||
|
|
||||||
|
export const getColumnDefs = () => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
headerCheckboxSelection: true,
|
||||||
|
checkboxSelection: true,
|
||||||
|
showDisabledCheckboxes: true,
|
||||||
|
headerName: "Name",
|
||||||
|
field: "name",
|
||||||
|
cellRenderer: TableAutoCellRender,
|
||||||
|
flex: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: "Key",
|
||||||
|
field: "api_key",
|
||||||
|
cellRenderer: TableAutoCellRender,
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: "Created",
|
||||||
|
field: "created_at",
|
||||||
|
cellRenderer: TableAutoCellRender,
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: "Last Used",
|
||||||
|
field: "last_used_at",
|
||||||
|
cellRenderer: TableAutoCellRender,
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: "Total Uses",
|
||||||
|
field: "total_uses",
|
||||||
|
cellRenderer: TableAutoCellRender,
|
||||||
|
flex: 1,
|
||||||
|
resizable: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { getApiKey } from "../../../../../controllers/API";
|
||||||
|
|
||||||
|
const useApiKeys = (userData, setLoadingKeys, keysList, setUserId) => {
|
||||||
|
const fetchApiKeys = () => {
|
||||||
|
setLoadingKeys(true);
|
||||||
|
getApiKey()
|
||||||
|
.then((keys) => {
|
||||||
|
keysList.current = keys["api_keys"].map((apikey) => ({
|
||||||
|
...apikey,
|
||||||
|
name: apikey.name && apikey.name !== "" ? apikey.name : "Untitled",
|
||||||
|
last_used_at: apikey.last_used_at ?? "Never",
|
||||||
|
}));
|
||||||
|
setUserId(keys["user_id"]);
|
||||||
|
setLoadingKeys(false);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setLoadingKeys(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
fetchApiKeys,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useApiKeys;
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import {
|
||||||
|
DEL_KEY_ERROR_ALERT,
|
||||||
|
DEL_KEY_ERROR_ALERT_PLURAL,
|
||||||
|
DEL_KEY_SUCCESS_ALERT,
|
||||||
|
DEL_KEY_SUCCESS_ALERT_PLURAL,
|
||||||
|
} from "../../../../../constants/alerts_constants";
|
||||||
|
import { deleteApiKey } from "../../../../../controllers/API";
|
||||||
|
|
||||||
|
const useDeleteApiKeys = (
|
||||||
|
selectedRows,
|
||||||
|
resetFilter,
|
||||||
|
setSuccessData,
|
||||||
|
setErrorData,
|
||||||
|
) => {
|
||||||
|
const handleDeleteKey = () => {
|
||||||
|
Promise.all(selectedRows.map((selectedRow) => deleteApiKey(selectedRow)))
|
||||||
|
.then(() => {
|
||||||
|
resetFilter();
|
||||||
|
setSuccessData({
|
||||||
|
title:
|
||||||
|
selectedRows.length === 1
|
||||||
|
? DEL_KEY_SUCCESS_ALERT
|
||||||
|
: DEL_KEY_SUCCESS_ALERT_PLURAL,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setErrorData({
|
||||||
|
title:
|
||||||
|
selectedRows.length === 1
|
||||||
|
? DEL_KEY_ERROR_ALERT
|
||||||
|
: DEL_KEY_ERROR_ALERT_PLURAL,
|
||||||
|
list: [error?.response?.data?.detail],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleDeleteKey,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useDeleteApiKeys;
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { SelectionChangedEvent } from "ag-grid-community";
|
||||||
|
import { useContext, useEffect, useRef, useState } from "react";
|
||||||
|
import TableComponent from "../../../../components/tableComponent";
|
||||||
|
import { Card, CardContent } from "../../../../components/ui/card";
|
||||||
|
import { AuthContext } from "../../../../contexts/authContext";
|
||||||
|
import useAlertStore from "../../../../stores/alertStore";
|
||||||
|
import ApiKeyHeaderComponent from "./components/ApiKeyHeader";
|
||||||
|
import { getColumnDefs } from "./helpers/column-defs";
|
||||||
|
import useApiKeys from "./hooks/use-api-keys";
|
||||||
|
import useDeleteApiKeys from "./hooks/use-handle-delete-key";
|
||||||
|
|
||||||
|
export default function ApiKeysPage() {
|
||||||
|
const [loadingKeys, setLoadingKeys] = useState(true);
|
||||||
|
const [selectedRows, setSelectedRows] = useState<string[]>([]);
|
||||||
|
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||||
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
|
const { userData } = useContext(AuthContext);
|
||||||
|
const [userId, setUserId] = useState("");
|
||||||
|
const keysList = useRef([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchApiKeys();
|
||||||
|
}, [userData]);
|
||||||
|
|
||||||
|
const { fetchApiKeys } = useApiKeys(
|
||||||
|
userData,
|
||||||
|
setLoadingKeys,
|
||||||
|
keysList,
|
||||||
|
setUserId,
|
||||||
|
);
|
||||||
|
|
||||||
|
function resetFilter() {
|
||||||
|
fetchApiKeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { handleDeleteKey } = useDeleteApiKeys(
|
||||||
|
selectedRows,
|
||||||
|
resetFilter,
|
||||||
|
setSuccessData,
|
||||||
|
setErrorData,
|
||||||
|
);
|
||||||
|
|
||||||
|
const columnDefs = getColumnDefs();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-full flex-col justify-between gap-6">
|
||||||
|
<ApiKeyHeaderComponent
|
||||||
|
selectedRows={selectedRows}
|
||||||
|
handleDeleteKey={handleDeleteKey}
|
||||||
|
fetchApiKeys={fetchApiKeys}
|
||||||
|
userId={userId}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex h-full w-full flex-col justify-between">
|
||||||
|
<TableComponent
|
||||||
|
overlayNoRowsTemplate="No data available"
|
||||||
|
onSelectionChanged={(event: SelectionChangedEvent) => {
|
||||||
|
setSelectedRows(event.api.getSelectedRows().map((row) => row.id));
|
||||||
|
}}
|
||||||
|
rowSelection="multiple"
|
||||||
|
suppressRowClickSelection={true}
|
||||||
|
pagination={true}
|
||||||
|
columnDefs={columnDefs}
|
||||||
|
rowData={keysList.current}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ import MessagesPage from "./pages/SettingsPage/pages/messagesPage";
|
||||||
|
|
||||||
const AdminPage = lazy(() => import("./pages/AdminPage"));
|
const AdminPage = lazy(() => import("./pages/AdminPage"));
|
||||||
const LoginAdminPage = lazy(() => import("./pages/AdminPage/LoginPage"));
|
const LoginAdminPage = lazy(() => import("./pages/AdminPage/LoginPage"));
|
||||||
const ApiKeysPage = lazy(() => import("./pages/ApiKeysPage"));
|
const ApiKeysPage = lazy(
|
||||||
|
() => import("./pages/SettingsPage/pages/ApiKeysPage"),
|
||||||
|
);
|
||||||
const DeleteAccountPage = lazy(() => import("./pages/DeleteAccountPage"));
|
const DeleteAccountPage = lazy(() => import("./pages/DeleteAccountPage"));
|
||||||
const FlowPage = lazy(() => import("./pages/FlowPage"));
|
const FlowPage = lazy(() => import("./pages/FlowPage"));
|
||||||
const LoginPage = lazy(() => import("./pages/LoginPage"));
|
const LoginPage = lazy(() => import("./pages/LoginPage"));
|
||||||
|
|
@ -77,6 +79,7 @@ const Router = () => {
|
||||||
>
|
>
|
||||||
<Route index element={<Navigate replace to={"general"} />} />
|
<Route index element={<Navigate replace to={"general"} />} />
|
||||||
<Route path="global-variables" element={<GlobalVariablesPage />} />
|
<Route path="global-variables" element={<GlobalVariablesPage />} />
|
||||||
|
<Route path="api-keys" element={<ApiKeysPage />} />
|
||||||
<Route path="general/:scrollId?" element={<GeneralPage />} />
|
<Route path="general/:scrollId?" element={<GeneralPage />} />
|
||||||
<Route path="shortcuts" element={<ShortcutsPage />} />
|
<Route path="shortcuts" element={<ShortcutsPage />} />
|
||||||
<Route path="messages" element={<MessagesPage />} />
|
<Route path="messages" element={<MessagesPage />} />
|
||||||
|
|
@ -189,14 +192,6 @@ const Router = () => {
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
></Route>
|
></Route>
|
||||||
<Route
|
|
||||||
path="api-keys"
|
|
||||||
element={
|
|
||||||
<ProtectedRoute>
|
|
||||||
<ApiKeysPage />
|
|
||||||
</ProtectedRoute>
|
|
||||||
}
|
|
||||||
></Route>
|
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -100,3 +100,13 @@ select:-webkit-autofill:focus {
|
||||||
.json-view-dark {
|
.json-view-dark {
|
||||||
background-color: #141924 !important;
|
background-color: #141924 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ag-row .ag-cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.ag-cell {
|
||||||
|
line-height: 1.25rem;
|
||||||
|
padding-top: 0.675rem;
|
||||||
|
padding-bottom: 0.675rem;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ export type InputComponentType = {
|
||||||
export type ToggleComponentType = {
|
export type ToggleComponentType = {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
setEnabled: (state: boolean) => void;
|
setEnabled: (state: boolean) => void;
|
||||||
disabled: boolean | undefined;
|
disabled?: boolean | undefined;
|
||||||
size: "small" | "medium" | "large";
|
size: "small" | "medium" | "large";
|
||||||
id?: string;
|
id?: string;
|
||||||
editNode?: boolean;
|
editNode?: boolean;
|
||||||
|
|
@ -411,11 +411,7 @@ export type UserInputType = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ApiKeyType = {
|
export type ApiKeyType = {
|
||||||
title: string;
|
|
||||||
cancelText: string;
|
|
||||||
confirmationText: string;
|
|
||||||
children: ReactElement;
|
children: ReactElement;
|
||||||
icon: string;
|
|
||||||
data?: any;
|
data?: any;
|
||||||
onCloseModal: () => void;
|
onCloseModal: () => void;
|
||||||
};
|
};
|
||||||
|
|
@ -577,7 +573,7 @@ export type iconsType = {
|
||||||
|
|
||||||
export type modalHeaderType = {
|
export type modalHeaderType = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
description: string | null;
|
description: string | JSX.Element | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type codeAreaModalPropsType = {
|
export type codeAreaModalPropsType = {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ import {
|
||||||
FolderIcon,
|
FolderIcon,
|
||||||
FolderPlus,
|
FolderPlus,
|
||||||
FolderPlusIcon,
|
FolderPlusIcon,
|
||||||
|
FolderUp,
|
||||||
FormInput,
|
FormInput,
|
||||||
Forward,
|
Forward,
|
||||||
Gift,
|
Gift,
|
||||||
|
|
@ -432,6 +433,7 @@ export const nodeIconsLucide: iconsType = {
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
SlidersHorizontal,
|
SlidersHorizontal,
|
||||||
Palette,
|
Palette,
|
||||||
|
FolderUp,
|
||||||
Blocks,
|
Blocks,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { ColDef, ColGroupDef } from "ag-grid-community";
|
import { ColDef, ColGroupDef } from "ag-grid-community";
|
||||||
import clsx, { ClassValue } from "clsx";
|
import clsx, { ClassValue } from "clsx";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
import TableAutoCellRender from "../components/tableAutoCellRender";
|
import TableAutoCellRender from "../components/tableComponent/components/tableAutoCellRender";
|
||||||
import { APIDataType, TemplateVariableType } from "../types/api";
|
import { APIDataType, TemplateVariableType } from "../types/api";
|
||||||
import {
|
import {
|
||||||
groupedObjType,
|
groupedObjType,
|
||||||
|
|
@ -345,8 +345,10 @@ export function freezeObject(obj: any) {
|
||||||
return JSON.parse(JSON.stringify(obj));
|
return JSON.parse(JSON.stringify(obj));
|
||||||
}
|
}
|
||||||
export function isTimeStampString(str: string): boolean {
|
export function isTimeStampString(str: string): boolean {
|
||||||
const timestampRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3}Z)?$/;
|
const timestampRegexA = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3}Z)?$/;
|
||||||
return timestampRegex.test(str);
|
const timestampRegexB = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{6})?$/;
|
||||||
|
|
||||||
|
return timestampRegexA.test(str) || timestampRegexB.test(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractColumnsFromRows(
|
export function extractColumnsFromRows(
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,8 @@ test("dropDownComponent", async ({ page }) => {
|
||||||
expect(false).toBeTruthy();
|
expect(false).toBeTruthy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
await page.getByTestId("more-options-modal").click();
|
await page.getByTestId("more-options-modal").click();
|
||||||
await page.getByTestId("edit-button-modal").click();
|
await page.getByTestId("edit-button-modal").click();
|
||||||
|
|
||||||
|
|
|
||||||
99
src/frontend/tests/end-to-end/generalBugs.spec.ts
Normal file
99
src/frontend/tests/end-to-end/generalBugs.spec.ts
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
|
test("should interact with api request", async ({ page }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
await page.waitForTimeout(2000);
|
||||||
|
|
||||||
|
let modalCount = 0;
|
||||||
|
try {
|
||||||
|
const modalTitleElement = await page?.getByTestId("modal-title");
|
||||||
|
if (modalTitleElement) {
|
||||||
|
modalCount = await modalTitleElement.count();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
modalCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (modalCount === 0) {
|
||||||
|
await page.getByText("New Project", { exact: true }).click();
|
||||||
|
await page.waitForTimeout(5000);
|
||||||
|
modalCount = await page.getByTestId("modal-title")?.count();
|
||||||
|
}
|
||||||
|
await page.getByTestId("blank-flow").click();
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
await page.getByTestId("extended-disclosure").click();
|
||||||
|
await page.getByPlaceholder("Search").click();
|
||||||
|
await page.getByPlaceholder("Search").fill("api request");
|
||||||
|
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByTestId("dataAPI Request")
|
||||||
|
.dragTo(page.locator('//*[@id="react-flow-id"]'));
|
||||||
|
await page.mouse.up();
|
||||||
|
await page.mouse.down();
|
||||||
|
await page.getByTitle("fit view").click();
|
||||||
|
await page.getByTitle("zoom out").click();
|
||||||
|
await page.getByTitle("zoom out").click();
|
||||||
|
await page.getByTitle("zoom out").click();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("erase button should clear the chat messages", async ({ page }) => {
|
||||||
|
if (!process.env.CI) {
|
||||||
|
dotenv.config();
|
||||||
|
dotenv.config({ path: path.resolve(__dirname, "../../.env") });
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.goto("/");
|
||||||
|
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
|
let modalCount = 0;
|
||||||
|
try {
|
||||||
|
const modalTitleElement = await page?.getByTestId("modal-title");
|
||||||
|
if (modalTitleElement) {
|
||||||
|
modalCount = await modalTitleElement.count();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
modalCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (modalCount === 0) {
|
||||||
|
await page.getByText("New Project", { exact: true }).click();
|
||||||
|
await page.waitForTimeout(5000);
|
||||||
|
modalCount = await page.getByTestId("modal-title")?.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.getByRole("heading", { name: "Basic Prompting" }).click();
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
|
||||||
|
await page.getByTitle("fit view").click();
|
||||||
|
await page.getByTitle("zoom out").click();
|
||||||
|
await page.getByTitle("zoom out").click();
|
||||||
|
await page.getByTitle("zoom out").click();
|
||||||
|
|
||||||
|
if (!process.env.OPENAI_API_KEY) {
|
||||||
|
//You must set the OPENAI_API_KEY on .env file to run this test
|
||||||
|
expect(false).toBe(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByTestId("popover-anchor-input-openai_api_key")
|
||||||
|
.fill(process.env.OPENAI_API_KEY ?? "");
|
||||||
|
await page.getByText("Playground", { exact: true }).click();
|
||||||
|
await page.getByPlaceholder("Send a message...").fill("Hello, how are you?");
|
||||||
|
await page.getByTestId("icon-LucideSend").click();
|
||||||
|
let valueUser = await page.getByTestId("sender_name_user").textContent();
|
||||||
|
let valueAI = await page.getByTestId("sender_name_ai").textContent();
|
||||||
|
|
||||||
|
expect(valueUser).toBe("User");
|
||||||
|
expect(valueAI).toBe("AI");
|
||||||
|
|
||||||
|
await page.getByTestId("icon-Eraser").last().click();
|
||||||
|
|
||||||
|
await page.getByText("Hello, how are you?").isHidden();
|
||||||
|
await page.getByText("AI", { exact: true }).last().isHidden();
|
||||||
|
await page.getByText("User", { exact: true }).last().isHidden();
|
||||||
|
await page.getByText("Start a conversation").isVisible();
|
||||||
|
await page.getByText("Langflow Chat").isVisible();
|
||||||
|
});
|
||||||
|
|
@ -95,5 +95,33 @@ test("should see shortcuts", async ({ page }) => {
|
||||||
await page.getByText("Delete Component", { exact: true }).isVisible();
|
await page.getByText("Delete Component", { exact: true }).isVisible();
|
||||||
await page.getByText("Open Playground", { exact: true }).isVisible();
|
await page.getByText("Open Playground", { exact: true }).isVisible();
|
||||||
await page.getByText("Undo", { exact: true }).isVisible();
|
await page.getByText("Undo", { exact: true }).isVisible();
|
||||||
await page.getByText("Redo", { exact: true }).isVisible();
|
|
||||||
|
await page.mouse.wheel(0, 10000);
|
||||||
|
|
||||||
|
await page.getByText("Redo", { exact: true }).last().isVisible();
|
||||||
|
|
||||||
|
await page.getByText("Reset Columns").last().isVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should interact with API Keys", async ({ page }) => {
|
||||||
|
await page.goto("/");
|
||||||
|
await page.waitForTimeout(2000);
|
||||||
|
await page.getByTestId("user-profile-settings").click();
|
||||||
|
await page.getByText("Settings").click();
|
||||||
|
await page.getByText("API Keys").click();
|
||||||
|
await page.getByText("API Keys", { exact: true }).nth(1).isVisible();
|
||||||
|
await page.getByText("Add New").click();
|
||||||
|
await page.getByPlaceholder("Insert a name for your API Key").isVisible();
|
||||||
|
|
||||||
|
const randomName = Math.random().toString(36).substring(2);
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByPlaceholder("Insert a name for your API Key")
|
||||||
|
.fill(randomName);
|
||||||
|
await page.getByText("Create Secret Key", { exact: true }).click();
|
||||||
|
await page.getByText("Please save").isVisible();
|
||||||
|
await page.getByTestId("icon-Copy").click();
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
|
await page.getByText("Api Key Copied!").isVisible();
|
||||||
|
await page.getByText(randomName).isVisible();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue