Merge remote-tracking branch 'origin/dev' into new-style-features
This commit is contained in:
commit
80a73f93fe
129 changed files with 6607 additions and 3026 deletions
15
Dockerfile
Normal file
15
Dockerfile
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
FROM python:3.10-slim
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install gcc g++ git make -y && apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
RUN useradd -m -u 1000 user
|
||||||
|
USER user
|
||||||
|
ENV HOME=/home/user \
|
||||||
|
PATH=/home/user/.local/bin:$PATH
|
||||||
|
|
||||||
|
WORKDIR $HOME/app
|
||||||
|
|
||||||
|
COPY --chown=user . $HOME/app
|
||||||
|
|
||||||
|
RUN pip install langflow>==0.0.86 -U --user
|
||||||
|
CMD ["python", "-m", "langflow", "--host", "0.0.0.0", "--port", "7860"]
|
||||||
7
Makefile
7
Makefile
|
|
@ -44,13 +44,18 @@ install_backend:
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
make install_backend
|
make install_backend
|
||||||
poetry run uvicorn langflow.main:app --port 7860 --reload --log-level debug
|
poetry run uvicorn src.backend.langflow.main:app --port 7860 --reload --log-level debug
|
||||||
|
|
||||||
build_and_run:
|
build_and_run:
|
||||||
echo 'Removing dist folder'
|
echo 'Removing dist folder'
|
||||||
rm -rf dist
|
rm -rf dist
|
||||||
make build && poetry run pip install dist/*.tar.gz && poetry run langflow
|
make build && poetry run pip install dist/*.tar.gz && poetry run langflow
|
||||||
|
|
||||||
|
build_and_install:
|
||||||
|
echo 'Removing dist folder'
|
||||||
|
rm -rf dist
|
||||||
|
make build && poetry run pip install dist/*.tar.gz
|
||||||
|
|
||||||
build_frontend:
|
build_frontend:
|
||||||
cd src/frontend && CI='' npm run build
|
cd src/frontend && CI='' npm run build
|
||||||
cp -r src/frontend/build src/backend/langflow/frontend
|
cp -r src/frontend/build src/backend/langflow/frontend
|
||||||
|
|
|
||||||
58
README.md
58
README.md
|
|
@ -2,10 +2,9 @@
|
||||||
|
|
||||||
# ⛓️ LangFlow
|
# ⛓️ LangFlow
|
||||||
|
|
||||||
~ A User Interface For [LangChain](https://github.com/hwchase17/langchain) ~
|
~ An effortless way to experiment and prototype [LangChain](https://github.com/hwchase17/langchain) pipelines ~
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<a href="https://huggingface.co/spaces/Logspace/LangFlow"><img src="https://huggingface.co/datasets/huggingface/badges/raw/main/open-in-hf-spaces-sm.svg" alt="HuggingFace Spaces"></a>
|
|
||||||
<img alt="GitHub Contributors" src="https://img.shields.io/github/contributors/logspace-ai/langflow" />
|
<img alt="GitHub Contributors" src="https://img.shields.io/github/contributors/logspace-ai/langflow" />
|
||||||
<img alt="GitHub Last Commit" src="https://img.shields.io/github/last-commit/logspace-ai/langflow" />
|
<img alt="GitHub Last Commit" src="https://img.shields.io/github/last-commit/logspace-ai/langflow" />
|
||||||
<img alt="" src="https://img.shields.io/github/repo-size/logspace-ai/langflow" />
|
<img alt="" src="https://img.shields.io/github/repo-size/logspace-ai/langflow" />
|
||||||
|
|
@ -14,10 +13,18 @@
|
||||||
<img alt="Github License" src="https://img.shields.io/github/license/logspace-ai/langflow" />
|
<img alt="Github License" src="https://img.shields.io/github/license/logspace-ai/langflow" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="https://discord.gg/EqksyE2EX9"><img alt="Discord Server" src="https://dcbadge.vercel.app/api/server/EqksyE2EX9?compact=true&style=flat"/></a>
|
||||||
|
<a href="https://huggingface.co/spaces/Logspace/LangFlow"><img src="https://huggingface.co/datasets/huggingface/badges/raw/main/open-in-hf-spaces-sm.svg" alt="HuggingFace Spaces"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
<a href="https://github.com/logspace-ai/langflow">
|
<a href="https://github.com/logspace-ai/langflow">
|
||||||
<img width="100%" src="https://github.com/logspace-ai/langflow/blob/main/img/langflow-demo.gif?raw=true"></a>
|
<img width="100%" src="https://github.com/logspace-ai/langflow/blob/main/img/langflow-demo.gif?raw=true"></a>
|
||||||
|
|
||||||
LangFlow is a GUI for [LangChain](https://github.com/hwchase17/langchain), designed with [react-flow](https://github.com/wbkd/react-flow) to provide an effortless way to experiment and prototype flows with drag-and-drop components and a chat box.
|
|
||||||
|
<p>
|
||||||
|
</p>
|
||||||
|
|
||||||
## 📦 Installation
|
## 📦 Installation
|
||||||
### <b>Locally</b>
|
### <b>Locally</b>
|
||||||
|
|
@ -109,24 +116,38 @@ You can use Langflow directly on your browser, or use the API endpoints on Jina
|
||||||
<summary>Show API usage (with python)</summary>
|
<summary>Show API usage (with python)</summary>
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import json
|
import requests
|
||||||
import requests
|
|
||||||
|
|
||||||
FLOW_PATH = "Time_traveller.json"
|
BASE_API_URL = "https://langflow-e3dd8820ec.wolf.jina.ai/api/v1/predict"
|
||||||
|
FLOW_ID = "864c4f98-2e59-468b-8e13-79cd8da07468"
|
||||||
|
# You can tweak the flow by adding a tweaks dictionary
|
||||||
|
# e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}}
|
||||||
|
TWEAKS = {
|
||||||
|
"ChatOpenAI-g4jEr": {},
|
||||||
|
"ConversationChain-UidfJ": {}
|
||||||
|
}
|
||||||
|
|
||||||
# HOST = 'http://localhost:7860'
|
def run_flow(message: str, flow_id: str, tweaks: dict = None) -> dict:
|
||||||
HOST = 'https://langflow-f1ed20e309.wolf.jina.ai'
|
"""
|
||||||
API_URL = f'{HOST}/predict'
|
Run a flow with a given message and optional tweaks.
|
||||||
|
|
||||||
def predict(message):
|
:param message: The message to send to the flow
|
||||||
with open(FLOW_PATH, "r") as f:
|
:param flow_id: The ID of the flow to run
|
||||||
json_data = json.load(f)
|
:param tweaks: Optional tweaks to customize the flow
|
||||||
payload = {'exported_flow': json_data, 'message': message}
|
:return: The JSON response from the flow
|
||||||
response = requests.post(API_URL, json=payload)
|
"""
|
||||||
return response.json()
|
api_url = f"{BASE_API_URL}/{flow_id}"
|
||||||
|
|
||||||
|
payload = {"message": message}
|
||||||
|
|
||||||
predict('Take me to 1920s Bangalore')
|
if tweaks:
|
||||||
|
payload["tweaks"] = tweaks
|
||||||
|
|
||||||
|
response = requests.post(api_url, json=payload)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
# Setup any tweaks you want to apply to the flow
|
||||||
|
print(run_flow("Your message", flow_id=FLOW_ID, tweaks=TWEAKS))
|
||||||
```
|
```
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|
@ -164,6 +185,11 @@ flow("Hey, have you heard of LangFlow?")
|
||||||
We welcome contributions from developers of all levels to our open-source project on GitHub. If you'd like to contribute, please check our [contributing guidelines](./CONTRIBUTING.md) and help make LangFlow more accessible.
|
We welcome contributions from developers of all levels to our open-source project on GitHub. If you'd like to contribute, please check our [contributing guidelines](./CONTRIBUTING.md) and help make LangFlow more accessible.
|
||||||
|
|
||||||
|
|
||||||
|
Join our [Discord](https://discord.com/invite/EqksyE2EX9) server to ask questions, make suggestions and showcase your projects! 🦾
|
||||||
|
|
||||||
|
<p>
|
||||||
|
</p>
|
||||||
|
|
||||||
[](https://star-history.com/#logspace-ai/langflow&Date)
|
[](https://star-history.com/#logspace-ai/langflow&Date)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
1813
poetry.lock
generated
1813
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,11 +1,12 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "langflow"
|
name = "langflow"
|
||||||
version = "0.0.88"
|
version = "0.2.2"
|
||||||
description = "A Python package with a built-in web application"
|
description = "A Python package with a built-in web application"
|
||||||
authors = ["Logspace <contact@logspace.ai>"]
|
authors = ["Logspace <contact@logspace.ai>"]
|
||||||
maintainers = [
|
maintainers = [
|
||||||
|
"Cristhian Zanforlin <cristhian.lousa@gmail.com>",
|
||||||
"Gabriel Almeida <gabriel@logspace.ai>",
|
"Gabriel Almeida <gabriel@logspace.ai>",
|
||||||
"Ibis Prevedello <ibiscp@gmail.com>",
|
"Gustavo Schaedler <gustavopoa@gmail.com>",
|
||||||
"Lucas Eduoli <lucaseduoli@gmail.com>",
|
"Lucas Eduoli <lucaseduoli@gmail.com>",
|
||||||
"Otávio Anovazzi <otavio2204@gmail.com>",
|
"Otávio Anovazzi <otavio2204@gmail.com>",
|
||||||
]
|
]
|
||||||
|
|
@ -22,22 +23,21 @@ langflow = "langflow.__main__:main"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = ">=3.9,<3.12"
|
python = ">=3.9,<3.12"
|
||||||
fastapi = "^0.96.0"
|
fastapi = "^0.98.0"
|
||||||
uvicorn = "^0.20.0"
|
uvicorn = "^0.22.0"
|
||||||
beautifulsoup4 = "^4.11.2"
|
beautifulsoup4 = "^4.11.2"
|
||||||
google-search-results = "^2.4.1"
|
google-search-results = "^2.4.1"
|
||||||
google-api-python-client = "^2.79.0"
|
google-api-python-client = "^2.79.0"
|
||||||
typer = "^0.7.0"
|
typer = "^0.9.0"
|
||||||
gunicorn = "^20.1.0"
|
gunicorn = "^20.1.0"
|
||||||
langchain = "^0.0.200"
|
langchain = "^0.0.215"
|
||||||
openai = "^0.27.8"
|
openai = "^0.27.8"
|
||||||
types-pyyaml = "^6.0.12.8"
|
types-pyyaml = "^6.0.12.8"
|
||||||
dill = "^0.3.6"
|
|
||||||
pandas = "^1.5.3"
|
pandas = "^1.5.3"
|
||||||
chromadb = "^0.3.21"
|
chromadb = "^0.3.21"
|
||||||
huggingface-hub = "^0.13.3"
|
huggingface-hub = "^0.13.3"
|
||||||
rich = "^13.3.3"
|
rich = "^13.4.2"
|
||||||
llama-cpp-python = "^0.1.50"
|
llama-cpp-python = "~0.1.0"
|
||||||
networkx = "^3.1"
|
networkx = "^3.1"
|
||||||
unstructured = "^0.5.11"
|
unstructured = "^0.5.11"
|
||||||
pypdf = "^3.7.1"
|
pypdf = "^3.7.1"
|
||||||
|
|
@ -46,25 +46,33 @@ pysrt = "^1.1.2"
|
||||||
fake-useragent = "^1.1.3"
|
fake-useragent = "^1.1.3"
|
||||||
docstring-parser = "^0.15"
|
docstring-parser = "^0.15"
|
||||||
psycopg2-binary = "^2.9.6"
|
psycopg2-binary = "^2.9.6"
|
||||||
pyarrow = "^11.0.0"
|
pyarrow = "^12.0.0"
|
||||||
tiktoken = "^0.3.3"
|
tiktoken = "~0.4.0"
|
||||||
wikipedia = "^1.4.0"
|
wikipedia = "^1.4.0"
|
||||||
langchain-serve = { version = ">0.0.39", optional = true }
|
langchain-serve = { version = ">0.0.39", optional = true }
|
||||||
qdrant-client = "^1.2.0"
|
qdrant-client = "^1.2.0"
|
||||||
websockets = "^11.0.3"
|
websockets = "^10.3"
|
||||||
weaviate-client = "^3.19.2"
|
weaviate-client = "^3.21.0"
|
||||||
jina = "3.15.2"
|
jina = "3.15.2"
|
||||||
sentence-transformers = "^2.2.2"
|
sentence-transformers = "^2.2.2"
|
||||||
ctransformers = "^0.2.2"
|
ctransformers = "^0.2.2"
|
||||||
cohere = "^4.6.0"
|
cohere = "^4.6.0"
|
||||||
|
python-multipart = "^0.0.6"
|
||||||
sqlmodel = "^0.0.8"
|
sqlmodel = "^0.0.8"
|
||||||
faiss-cpu = "^1.7.4"
|
faiss-cpu = "^1.7.4"
|
||||||
anthropic = "^0.2.9"
|
anthropic = "^0.2.10"
|
||||||
orjson = "^3.9.0"
|
orjson = "^3.9.1"
|
||||||
multiprocess = "^0.70.14"
|
multiprocess = "^0.70.14"
|
||||||
|
cachetools = "^5.3.1"
|
||||||
|
types-cachetools = "^5.3.0.5"
|
||||||
|
appdirs = "^1.4.4"
|
||||||
|
pinecone-client = "^2.2.2"
|
||||||
|
supabase = "^1.0.3"
|
||||||
|
pymongo = "^4.4.0"
|
||||||
|
certifi = "^2023.5.7"
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
black = "^23.1.0"
|
black = "^23.1.0"
|
||||||
ipykernel = "^6.21.2"
|
ipykernel = "^6.21.2"
|
||||||
mypy = "^1.1.1"
|
mypy = "^1.1.1"
|
||||||
|
|
@ -76,6 +84,7 @@ requests = "^2.28.0"
|
||||||
pytest-cov = "^4.0.0"
|
pytest-cov = "^4.0.0"
|
||||||
pandas-stubs = "^2.0.0.230412"
|
pandas-stubs = "^2.0.0.230412"
|
||||||
types-pillow = "^9.5.0.2"
|
types-pillow = "^9.5.0.2"
|
||||||
|
types-appdirs = "^1.4.3.5"
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.extras]
|
[tool.poetry.extras]
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
from fastapi import FastAPI
|
||||||
import httpx
|
import httpx
|
||||||
from multiprocess import Process, cpu_count # type: ignore
|
from multiprocess import Process, cpu_count # type: ignore
|
||||||
import platform
|
import platform
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
import socket
|
||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
from rich import box
|
from rich import box
|
||||||
from rich import print as rprint
|
from rich import print as rprint
|
||||||
import typer
|
import typer
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from fastapi.responses import FileResponse
|
||||||
from langflow.main import create_app
|
from langflow.main import create_app
|
||||||
from langflow.settings import settings
|
from langflow.settings import settings
|
||||||
from langflow.utils.logger import configure, logger
|
from langflow.utils.logger import configure, logger
|
||||||
|
|
@ -29,17 +30,20 @@ def get_number_of_workers(workers=None):
|
||||||
|
|
||||||
def update_settings(
|
def update_settings(
|
||||||
config: str,
|
config: str,
|
||||||
|
cache: str,
|
||||||
dev: bool = False,
|
dev: bool = False,
|
||||||
database_url: Optional[str] = None,
|
database_url: Optional[str] = None,
|
||||||
save_api_keys: bool = True,
|
remove_api_keys: bool = False,
|
||||||
):
|
):
|
||||||
"""Update the settings from a config file."""
|
"""Update the settings from a config file."""
|
||||||
if config:
|
if config:
|
||||||
settings.update_from_yaml(config, dev=dev)
|
settings.update_from_yaml(config, dev=dev)
|
||||||
if database_url:
|
if database_url:
|
||||||
settings.update_settings(database_url=database_url)
|
settings.update_settings(database_url=database_url)
|
||||||
if save_api_keys:
|
if remove_api_keys:
|
||||||
settings.update_settings(save_api_keys=save_api_keys)
|
settings.update_settings(remove_api_keys=remove_api_keys)
|
||||||
|
if cache:
|
||||||
|
settings.update_settings(cache=cache)
|
||||||
|
|
||||||
|
|
||||||
def serve_on_jcloud():
|
def serve_on_jcloud():
|
||||||
|
|
@ -101,6 +105,11 @@ def serve(
|
||||||
),
|
),
|
||||||
log_level: str = typer.Option("critical", help="Logging level."),
|
log_level: str = typer.Option("critical", help="Logging level."),
|
||||||
log_file: Path = typer.Option("logs/langflow.log", help="Path to the log file."),
|
log_file: Path = typer.Option("logs/langflow.log", help="Path to the log file."),
|
||||||
|
cache: str = typer.Argument(
|
||||||
|
envvar="LANGCHAIN_CACHE",
|
||||||
|
help="Type of cache to use. (InMemoryCache, SQLiteCache)",
|
||||||
|
default="SQLiteCache",
|
||||||
|
),
|
||||||
jcloud: bool = typer.Option(False, help="Deploy on Jina AI Cloud"),
|
jcloud: bool = typer.Option(False, help="Deploy on Jina AI Cloud"),
|
||||||
dev: bool = typer.Option(False, help="Run in development mode (may contain bugs)"),
|
dev: bool = typer.Option(False, help="Run in development mode (may contain bugs)"),
|
||||||
database_url: str = typer.Option(
|
database_url: str = typer.Option(
|
||||||
|
|
@ -114,26 +123,12 @@ def serve(
|
||||||
open_browser: bool = typer.Option(
|
open_browser: bool = typer.Option(
|
||||||
True, help="Open the browser after starting the server."
|
True, help="Open the browser after starting the server."
|
||||||
),
|
),
|
||||||
save_api_keys: bool = typer.Option(
|
remove_api_keys: bool = typer.Option(
|
||||||
True, help="Save API keys in your projects for future use."
|
False, help="Remove API keys from the projects saved in the database."
|
||||||
),
|
),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Run the Langflow server.
|
Run the Langflow server.
|
||||||
|
|
||||||
Args:
|
|
||||||
host (str): Host to bind the server to.
|
|
||||||
workers (int): Number of worker processes.
|
|
||||||
timeout (int): Worker timeout in seconds.
|
|
||||||
port (int): Port to listen on.
|
|
||||||
config (str): Path to the configuration file.
|
|
||||||
env_file (Path): Path to the .env file containing environment variables.
|
|
||||||
log_level (str): Logging level.
|
|
||||||
log_file (Path): Path to the log file.
|
|
||||||
jcloud (bool): Deploy on Jina AI Cloud.
|
|
||||||
dev (bool): Run in development mode (may contain bugs).
|
|
||||||
path (str): Path to the frontend directory containing build files. This is for development purposes only.
|
|
||||||
open_browser (bool): Open the browser after starting the server.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if jcloud:
|
if jcloud:
|
||||||
|
|
@ -143,20 +138,25 @@ def serve(
|
||||||
|
|
||||||
configure(log_level=log_level, log_file=log_file)
|
configure(log_level=log_level, log_file=log_file)
|
||||||
update_settings(
|
update_settings(
|
||||||
config, dev=dev, database_url=database_url, save_api_keys=save_api_keys
|
config,
|
||||||
|
dev=dev,
|
||||||
|
database_url=database_url,
|
||||||
|
remove_api_keys=remove_api_keys,
|
||||||
|
cache=cache,
|
||||||
)
|
)
|
||||||
app = create_app()
|
|
||||||
# get the directory of the current file
|
# get the directory of the current file
|
||||||
if not path:
|
if not path:
|
||||||
frontend_path = Path(__file__).parent
|
frontend_path = Path(__file__).parent
|
||||||
static_files_dir = frontend_path / "frontend"
|
static_files_dir = frontend_path / "frontend"
|
||||||
else:
|
else:
|
||||||
static_files_dir = Path(path)
|
static_files_dir = Path(path)
|
||||||
app.mount(
|
|
||||||
"/",
|
app = create_app()
|
||||||
StaticFiles(directory=static_files_dir, html=True),
|
setup_static_files(app, static_files_dir)
|
||||||
name="static",
|
# check if port is being used
|
||||||
)
|
if is_port_in_use(port, host):
|
||||||
|
port = get_free_port(port)
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
"bind": f"{host}:{port}",
|
"bind": f"{host}:{port}",
|
||||||
"workers": get_number_of_workers(workers),
|
"workers": get_number_of_workers(workers),
|
||||||
|
|
@ -164,6 +164,17 @@ def serve(
|
||||||
"timeout": timeout,
|
"timeout": timeout,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if platform.system() in ["Windows"]:
|
||||||
|
# Run using uvicorn on MacOS and Windows
|
||||||
|
# Windows doesn't support gunicorn
|
||||||
|
# MacOS requires an env variable to be set to use gunicorn
|
||||||
|
run_on_windows(host, port, log_level, options, app)
|
||||||
|
else:
|
||||||
|
# Run using gunicorn on Linux
|
||||||
|
run_on_mac_or_linux(host, port, log_level, options, app, open_browser)
|
||||||
|
|
||||||
|
|
||||||
|
def run_on_mac_or_linux(host, port, log_level, options, app, open_browser=True):
|
||||||
webapp_process = Process(
|
webapp_process = Process(
|
||||||
target=run_langflow, args=(host, port, log_level, options, app)
|
target=run_langflow, args=(host, port, log_level, options, app)
|
||||||
)
|
)
|
||||||
|
|
@ -171,7 +182,8 @@ def serve(
|
||||||
status_code = 0
|
status_code = 0
|
||||||
while status_code != 200:
|
while status_code != 200:
|
||||||
try:
|
try:
|
||||||
status_code = httpx.get(f"http://{host}:{port}").status_code
|
status_code = httpx.get(f"http://{host}:{port}/health").status_code
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
@ -180,11 +192,72 @@ def serve(
|
||||||
webbrowser.open(f"http://{host}:{port}")
|
webbrowser.open(f"http://{host}:{port}")
|
||||||
|
|
||||||
|
|
||||||
|
def run_on_windows(host, port, log_level, options, app):
|
||||||
|
"""
|
||||||
|
Run the Langflow server on Windows.
|
||||||
|
"""
|
||||||
|
print_banner(host, port)
|
||||||
|
run_langflow(host, port, log_level, options, app)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_static_files(app: FastAPI, static_files_dir: Path):
|
||||||
|
"""
|
||||||
|
Setup the static files directory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
app (FastAPI): FastAPI app.
|
||||||
|
path (str): Path to the static files directory.
|
||||||
|
"""
|
||||||
|
app.mount(
|
||||||
|
"/",
|
||||||
|
StaticFiles(directory=static_files_dir, html=True),
|
||||||
|
name="static",
|
||||||
|
)
|
||||||
|
|
||||||
|
@app.exception_handler(404)
|
||||||
|
async def custom_404_handler(request, __):
|
||||||
|
path = static_files_dir / "index.html"
|
||||||
|
|
||||||
|
if not path.exists():
|
||||||
|
raise RuntimeError(f"File at path {path} does not exist.")
|
||||||
|
return FileResponse(path)
|
||||||
|
|
||||||
|
|
||||||
|
def is_port_in_use(port, host="localhost"):
|
||||||
|
"""
|
||||||
|
Check if a port is in use.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
port (int): The port number to check.
|
||||||
|
host (str): The host to check the port on. Defaults to 'localhost'.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if the port is in use, False otherwise.
|
||||||
|
"""
|
||||||
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||||
|
return s.connect_ex((host, port)) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def get_free_port(port):
|
||||||
|
"""
|
||||||
|
Given a used port, find a free port.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
port (int): The port number to check.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: A free port number.
|
||||||
|
"""
|
||||||
|
while is_port_in_use(port):
|
||||||
|
port += 1
|
||||||
|
return port
|
||||||
|
|
||||||
|
|
||||||
def print_banner(host, port):
|
def print_banner(host, port):
|
||||||
# console = Console()
|
# console = Console()
|
||||||
|
|
||||||
word = "LangFlow"
|
word = "LangFlow"
|
||||||
colors = ["#690080", "#660099", "#4d00b3", "#3300cc", "#1a00e6", "#0000ff"]
|
colors = ["#3300cc"]
|
||||||
|
|
||||||
styled_word = ""
|
styled_word = ""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import json
|
|
||||||
from fastapi import (
|
from fastapi import (
|
||||||
APIRouter,
|
APIRouter,
|
||||||
HTTPException,
|
HTTPException,
|
||||||
|
|
@ -7,15 +6,16 @@ from fastapi import (
|
||||||
status,
|
status,
|
||||||
)
|
)
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from langflow.api.v1.schemas import BuiltResponse, InitResponse
|
from langflow.api.v1.schemas import BuiltResponse, InitResponse, StreamData
|
||||||
|
|
||||||
from langflow.chat.manager import ChatManager
|
from langflow.chat.manager import ChatManager
|
||||||
from langflow.graph.graph.base import Graph
|
from langflow.graph.graph.base import Graph
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
|
from cachetools import LRUCache
|
||||||
|
|
||||||
router = APIRouter(tags=["Chat"])
|
router = APIRouter(tags=["Chat"])
|
||||||
chat_manager = ChatManager()
|
chat_manager = ChatManager()
|
||||||
flow_data_store = {}
|
flow_data_store: LRUCache = LRUCache(maxsize=10)
|
||||||
|
|
||||||
|
|
||||||
@router.websocket("/chat/{client_id}")
|
@router.websocket("/chat/{client_id}")
|
||||||
|
|
@ -38,7 +38,8 @@ async def init_build(graph_data: dict):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
flow_id = graph_data.get("id")
|
flow_id = graph_data.get("id")
|
||||||
|
if flow_id is None:
|
||||||
|
raise ValueError("No ID provided")
|
||||||
flow_data_store[flow_id] = graph_data
|
flow_data_store[flow_id] = graph_data
|
||||||
|
|
||||||
return InitResponse(flowId=flow_id)
|
return InitResponse(flowId=flow_id)
|
||||||
|
|
@ -69,26 +70,39 @@ async def stream_build(flow_id: str):
|
||||||
"""Stream the build process based on stored flow data."""
|
"""Stream the build process based on stored flow data."""
|
||||||
|
|
||||||
async def event_stream(flow_id):
|
async def event_stream(flow_id):
|
||||||
final_response = json.dumps({"end_of_stream": True})
|
final_response = {"end_of_stream": True}
|
||||||
try:
|
try:
|
||||||
if flow_id not in flow_data_store:
|
if flow_id not in flow_data_store:
|
||||||
error_message = "Invalid session ID"
|
error_message = "Invalid session ID"
|
||||||
yield f"data: {json.dumps({'error': error_message})}\n\n"
|
yield str(StreamData(event="error", data={"error": error_message}))
|
||||||
return
|
return
|
||||||
|
|
||||||
graph_data = flow_data_store[flow_id].get("data")
|
graph_data = flow_data_store[flow_id].get("data")
|
||||||
|
|
||||||
if not graph_data:
|
if not graph_data:
|
||||||
error_message = "No data provided"
|
error_message = "No data provided"
|
||||||
yield f"data: {json.dumps({'error': error_message})}\n\n"
|
yield str(StreamData(event="error", data={"error": error_message}))
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.debug("Building langchain object")
|
logger.debug("Building langchain object")
|
||||||
graph = Graph.from_payload(graph_data)
|
try:
|
||||||
for node in graph.generator_build():
|
# Some error could happen when building the graph
|
||||||
|
graph = Graph.from_payload(graph_data)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception(exc)
|
||||||
|
error_message = str(exc)
|
||||||
|
yield str(StreamData(event="error", data={"error": error_message}))
|
||||||
|
return
|
||||||
|
|
||||||
|
number_of_nodes = len(graph.nodes)
|
||||||
|
for i, vertex in enumerate(graph.generator_build(), 1):
|
||||||
try:
|
try:
|
||||||
node.build()
|
log_dict = {
|
||||||
params = node._built_object_repr()
|
"log": f"Building node {vertex.vertex_type}",
|
||||||
|
}
|
||||||
|
yield str(StreamData(event="log", data=log_dict))
|
||||||
|
vertex.build()
|
||||||
|
params = vertex._built_object_repr()
|
||||||
valid = True
|
valid = True
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Building node {params[:50]}{'...' if len(params) > 50 else ''}"
|
f"Building node {params[:50]}{'...' if len(params) > 50 else ''}"
|
||||||
|
|
@ -97,21 +111,21 @@ async def stream_build(flow_id: str):
|
||||||
params = str(exc)
|
params = str(exc)
|
||||||
valid = False
|
valid = False
|
||||||
|
|
||||||
response = json.dumps(
|
response = {
|
||||||
{
|
"valid": valid,
|
||||||
"valid": valid,
|
"params": params,
|
||||||
"params": params,
|
"id": vertex.id,
|
||||||
"id": node.id,
|
"progress": round(i / number_of_nodes, 2),
|
||||||
}
|
}
|
||||||
)
|
|
||||||
yield f"data: {response}\n\n"
|
yield str(StreamData(event="message", data=response))
|
||||||
|
|
||||||
chat_manager.set_cache(flow_id, graph.build())
|
chat_manager.set_cache(flow_id, graph.build())
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error("Error while building the flow: %s", exc)
|
logger.error("Error while building the flow: %s", exc)
|
||||||
yield f"error: {json.dumps({'error': str(exc)})}\n\n"
|
yield str(StreamData(event="error", data={"error": str(exc)}))
|
||||||
finally:
|
finally:
|
||||||
yield f"data: {final_response}\n\n"
|
yield str(StreamData(event="message", data=final_response))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return StreamingResponse(event_stream(flow_id), media_type="text/event-stream")
|
return StreamingResponse(event_stream(flow_id), media_type="text/event-stream")
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
|
from typing import Optional
|
||||||
|
from langflow.cache.utils import save_uploaded_file
|
||||||
from langflow.database.models.flow import Flow
|
from langflow.database.models.flow import Flow
|
||||||
from langflow.processing.process import process_graph_cached, process_tweaks
|
from langflow.processing.process import process_graph_cached, process_tweaks
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException, UploadFile
|
||||||
from fastapi.security import HTTPBearer
|
|
||||||
|
|
||||||
from langflow.api.v1.schemas import (
|
from langflow.api.v1.schemas import (
|
||||||
PredictRequest,
|
ProcessResponse,
|
||||||
PredictResponse,
|
UploadFileResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
from langflow.interface.types import build_langchain_types_dict
|
from langflow.interface.types import build_langchain_types_dict
|
||||||
|
|
@ -17,48 +18,41 @@ from sqlmodel import Session
|
||||||
# build router
|
# build router
|
||||||
router = APIRouter(tags=["Base"])
|
router = APIRouter(tags=["Base"])
|
||||||
|
|
||||||
security = HTTPBearer()
|
|
||||||
|
|
||||||
|
|
||||||
def get_flow_from_token(
|
|
||||||
bearer: HTTPBearer = Depends(security), session: Session = Depends(get_session)
|
|
||||||
) -> str:
|
|
||||||
# Extract the token, which is the flow_id in this case
|
|
||||||
flow_id = bearer.credentials
|
|
||||||
# Check if the flow_id exists in the database
|
|
||||||
flow = session.get(Flow, flow_id)
|
|
||||||
if flow is None:
|
|
||||||
raise HTTPException(status_code=401, detail="Invalid token")
|
|
||||||
return flow
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/all")
|
@router.get("/all")
|
||||||
def get_all():
|
def get_all():
|
||||||
return build_langchain_types_dict()
|
return build_langchain_types_dict()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/predict/{flow_id}", response_model=PredictResponse)
|
# For backwards compatibility we will keep the old endpoint
|
||||||
async def predict_flow(
|
@router.post("/predict/{flow_id}", response_model=ProcessResponse)
|
||||||
predict_request: PredictRequest,
|
@router.post("/process/{flow_id}", response_model=ProcessResponse)
|
||||||
|
async def process_flow(
|
||||||
flow_id: str,
|
flow_id: str,
|
||||||
|
inputs: Optional[dict] = None,
|
||||||
|
tweaks: Optional[dict] = None,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Endpoint to process a message using the flow passed in the bearer token.
|
Endpoint to process an input with a given flow_id.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
flow = session.get(Flow, flow_id)
|
flow = session.get(Flow, flow_id)
|
||||||
if flow is None:
|
if flow is None:
|
||||||
raise ValueError(f"Flow {flow_id} not found")
|
raise ValueError(f"Flow {flow_id} not found")
|
||||||
graph_data = flow.data
|
|
||||||
if predict_request.tweaks:
|
|
||||||
graph_data = process_tweaks(graph_data, predict_request.tweaks)
|
|
||||||
|
|
||||||
response = process_graph_cached(graph_data, predict_request.message)
|
if flow.data is None:
|
||||||
return PredictResponse(
|
raise ValueError(f"Flow {flow_id} has no data")
|
||||||
result=response.get("result", ""),
|
graph_data = flow.data
|
||||||
intermediate_steps=response.get("thought", ""),
|
if tweaks:
|
||||||
|
try:
|
||||||
|
graph_data = process_tweaks(graph_data, tweaks)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(f"Error processing tweaks: {exc}")
|
||||||
|
response = process_graph_cached(graph_data, inputs)
|
||||||
|
return ProcessResponse(
|
||||||
|
result=response,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Log stack trace
|
# Log stack trace
|
||||||
|
|
@ -66,6 +60,21 @@ async def predict_flow(
|
||||||
raise HTTPException(status_code=500, detail=str(e)) from e
|
raise HTTPException(status_code=500, detail=str(e)) from e
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/upload/{flow_id}", response_model=UploadFileResponse, status_code=201)
|
||||||
|
async def create_upload_file(file: UploadFile, flow_id: str):
|
||||||
|
# Cache file
|
||||||
|
try:
|
||||||
|
file_path = save_uploaded_file(file.file, folder_name=flow_id)
|
||||||
|
|
||||||
|
return UploadFileResponse(
|
||||||
|
flowId=flow_id,
|
||||||
|
file_path=file_path,
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(f"Error saving file: {exc}")
|
||||||
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
# get endpoint to return version of langflow
|
# get endpoint to return version of langflow
|
||||||
@router.get("/version")
|
@router.get("/version")
|
||||||
def get_version():
|
def get_version():
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
from typing import List
|
from typing import List
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
from langflow import settings
|
from langflow.settings import settings
|
||||||
from langflow.api.utils import remove_api_keys
|
from langflow.api.utils import remove_api_keys
|
||||||
from langflow.api.v1.schemas import FlowListCreate, FlowListRead
|
from langflow.api.v1.schemas import FlowListCreate, FlowListRead
|
||||||
from langflow.database.models.flow import (
|
from langflow.database.models.flow import (
|
||||||
|
|
@ -61,7 +61,7 @@ def update_flow(
|
||||||
if not db_flow:
|
if not db_flow:
|
||||||
raise HTTPException(status_code=404, detail="Flow not found")
|
raise HTTPException(status_code=404, detail="Flow not found")
|
||||||
flow_data = flow.dict(exclude_unset=True)
|
flow_data = flow.dict(exclude_unset=True)
|
||||||
if settings.save_api_keys:
|
if settings.remove_api_keys:
|
||||||
flow_data = remove_api_keys(flow_data)
|
flow_data = remove_api_keys(flow_data)
|
||||||
for key, value in flow_data.items():
|
for key, value in flow_data.items():
|
||||||
setattr(db_flow, key, value)
|
setattr(db_flow, key, value)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Optional, Union
|
from typing import Any, Dict, List, Optional, Union
|
||||||
from langflow.database.models.flow import FlowCreate, FlowRead
|
from langflow.database.models.flow import FlowCreate, FlowRead
|
||||||
from pydantic import BaseModel, Field, validator
|
from pydantic import BaseModel, Field, validator
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
class GraphData(BaseModel):
|
class GraphData(BaseModel):
|
||||||
|
|
@ -19,34 +21,22 @@ class ExportedFlow(BaseModel):
|
||||||
data: GraphData
|
data: GraphData
|
||||||
|
|
||||||
|
|
||||||
class PredictRequest(BaseModel):
|
class InputRequest(BaseModel):
|
||||||
"""Predict request schema."""
|
input: dict
|
||||||
|
|
||||||
message: str
|
|
||||||
|
class TweaksRequest(BaseModel):
|
||||||
tweaks: Optional[Dict[str, Dict[str, str]]] = Field(default_factory=dict)
|
tweaks: Optional[Dict[str, Dict[str, str]]] = Field(default_factory=dict)
|
||||||
|
|
||||||
class Config:
|
|
||||||
schema_extra = {
|
class UpdateTemplateRequest(BaseModel):
|
||||||
"example": {
|
template: dict
|
||||||
"message": "Hello, how are you?",
|
|
||||||
"tweaks": {
|
|
||||||
"dndnode_986363f0-4677-4035-9f38-74b94af5dd78": {
|
|
||||||
"name": "A tool name",
|
|
||||||
"description": "A tool description",
|
|
||||||
},
|
|
||||||
"dndnode_986363f0-4677-4035-9f38-74b94af57378": {
|
|
||||||
"template": "A {template}",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PredictResponse(BaseModel):
|
class ProcessResponse(BaseModel):
|
||||||
"""Predict response schema."""
|
"""Process response schema."""
|
||||||
|
|
||||||
result: str
|
result: dict
|
||||||
intermediate_steps: str = ""
|
|
||||||
|
|
||||||
|
|
||||||
class ChatMessage(BaseModel):
|
class ChatMessage(BaseModel):
|
||||||
|
|
@ -101,3 +91,18 @@ class InitResponse(BaseModel):
|
||||||
|
|
||||||
class BuiltResponse(BaseModel):
|
class BuiltResponse(BaseModel):
|
||||||
built: bool
|
built: bool
|
||||||
|
|
||||||
|
|
||||||
|
class UploadFileResponse(BaseModel):
|
||||||
|
"""Upload file response schema."""
|
||||||
|
|
||||||
|
flowId: str
|
||||||
|
file_path: Path
|
||||||
|
|
||||||
|
|
||||||
|
class StreamData(BaseModel):
|
||||||
|
event: str
|
||||||
|
data: dict
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"event: {self.event}\ndata: {json.dumps(self.data)}\n\n"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import json
|
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
|
|
||||||
from langflow.api.v1.base import (
|
from langflow.api.v1.base import (
|
||||||
|
|
@ -9,8 +7,6 @@ from langflow.api.v1.base import (
|
||||||
PromptValidationResponse,
|
PromptValidationResponse,
|
||||||
validate_prompt,
|
validate_prompt,
|
||||||
)
|
)
|
||||||
from langflow.graph.vertex.types import VectorStoreVertex
|
|
||||||
from langflow.graph import Graph
|
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.validate import validate_code
|
from langflow.utils.validate import validate_code
|
||||||
|
|
||||||
|
|
@ -37,21 +33,3 @@ def post_validate_prompt(prompt: Prompt):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
raise HTTPException(status_code=500, detail=str(e)) from e
|
raise HTTPException(status_code=500, detail=str(e)) from e
|
||||||
|
|
||||||
|
|
||||||
# validate node
|
|
||||||
@router.post("/node/{node_id}", status_code=200)
|
|
||||||
def post_validate_node(node_id: str, data: dict):
|
|
||||||
try:
|
|
||||||
# build graph
|
|
||||||
graph = Graph.from_payload(data)
|
|
||||||
# validate node
|
|
||||||
node = graph.get_node(node_id)
|
|
||||||
if node is None:
|
|
||||||
raise ValueError(f"Node {node_id} not found")
|
|
||||||
if not isinstance(node, VectorStoreVertex):
|
|
||||||
node.build()
|
|
||||||
return json.dumps({"valid": True, "params": str(node._built_object_repr())})
|
|
||||||
except Exception as e:
|
|
||||||
logger.exception(e)
|
|
||||||
return json.dumps({"valid": False, "params": str(e)})
|
|
||||||
|
|
|
||||||
11
src/backend/langflow/cache/base.py
vendored
11
src/backend/langflow/cache/base.py
vendored
|
|
@ -17,7 +17,6 @@ class BaseCache(abc.ABC):
|
||||||
Returns:
|
Returns:
|
||||||
The value associated with the key, or None if the key is not found.
|
The value associated with the key, or None if the key is not found.
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def set(self, key, value):
|
def set(self, key, value):
|
||||||
|
|
@ -28,7 +27,6 @@ class BaseCache(abc.ABC):
|
||||||
key: The key of the item.
|
key: The key of the item.
|
||||||
value: The value to cache.
|
value: The value to cache.
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def delete(self, key):
|
def delete(self, key):
|
||||||
|
|
@ -38,14 +36,12 @@ class BaseCache(abc.ABC):
|
||||||
Args:
|
Args:
|
||||||
key: The key of the item to remove.
|
key: The key of the item to remove.
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def clear(self):
|
def clear(self):
|
||||||
"""
|
"""
|
||||||
Clear all items from the cache.
|
Clear all items from the cache.
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def __contains__(self, key):
|
def __contains__(self, key):
|
||||||
|
|
@ -58,7 +54,6 @@ class BaseCache(abc.ABC):
|
||||||
Returns:
|
Returns:
|
||||||
True if the key is in the cache, False otherwise.
|
True if the key is in the cache, False otherwise.
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
|
|
@ -67,11 +62,7 @@ class BaseCache(abc.ABC):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
key: The key of the item to retrieve.
|
key: The key of the item to retrieve.
|
||||||
|
|
||||||
Returns:
|
|
||||||
The value associated with the key, or None if the key is not found.
|
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
|
|
@ -82,7 +73,6 @@ class BaseCache(abc.ABC):
|
||||||
key: The key of the item.
|
key: The key of the item.
|
||||||
value: The value to cache.
|
value: The value to cache.
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key):
|
||||||
|
|
@ -92,4 +82,3 @@ class BaseCache(abc.ABC):
|
||||||
Args:
|
Args:
|
||||||
key: The key of the item to remove.
|
key: The key of the item to remove.
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
|
||||||
59
src/backend/langflow/cache/utils.py
vendored
59
src/backend/langflow/cache/utils.py
vendored
|
|
@ -8,16 +8,17 @@ import tempfile
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
from appdirs import user_cache_dir
|
||||||
import dill # type: ignore
|
|
||||||
|
|
||||||
CACHE: Dict[str, Any] = {}
|
CACHE: Dict[str, Any] = {}
|
||||||
|
|
||||||
|
CACHE_DIR = user_cache_dir("langflow", "langflow")
|
||||||
|
|
||||||
|
|
||||||
def create_cache_folder(func):
|
def create_cache_folder(func):
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
# Get the destination folder
|
# Get the destination folder
|
||||||
cache_path = Path(tempfile.gettempdir()) / PREFIX
|
cache_path = Path(CACHE_DIR) / PREFIX
|
||||||
|
|
||||||
# Create the destination folder if it doesn't exist
|
# Create the destination folder if it doesn't exist
|
||||||
os.makedirs(cache_path, exist_ok=True)
|
os.makedirs(cache_path, exist_ok=True)
|
||||||
|
|
@ -119,7 +120,7 @@ def save_binary_file(content: str, file_name: str, accepted_types: list[str]) ->
|
||||||
raise ValueError(f"File {file_name} is not accepted")
|
raise ValueError(f"File {file_name} is not accepted")
|
||||||
|
|
||||||
# Get the destination folder
|
# Get the destination folder
|
||||||
cache_path = Path(tempfile.gettempdir()) / PREFIX
|
cache_path = Path(CACHE_DIR) / PREFIX
|
||||||
if not content:
|
if not content:
|
||||||
raise ValueError("Please, reload the file in the loader.")
|
raise ValueError("Please, reload the file in the loader.")
|
||||||
data = content.split(",")[1]
|
data = content.split(",")[1]
|
||||||
|
|
@ -136,19 +137,43 @@ def save_binary_file(content: str, file_name: str, accepted_types: list[str]) ->
|
||||||
|
|
||||||
|
|
||||||
@create_cache_folder
|
@create_cache_folder
|
||||||
def save_cache(hash_val: str, chat_data, clean_old_cache_files: bool):
|
def save_uploaded_file(file, folder_name):
|
||||||
cache_path = Path(tempfile.gettempdir()) / PREFIX / f"{hash_val}.dill"
|
"""
|
||||||
with cache_path.open("wb") as cache_file:
|
Save an uploaded file to the specified folder with a hash of its content as the file name.
|
||||||
dill.dump(chat_data, cache_file)
|
|
||||||
|
|
||||||
if clean_old_cache_files:
|
Args:
|
||||||
clear_old_cache_files()
|
file: The uploaded file object.
|
||||||
|
folder_name: The name of the folder to save the file in.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The path to the saved file.
|
||||||
|
"""
|
||||||
|
cache_path = Path(CACHE_DIR)
|
||||||
|
folder_path = cache_path / folder_name
|
||||||
|
|
||||||
@create_cache_folder
|
# Create the folder if it doesn't exist
|
||||||
def load_cache(hash_val):
|
if not folder_path.exists():
|
||||||
cache_path = Path(tempfile.gettempdir()) / PREFIX / f"{hash_val}.dill"
|
folder_path.mkdir()
|
||||||
if cache_path.exists():
|
|
||||||
with cache_path.open("rb") as cache_file:
|
# Create a hash of the file content
|
||||||
return dill.load(cache_file)
|
sha256_hash = hashlib.sha256()
|
||||||
return None
|
# Reset the file cursor to the beginning of the file
|
||||||
|
file.seek(0)
|
||||||
|
# Iterate over the uploaded file in small chunks to conserve memory
|
||||||
|
while chunk := file.read(8192): # Read 8KB at a time (adjust as needed)
|
||||||
|
sha256_hash.update(chunk)
|
||||||
|
|
||||||
|
# Use the hex digest of the hash as the file name
|
||||||
|
hex_dig = sha256_hash.hexdigest()
|
||||||
|
file_name = hex_dig
|
||||||
|
|
||||||
|
# Reset the file cursor to the beginning of the file
|
||||||
|
file.seek(0)
|
||||||
|
|
||||||
|
# Save the file with the hash as its name
|
||||||
|
file_path = folder_path / file_name
|
||||||
|
with open(file_path, "wb") as new_file:
|
||||||
|
while chunk := file.read(8192):
|
||||||
|
new_file.write(chunk)
|
||||||
|
|
||||||
|
return file_path
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,8 @@ class ChatManager:
|
||||||
if isinstance(msg, FileResponse):
|
if isinstance(msg, FileResponse):
|
||||||
if msg.data_type == "image":
|
if msg.data_type == "image":
|
||||||
# Base64 encode the image
|
# Base64 encode the image
|
||||||
|
if isinstance(msg.data, str):
|
||||||
|
continue
|
||||||
msg.data = pil_to_base64(msg.data)
|
msg.data = pil_to_base64(msg.data)
|
||||||
file_responses.append(msg)
|
file_responses.append(msg)
|
||||||
if msg.type == "start":
|
if msg.type == "start":
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ agents:
|
||||||
- ZeroShotAgent
|
- ZeroShotAgent
|
||||||
- JsonAgent
|
- JsonAgent
|
||||||
- CSVAgent
|
- CSVAgent
|
||||||
- initialize_agent
|
- AgentInitializer
|
||||||
- VectorStoreAgent
|
- VectorStoreAgent
|
||||||
- VectorStoreRouterAgent
|
- VectorStoreRouterAgent
|
||||||
- SQLAgent
|
- SQLAgent
|
||||||
|
|
@ -48,6 +48,8 @@ documentloaders:
|
||||||
- ReadTheDocsLoader
|
- ReadTheDocsLoader
|
||||||
- SlackDirectoryLoader
|
- SlackDirectoryLoader
|
||||||
- NotionDirectoryLoader
|
- NotionDirectoryLoader
|
||||||
|
- DirectoryLoader
|
||||||
|
- GitLoader
|
||||||
embeddings:
|
embeddings:
|
||||||
- OpenAIEmbeddings
|
- OpenAIEmbeddings
|
||||||
- HuggingFaceEmbeddings
|
- HuggingFaceEmbeddings
|
||||||
|
|
@ -129,6 +131,9 @@ vectorstores:
|
||||||
- Qdrant
|
- Qdrant
|
||||||
- Weaviate
|
- Weaviate
|
||||||
- FAISS
|
- FAISS
|
||||||
|
- Pinecone
|
||||||
|
- SupabaseVectorStore
|
||||||
|
- MongoDBAtlasVectorSearch
|
||||||
wrappers:
|
wrappers:
|
||||||
- RequestsWrapper
|
- RequestsWrapper
|
||||||
# - ChatPromptTemplate
|
# - ChatPromptTemplate
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ CUSTOM_NODES = {
|
||||||
"agents": {
|
"agents": {
|
||||||
"JsonAgent": frontend_node.agents.JsonAgentNode(),
|
"JsonAgent": frontend_node.agents.JsonAgentNode(),
|
||||||
"CSVAgent": frontend_node.agents.CSVAgentNode(),
|
"CSVAgent": frontend_node.agents.CSVAgentNode(),
|
||||||
"initialize_agent": frontend_node.agents.InitializeAgentNode(),
|
"AgentInitializer": frontend_node.agents.InitializeAgentNode(),
|
||||||
"VectorStoreAgent": frontend_node.agents.VectorStoreAgentNode(),
|
"VectorStoreAgent": frontend_node.agents.VectorStoreAgentNode(),
|
||||||
"VectorStoreRouterAgent": frontend_node.agents.VectorStoreRouterAgentNode(),
|
"VectorStoreRouterAgent": frontend_node.agents.VectorStoreRouterAgentNode(),
|
||||||
"SQLAgent": frontend_node.agents.SQLAgentNode(),
|
"SQLAgent": frontend_node.agents.SQLAgentNode(),
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ from langflow.graph.vertex.types import (
|
||||||
from langflow.interface.tools.constants import FILE_TOOLS
|
from langflow.interface.tools.constants import FILE_TOOLS
|
||||||
from langflow.utils import payload
|
from langflow.utils import payload
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
|
from langchain.chains.base import Chain
|
||||||
|
|
||||||
|
|
||||||
class Graph:
|
class Graph:
|
||||||
|
|
@ -99,7 +100,7 @@ class Graph:
|
||||||
]
|
]
|
||||||
return connected_nodes
|
return connected_nodes
|
||||||
|
|
||||||
def build(self) -> List[Vertex]:
|
def build(self) -> Chain:
|
||||||
"""Builds the graph."""
|
"""Builds the graph."""
|
||||||
# Get root node
|
# Get root node
|
||||||
root_node = payload.get_root_node(self)
|
root_node = payload.get_root_node(self)
|
||||||
|
|
@ -145,7 +146,7 @@ class Graph:
|
||||||
def generator_build(self) -> Generator:
|
def generator_build(self) -> Generator:
|
||||||
"""Builds each vertex in the graph and yields it."""
|
"""Builds each vertex in the graph and yields it."""
|
||||||
sorted_vertices = self.topological_sort()
|
sorted_vertices = self.topological_sort()
|
||||||
logger.info("Sorted vertices: %s", sorted_vertices)
|
logger.debug("Sorted vertices: %s", sorted_vertices)
|
||||||
yield from sorted_vertices
|
yield from sorted_vertices
|
||||||
|
|
||||||
def get_node_neighbors(self, node: Vertex) -> Dict[Vertex, int]:
|
def get_node_neighbors(self, node: Vertex) -> Dict[Vertex, int]:
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,6 @@ from langflow.interface.wrappers.base import wrapper_creator
|
||||||
from typing import Dict, Type
|
from typing import Dict, Type
|
||||||
|
|
||||||
|
|
||||||
DIRECT_TYPES = ["str", "bool", "code", "int", "float", "Any", "prompt"]
|
|
||||||
|
|
||||||
|
|
||||||
VERTEX_TYPE_MAP: Dict[str, Type[Vertex]] = {
|
VERTEX_TYPE_MAP: Dict[str, Type[Vertex]] = {
|
||||||
**{t: PromptVertex for t in prompt_creator.to_list()},
|
**{t: PromptVertex for t in prompt_creator.to_list()},
|
||||||
**{t: AgentVertex for t in agent_creator.to_list()},
|
**{t: AgentVertex for t in agent_creator.to_list()},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
from langflow.cache import utils as cache_utils
|
from langflow.utils.constants import DIRECT_TYPES
|
||||||
from langflow.graph.vertex.constants import DIRECT_TYPES
|
from langflow.interface.initialize import loading
|
||||||
from langflow.interface import loading
|
|
||||||
from langflow.interface.listing import ALL_TYPES_DICT
|
from langflow.interface.listing import ALL_TYPES_DICT
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.utils.util import sync_to_async
|
from langflow.utils.util import sync_to_async
|
||||||
|
|
@ -90,12 +89,7 @@ class Vertex:
|
||||||
# Load the type in value.get('suffixes') using
|
# Load the type in value.get('suffixes') using
|
||||||
# what is inside value.get('content')
|
# what is inside value.get('content')
|
||||||
# value.get('value') is the file name
|
# value.get('value') is the file name
|
||||||
file_name = value.get("value")
|
file_path = value.get("file_path")
|
||||||
content = value.get("content")
|
|
||||||
type_to_load = value.get("suffixes")
|
|
||||||
file_path = cache_utils.save_binary_file(
|
|
||||||
content=content, file_name=file_name, accepted_types=type_to_load
|
|
||||||
)
|
|
||||||
|
|
||||||
params[key] = file_path
|
params[key] = file_path
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
DIRECT_TYPES = ["str", "bool", "code", "int", "float", "Any", "prompt"]
|
|
||||||
|
|
|
||||||
|
|
@ -106,9 +106,6 @@ class VectorStoreVertex(Vertex):
|
||||||
def __init__(self, data: Dict):
|
def __init__(self, data: Dict):
|
||||||
super().__init__(data, base_type="vectorstores")
|
super().__init__(data, base_type="vectorstores")
|
||||||
|
|
||||||
def _built_object_repr(self):
|
|
||||||
return "Vector stores can take time to build. It will build on the first query."
|
|
||||||
|
|
||||||
|
|
||||||
class MemoryVertex(Vertex):
|
class MemoryVertex(Vertex):
|
||||||
def __init__(self, data: Dict):
|
def __init__(self, data: Dict):
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ from langchain.agents import (
|
||||||
Tool,
|
Tool,
|
||||||
ZeroShotAgent,
|
ZeroShotAgent,
|
||||||
initialize_agent,
|
initialize_agent,
|
||||||
|
AgentType,
|
||||||
)
|
)
|
||||||
from langchain.agents.agent_toolkits import (
|
from langchain.agents.agent_toolkits import (
|
||||||
SQLDatabaseToolkit,
|
SQLDatabaseToolkit,
|
||||||
|
|
@ -64,7 +65,9 @@ class JsonAgent(CustomAgentExecutor):
|
||||||
llm=llm,
|
llm=llm,
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
)
|
)
|
||||||
agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names) # type: ignore
|
agent = ZeroShotAgent(
|
||||||
|
llm_chain=llm_chain, allowed_tools=tool_names # type: ignore
|
||||||
|
)
|
||||||
return cls.from_agent_and_tools(agent=agent, tools=tools, verbose=True)
|
return cls.from_agent_and_tools(agent=agent, tools=tools, verbose=True)
|
||||||
|
|
||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
|
|
@ -111,7 +114,9 @@ class CSVAgent(CustomAgentExecutor):
|
||||||
prompt=partial_prompt,
|
prompt=partial_prompt,
|
||||||
)
|
)
|
||||||
tool_names = {tool.name for tool in tools}
|
tool_names = {tool.name for tool in tools}
|
||||||
agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names, **kwargs) # type: ignore
|
agent = ZeroShotAgent(
|
||||||
|
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore
|
||||||
|
)
|
||||||
|
|
||||||
return cls.from_agent_and_tools(agent=agent, tools=tools, verbose=True)
|
return cls.from_agent_and_tools(agent=agent, tools=tools, verbose=True)
|
||||||
|
|
||||||
|
|
@ -148,7 +153,9 @@ class VectorStoreAgent(CustomAgentExecutor):
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
)
|
)
|
||||||
tool_names = {tool.name for tool in tools}
|
tool_names = {tool.name for tool in tools}
|
||||||
agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names, **kwargs) # type: ignore
|
agent = ZeroShotAgent(
|
||||||
|
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore
|
||||||
|
)
|
||||||
return AgentExecutor.from_agent_and_tools(
|
return AgentExecutor.from_agent_and_tools(
|
||||||
agent=agent, tools=tools, verbose=True
|
agent=agent, tools=tools, verbose=True
|
||||||
)
|
)
|
||||||
|
|
@ -186,7 +193,7 @@ class SQLAgent(CustomAgentExecutor):
|
||||||
from langchain.tools.sql_database.tool import (
|
from langchain.tools.sql_database.tool import (
|
||||||
InfoSQLDatabaseTool,
|
InfoSQLDatabaseTool,
|
||||||
ListSQLDatabaseTool,
|
ListSQLDatabaseTool,
|
||||||
QueryCheckerTool,
|
QuerySQLCheckerTool,
|
||||||
QuerySQLDataBaseTool,
|
QuerySQLDataBaseTool,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -201,7 +208,7 @@ class SQLAgent(CustomAgentExecutor):
|
||||||
QuerySQLDataBaseTool(db=db), # type: ignore
|
QuerySQLDataBaseTool(db=db), # type: ignore
|
||||||
InfoSQLDatabaseTool(db=db), # type: ignore
|
InfoSQLDatabaseTool(db=db), # type: ignore
|
||||||
ListSQLDatabaseTool(db=db), # type: ignore
|
ListSQLDatabaseTool(db=db), # type: ignore
|
||||||
QueryCheckerTool(db=db, llm_chain=llmchain, llm=llm), # type: ignore
|
QuerySQLCheckerTool(db=db, llm_chain=llmchain, llm=llm), # type: ignore
|
||||||
]
|
]
|
||||||
|
|
||||||
prefix = SQL_PREFIX.format(dialect=toolkit.dialect, top_k=10)
|
prefix = SQL_PREFIX.format(dialect=toolkit.dialect, top_k=10)
|
||||||
|
|
@ -216,7 +223,9 @@ class SQLAgent(CustomAgentExecutor):
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
)
|
)
|
||||||
tool_names = {tool.name for tool in tools} # type: ignore
|
tool_names = {tool.name for tool in tools} # type: ignore
|
||||||
agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names, **kwargs) # type: ignore
|
agent = ZeroShotAgent(
|
||||||
|
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore
|
||||||
|
)
|
||||||
return AgentExecutor.from_agent_and_tools(
|
return AgentExecutor.from_agent_and_tools(
|
||||||
agent=agent,
|
agent=agent,
|
||||||
tools=tools, # type: ignore
|
tools=tools, # type: ignore
|
||||||
|
|
@ -252,14 +261,20 @@ class VectorStoreRouterAgent(CustomAgentExecutor):
|
||||||
):
|
):
|
||||||
"""Construct a vector store router agent from an LLM and tools."""
|
"""Construct a vector store router agent from an LLM and tools."""
|
||||||
|
|
||||||
tools = vectorstoreroutertoolkit.get_tools()
|
tools = (
|
||||||
|
vectorstoreroutertoolkit
|
||||||
|
if isinstance(vectorstoreroutertoolkit, list)
|
||||||
|
else vectorstoreroutertoolkit.get_tools()
|
||||||
|
)
|
||||||
prompt = ZeroShotAgent.create_prompt(tools, prefix=VECTORSTORE_ROUTER_PREFIX)
|
prompt = ZeroShotAgent.create_prompt(tools, prefix=VECTORSTORE_ROUTER_PREFIX)
|
||||||
llm_chain = LLMChain(
|
llm_chain = LLMChain(
|
||||||
llm=llm,
|
llm=llm,
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
)
|
)
|
||||||
tool_names = {tool.name for tool in tools}
|
tool_names = {tool.name for tool in tools}
|
||||||
agent = ZeroShotAgent(llm_chain=llm_chain, allowed_tools=tool_names, **kwargs) # type: ignore
|
agent = ZeroShotAgent(
|
||||||
|
llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore
|
||||||
|
)
|
||||||
return AgentExecutor.from_agent_and_tools(
|
return AgentExecutor.from_agent_and_tools(
|
||||||
agent=agent, tools=tools, verbose=True
|
agent=agent, tools=tools, verbose=True
|
||||||
)
|
)
|
||||||
|
|
@ -269,11 +284,11 @@ class VectorStoreRouterAgent(CustomAgentExecutor):
|
||||||
|
|
||||||
|
|
||||||
class InitializeAgent(CustomAgentExecutor):
|
class InitializeAgent(CustomAgentExecutor):
|
||||||
"""Implementation of initialize_agent function"""
|
"""Implementation of AgentInitializer function"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def function_name():
|
def function_name():
|
||||||
return "initialize_agent"
|
return "AgentInitializer"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def initialize(
|
def initialize(
|
||||||
|
|
@ -283,6 +298,9 @@ class InitializeAgent(CustomAgentExecutor):
|
||||||
agent: str,
|
agent: str,
|
||||||
memory: Optional[BaseChatMemory] = None,
|
memory: Optional[BaseChatMemory] = None,
|
||||||
):
|
):
|
||||||
|
# Find which value in the AgentType enum corresponds to the string
|
||||||
|
# passed in as agent
|
||||||
|
agent = AgentType(agent)
|
||||||
return initialize_agent(
|
return initialize_agent(
|
||||||
tools=tools,
|
tools=tools,
|
||||||
llm=llm,
|
llm=llm,
|
||||||
|
|
@ -302,7 +320,7 @@ class InitializeAgent(CustomAgentExecutor):
|
||||||
CUSTOM_AGENTS = {
|
CUSTOM_AGENTS = {
|
||||||
"JsonAgent": JsonAgent,
|
"JsonAgent": JsonAgent,
|
||||||
"CSVAgent": CSVAgent,
|
"CSVAgent": CSVAgent,
|
||||||
"initialize_agent": InitializeAgent,
|
"AgentInitializer": InitializeAgent,
|
||||||
"VectorStoreAgent": VectorStoreAgent,
|
"VectorStoreAgent": VectorStoreAgent,
|
||||||
"VectorStoreRouterAgent": VectorStoreRouterAgent,
|
"VectorStoreRouterAgent": VectorStoreRouterAgent,
|
||||||
"SQLAgent": SQLAgent,
|
"SQLAgent": SQLAgent,
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ class LangChainTypeCreator(BaseModel, ABC):
|
||||||
value=value.get("value", None),
|
value=value.get("value", None),
|
||||||
suffixes=value.get("suffixes", []),
|
suffixes=value.get("suffixes", []),
|
||||||
file_types=value.get("fileTypes", []),
|
file_types=value.get("fileTypes", []),
|
||||||
content=value.get("content", None),
|
file_path=value.get("file_path", None),
|
||||||
)
|
)
|
||||||
for key, value in signature["template"].items()
|
for key, value in signature["template"].items()
|
||||||
if key != "_type"
|
if key != "_type"
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,10 @@ class ChainCreator(LangChainTypeCreator):
|
||||||
return ChainFrontendNode
|
return ChainFrontendNode
|
||||||
|
|
||||||
#! We need to find a better solution for this
|
#! We need to find a better solution for this
|
||||||
from_method_nodes = {"ConversationalRetrievalChain": "from_llm"}
|
from_method_nodes = {
|
||||||
|
"ConversationalRetrievalChain": "from_llm",
|
||||||
|
"LLMCheckerChain": "from_llm",
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type_to_loader_dict(self) -> Dict:
|
def type_to_loader_dict(self) -> Dict:
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ class TimeTravelGuideChain(BaseCustomConversationChain):
|
||||||
|
|
||||||
|
|
||||||
class CombineDocsChain(CustomChain):
|
class CombineDocsChain(CustomChain):
|
||||||
"""Implementation of initialize_agent function"""
|
"""Implementation of load_qa_chain function"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def function_name():
|
def function_name():
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,14 @@ from langflow.interface.importing.utils import import_class
|
||||||
from langflow.interface.agents.custom import CUSTOM_AGENTS
|
from langflow.interface.agents.custom import CUSTOM_AGENTS
|
||||||
from langflow.interface.chains.custom import CUSTOM_CHAINS
|
from langflow.interface.chains.custom import CUSTOM_CHAINS
|
||||||
|
|
||||||
## LLMs
|
# LLMs
|
||||||
llm_type_to_cls_dict = llms.type_to_cls_dict
|
llm_type_to_cls_dict = llms.type_to_cls_dict
|
||||||
llm_type_to_cls_dict["anthropic-chat"] = ChatAnthropic # type: ignore
|
llm_type_to_cls_dict["anthropic-chat"] = ChatAnthropic # type: ignore
|
||||||
llm_type_to_cls_dict["azure-chat"] = AzureChatOpenAI # type: ignore
|
llm_type_to_cls_dict["azure-chat"] = AzureChatOpenAI # type: ignore
|
||||||
llm_type_to_cls_dict["openai-chat"] = ChatOpenAI # type: ignore
|
llm_type_to_cls_dict["openai-chat"] = ChatOpenAI # type: ignore
|
||||||
|
|
||||||
|
|
||||||
## Toolkits
|
# Toolkits
|
||||||
toolkit_type_to_loader_dict: dict[str, Any] = {
|
toolkit_type_to_loader_dict: dict[str, Any] = {
|
||||||
toolkit_name: import_class(f"langchain.agents.agent_toolkits.{toolkit_name}")
|
toolkit_name: import_class(f"langchain.agents.agent_toolkits.{toolkit_name}")
|
||||||
# if toolkit_name is lower case it is a loader
|
# if toolkit_name is lower case it is a loader
|
||||||
|
|
@ -39,25 +39,25 @@ toolkit_type_to_cls_dict: dict[str, Any] = {
|
||||||
if not toolkit_name.islower()
|
if not toolkit_name.islower()
|
||||||
}
|
}
|
||||||
|
|
||||||
## Memories
|
# Memories
|
||||||
memory_type_to_cls_dict: dict[str, Any] = {
|
memory_type_to_cls_dict: dict[str, Any] = {
|
||||||
memory_name: import_class(f"langchain.memory.{memory_name}")
|
memory_name: import_class(f"langchain.memory.{memory_name}")
|
||||||
for memory_name in memory.__all__
|
for memory_name in memory.__all__
|
||||||
}
|
}
|
||||||
|
|
||||||
## Wrappers
|
# Wrappers
|
||||||
wrapper_type_to_cls_dict: dict[str, Any] = {
|
wrapper_type_to_cls_dict: dict[str, Any] = {
|
||||||
wrapper.__name__: wrapper for wrapper in [requests.RequestsWrapper]
|
wrapper.__name__: wrapper for wrapper in [requests.RequestsWrapper]
|
||||||
}
|
}
|
||||||
|
|
||||||
## Embeddings
|
# Embeddings
|
||||||
embedding_type_to_cls_dict: dict[str, Any] = {
|
embedding_type_to_cls_dict: dict[str, Any] = {
|
||||||
embedding_name: import_class(f"langchain.embeddings.{embedding_name}")
|
embedding_name: import_class(f"langchain.embeddings.{embedding_name}")
|
||||||
for embedding_name in embeddings.__all__
|
for embedding_name in embeddings.__all__
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
## Document Loaders
|
# Document Loaders
|
||||||
documentloaders_type_to_cls_dict: dict[str, Any] = {
|
documentloaders_type_to_cls_dict: dict[str, Any] = {
|
||||||
documentloader_name: import_class(
|
documentloader_name: import_class(
|
||||||
f"langchain.document_loaders.{documentloader_name}"
|
f"langchain.document_loaders.{documentloader_name}"
|
||||||
|
|
@ -65,7 +65,7 @@ documentloaders_type_to_cls_dict: dict[str, Any] = {
|
||||||
for documentloader_name in document_loaders.__all__
|
for documentloader_name in document_loaders.__all__
|
||||||
}
|
}
|
||||||
|
|
||||||
## Text Splitters
|
# Text Splitters
|
||||||
textsplitter_type_to_cls_dict: dict[str, Any] = dict(
|
textsplitter_type_to_cls_dict: dict[str, Any] = dict(
|
||||||
inspect.getmembers(text_splitter, inspect.isclass)
|
inspect.getmembers(text_splitter, inspect.isclass)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
0
src/backend/langflow/interface/initialize/__init__.py
Normal file
0
src/backend/langflow/interface/initialize/__init__.py
Normal file
|
|
@ -1,31 +1,21 @@
|
||||||
import json
|
import json
|
||||||
from typing import Any, Callable, Dict, Optional
|
from typing import Any, Callable, Dict, Sequence
|
||||||
|
|
||||||
from langchain.agents import ZeroShotAgent
|
from langchain.agents import ZeroShotAgent
|
||||||
from langchain.agents import agent as agent_module
|
from langchain.agents import agent as agent_module
|
||||||
from langchain.agents.agent import AgentExecutor
|
from langchain.agents.agent import AgentExecutor
|
||||||
from langchain.agents.agent_toolkits.base import BaseToolkit
|
from langchain.agents.agent_toolkits.base import BaseToolkit
|
||||||
from langchain.agents.load_tools import (
|
from langchain.agents.tools import BaseTool
|
||||||
_BASE_TOOLS,
|
from langflow.interface.initialize.vector_store import vecstore_initializer
|
||||||
_EXTRA_LLM_TOOLS,
|
|
||||||
_EXTRA_OPTIONAL_TOOLS,
|
|
||||||
_LLM_TOOLS,
|
|
||||||
)
|
|
||||||
from langchain.agents.loading import load_agent_from_config
|
|
||||||
from langchain.agents.tools import Tool
|
|
||||||
from langchain.base_language import BaseLanguageModel
|
|
||||||
from langchain.callbacks.base import BaseCallbackManager
|
|
||||||
from langchain.chains.loading import load_chain_from_config
|
|
||||||
from langchain.llms.loading import load_llm_from_config
|
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
|
|
||||||
from langflow.interface.custom_lists import CUSTOM_NODES
|
from langflow.interface.custom_lists import CUSTOM_NODES
|
||||||
from langflow.interface.importing.utils import get_function, import_by_type
|
from langflow.interface.importing.utils import get_function, import_by_type
|
||||||
from langflow.interface.toolkits.base import toolkits_creator
|
from langflow.interface.toolkits.base import toolkits_creator
|
||||||
from langflow.interface.chains.base import chain_creator
|
from langflow.interface.chains.base import chain_creator
|
||||||
from langflow.interface.types import get_type_list
|
|
||||||
from langflow.interface.utils import load_file_into_dict
|
from langflow.interface.utils import load_file_into_dict
|
||||||
from langflow.utils import util, validate
|
from langflow.utils import validate
|
||||||
|
|
||||||
|
|
||||||
def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
|
def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
|
||||||
|
|
@ -153,24 +143,48 @@ def instantiate_embedding(class_object, params):
|
||||||
|
|
||||||
|
|
||||||
def instantiate_vectorstore(class_object, params):
|
def instantiate_vectorstore(class_object, params):
|
||||||
if len(params.get("documents", [])) == 0:
|
search_kwargs = params.pop("search_kwargs", {})
|
||||||
raise ValueError(
|
if initializer := vecstore_initializer.get(class_object.__name__):
|
||||||
"The source you provided did not load correctly or was empty."
|
vecstore = initializer(class_object, params)
|
||||||
"This may cause an error in the vectorstore."
|
else:
|
||||||
)
|
if "texts" in params:
|
||||||
# Chroma requires all metadata values to not be None
|
params["documents"] = params.pop("texts")
|
||||||
if class_object.__name__ == "Chroma":
|
vecstore = class_object.from_documents(**params)
|
||||||
for doc in params["documents"]:
|
|
||||||
if doc.metadata is None:
|
# ! This might not work. Need to test
|
||||||
doc.metadata = {}
|
if search_kwargs and hasattr(vecstore, "as_retriever"):
|
||||||
for key, value in doc.metadata.items():
|
vecstore = vecstore.as_retriever(search_kwargs=search_kwargs)
|
||||||
if value is None:
|
|
||||||
doc.metadata[key] = ""
|
return vecstore
|
||||||
return class_object.from_documents(**params)
|
|
||||||
|
|
||||||
|
|
||||||
def instantiate_documentloader(class_object, params):
|
def instantiate_documentloader(class_object, params):
|
||||||
return class_object(**params).load()
|
if "file_filter" in params:
|
||||||
|
# file_filter will be a string but we need a function
|
||||||
|
# that will be used to filter the files using file_filter
|
||||||
|
# like lambda x: x.endswith(".txt") but as we don't know
|
||||||
|
# anything besides the string, we will simply check if the string is
|
||||||
|
# in x and if it is, we will return True
|
||||||
|
file_filter = params.pop("file_filter", None)
|
||||||
|
extensions = file_filter.split(",")
|
||||||
|
params["file_filter"] = lambda x: any(
|
||||||
|
extension.strip() in x for extension in extensions
|
||||||
|
)
|
||||||
|
metadata = params.pop("metadata", None)
|
||||||
|
docs = class_object(**params).load()
|
||||||
|
if metadata:
|
||||||
|
if isinstance(metadata, str):
|
||||||
|
try:
|
||||||
|
metadata = json.loads(metadata)
|
||||||
|
except json.JSONDecodeError as exc:
|
||||||
|
raise ValueError(
|
||||||
|
"The metadata you provided is not a valid JSON string."
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
for doc in docs:
|
||||||
|
doc.metadata = metadata
|
||||||
|
|
||||||
|
return docs
|
||||||
|
|
||||||
|
|
||||||
def instantiate_textsplitter(class_object, params):
|
def instantiate_textsplitter(class_object, params):
|
||||||
|
|
@ -207,48 +221,14 @@ def replace_zero_shot_prompt_with_prompt_template(nodes):
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
|
|
||||||
def load_langchain_type_from_config(config: Dict[str, Any]):
|
|
||||||
"""Load langchain type from config"""
|
|
||||||
# Get type list
|
|
||||||
type_list = get_type_list()
|
|
||||||
if config["_type"] in type_list["agents"]:
|
|
||||||
config = util.update_verbose(config, new_value=False)
|
|
||||||
return load_agent_executor_from_config(config, verbose=True)
|
|
||||||
elif config["_type"] in type_list["chains"]:
|
|
||||||
config = util.update_verbose(config, new_value=False)
|
|
||||||
return load_chain_from_config(config, verbose=True)
|
|
||||||
elif config["_type"] in type_list["llms"]:
|
|
||||||
config = util.update_verbose(config, new_value=True)
|
|
||||||
return load_llm_from_config(config)
|
|
||||||
else:
|
|
||||||
raise ValueError("Type should be either agent, chain or llm")
|
|
||||||
|
|
||||||
|
|
||||||
def load_agent_executor_from_config(
|
|
||||||
config: dict,
|
|
||||||
llm: Optional[BaseLanguageModel] = None,
|
|
||||||
tools: Optional[list[Tool]] = None,
|
|
||||||
callback_manager: Optional[BaseCallbackManager] = None,
|
|
||||||
**kwargs: Any,
|
|
||||||
):
|
|
||||||
tools = load_tools_from_config(config["allowed_tools"])
|
|
||||||
config["allowed_tools"] = [tool.name for tool in tools] if tools else []
|
|
||||||
agent_obj = load_agent_from_config(config, llm, tools, **kwargs)
|
|
||||||
|
|
||||||
return AgentExecutor.from_agent_and_tools(
|
|
||||||
agent=agent_obj,
|
|
||||||
tools=tools,
|
|
||||||
callback_manager=callback_manager,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def load_agent_executor(agent_class: type[agent_module.Agent], params, **kwargs):
|
def load_agent_executor(agent_class: type[agent_module.Agent], params, **kwargs):
|
||||||
"""Load agent executor from agent class, tools and chain"""
|
"""Load agent executor from agent class, tools and chain"""
|
||||||
allowed_tools = params.get("allowed_tools", [])
|
allowed_tools: Sequence[BaseTool] = params.get("allowed_tools", [])
|
||||||
llm_chain = params["llm_chain"]
|
llm_chain = params["llm_chain"]
|
||||||
# if allowed_tools is not a list or set, make it a list
|
# if allowed_tools is not a list or set, make it a list
|
||||||
if not isinstance(allowed_tools, (list, set)):
|
if not isinstance(allowed_tools, (list, set)) and isinstance(
|
||||||
|
allowed_tools, BaseTool
|
||||||
|
):
|
||||||
allowed_tools = [allowed_tools]
|
allowed_tools = [allowed_tools]
|
||||||
tool_names = [tool.name for tool in allowed_tools]
|
tool_names = [tool.name for tool in allowed_tools]
|
||||||
# Agent class requires an output_parser but Agent classes
|
# Agent class requires an output_parser but Agent classes
|
||||||
|
|
@ -267,46 +247,6 @@ def load_toolkits_executor(node_type: str, toolkit: BaseToolkit, params: dict):
|
||||||
return create_function(llm=llm, toolkit=toolkit)
|
return create_function(llm=llm, toolkit=toolkit)
|
||||||
|
|
||||||
|
|
||||||
def load_tools_from_config(tool_list: list[dict]) -> list:
|
|
||||||
"""Load tools based on a config list.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
config: config list.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List of tools.
|
|
||||||
"""
|
|
||||||
tools = []
|
|
||||||
for tool in tool_list:
|
|
||||||
tool_type = tool.pop("_type")
|
|
||||||
llm_config = tool.pop("llm", None)
|
|
||||||
llm = load_llm_from_config(llm_config) if llm_config else None
|
|
||||||
kwargs = tool
|
|
||||||
if tool_type in _BASE_TOOLS:
|
|
||||||
tools.append(_BASE_TOOLS[tool_type]())
|
|
||||||
elif tool_type in _LLM_TOOLS:
|
|
||||||
if llm is None:
|
|
||||||
raise ValueError(f"Tool {tool_type} requires an LLM to be provided")
|
|
||||||
tools.append(_LLM_TOOLS[tool_type](llm))
|
|
||||||
elif tool_type in _EXTRA_LLM_TOOLS:
|
|
||||||
if llm is None:
|
|
||||||
raise ValueError(f"Tool {tool_type} requires an LLM to be provided")
|
|
||||||
_get_llm_tool_func, extra_keys = _EXTRA_LLM_TOOLS[tool_type]
|
|
||||||
if missing_keys := set(extra_keys).difference(kwargs):
|
|
||||||
raise ValueError(
|
|
||||||
f"Tool {tool_type} requires some parameters that were not "
|
|
||||||
f"provided: {missing_keys}"
|
|
||||||
)
|
|
||||||
tools.append(_get_llm_tool_func(llm=llm, **kwargs))
|
|
||||||
elif tool_type in _EXTRA_OPTIONAL_TOOLS:
|
|
||||||
_get_tool_func, extra_keys = _EXTRA_OPTIONAL_TOOLS[tool_type]
|
|
||||||
kwargs = {k: value for k, value in kwargs.items() if value}
|
|
||||||
tools.append(_get_tool_func(**kwargs))
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Got unknown tool {tool_type}")
|
|
||||||
return tools
|
|
||||||
|
|
||||||
|
|
||||||
def build_prompt_template(prompt, tools):
|
def build_prompt_template(prompt, tools):
|
||||||
"""Build PromptTemplate from ZeroShotPrompt"""
|
"""Build PromptTemplate from ZeroShotPrompt"""
|
||||||
prefix = prompt["node"]["template"]["prefix"]["value"]
|
prefix = prompt["node"]["template"]["prefix"]["value"]
|
||||||
223
src/backend/langflow/interface/initialize/vector_store.py
Normal file
223
src/backend/langflow/interface/initialize/vector_store.py
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
import json
|
||||||
|
from typing import Any, Callable, Dict, Type
|
||||||
|
from langchain.vectorstores import (
|
||||||
|
Pinecone,
|
||||||
|
Qdrant,
|
||||||
|
Chroma,
|
||||||
|
FAISS,
|
||||||
|
Weaviate,
|
||||||
|
SupabaseVectorStore,
|
||||||
|
MongoDBAtlasVectorSearch,
|
||||||
|
)
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def docs_in_params(params: dict) -> bool:
|
||||||
|
"""Check if params has documents OR texts and one of them is not an empty list,
|
||||||
|
If any of them is not an empty list, return True, else return False"""
|
||||||
|
return ("documents" in params and params["documents"]) or (
|
||||||
|
"texts" in params and params["texts"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_mongodb(class_object: Type[MongoDBAtlasVectorSearch], params: dict):
|
||||||
|
"""Initialize mongodb and return the class object"""
|
||||||
|
|
||||||
|
MONGODB_ATLAS_CLUSTER_URI = params.pop("mongodb_atlas_cluster_uri")
|
||||||
|
if not MONGODB_ATLAS_CLUSTER_URI:
|
||||||
|
raise ValueError("Mongodb atlas cluster uri must be provided in the params")
|
||||||
|
from pymongo import MongoClient
|
||||||
|
import certifi
|
||||||
|
|
||||||
|
client: MongoClient = MongoClient(
|
||||||
|
MONGODB_ATLAS_CLUSTER_URI, tlsCAFile=certifi.where()
|
||||||
|
)
|
||||||
|
db_name = params.pop("db_name", None)
|
||||||
|
collection_name = params.pop("collection_name", None)
|
||||||
|
if not db_name or not collection_name:
|
||||||
|
raise ValueError("db_name and collection_name must be provided in the params")
|
||||||
|
|
||||||
|
index_name = params.pop("index_name", None)
|
||||||
|
if not index_name:
|
||||||
|
raise ValueError("index_name must be provided in the params")
|
||||||
|
|
||||||
|
collection = client[db_name][collection_name]
|
||||||
|
if not docs_in_params(params):
|
||||||
|
# __init__ requires collection, embedding and index_name
|
||||||
|
init_args = {
|
||||||
|
"collection": collection,
|
||||||
|
"index_name": index_name,
|
||||||
|
"embedding": params.get("embedding"),
|
||||||
|
}
|
||||||
|
|
||||||
|
return class_object(**init_args)
|
||||||
|
|
||||||
|
if "texts" in params:
|
||||||
|
params["documents"] = params.pop("texts")
|
||||||
|
|
||||||
|
params["collection"] = collection
|
||||||
|
params["index_name"] = index_name
|
||||||
|
|
||||||
|
return class_object.from_documents(**params)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_supabase(class_object: Type[SupabaseVectorStore], params: dict):
|
||||||
|
"""Initialize supabase and return the class object"""
|
||||||
|
from supabase.client import Client, create_client
|
||||||
|
|
||||||
|
if "supabase_url" not in params or "supabase_service_key" not in params:
|
||||||
|
raise ValueError("Supabase url and service key must be provided in the params")
|
||||||
|
if "texts" in params:
|
||||||
|
params["documents"] = params.pop("texts")
|
||||||
|
|
||||||
|
client_kwargs = {
|
||||||
|
"supabase_url": params.pop("supabase_url"),
|
||||||
|
"supabase_key": params.pop("supabase_service_key"),
|
||||||
|
}
|
||||||
|
|
||||||
|
supabase: Client = create_client(**client_kwargs)
|
||||||
|
if not docs_in_params(params):
|
||||||
|
params.pop("documents", None)
|
||||||
|
params.pop("texts", None)
|
||||||
|
return class_object(client=supabase, **params)
|
||||||
|
# If there are docs in the params, create a new index
|
||||||
|
|
||||||
|
return class_object.from_documents(client=supabase, **params)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_weaviate(class_object: Type[Weaviate], params: dict):
|
||||||
|
"""Initialize weaviate and return the class object"""
|
||||||
|
if not docs_in_params(params):
|
||||||
|
import weaviate # type: ignore
|
||||||
|
|
||||||
|
client_kwargs_json = params.get("client_kwargs", "{}")
|
||||||
|
client_kwargs = json.loads(client_kwargs_json)
|
||||||
|
client_params = {
|
||||||
|
"url": params.get("weaviate_url"),
|
||||||
|
}
|
||||||
|
client_params.update(client_kwargs)
|
||||||
|
weaviate_client = weaviate.Client(**client_params)
|
||||||
|
|
||||||
|
new_params = {
|
||||||
|
"client": weaviate_client,
|
||||||
|
"index_name": params.get("index_name"),
|
||||||
|
"text_key": params.get("text_key"),
|
||||||
|
}
|
||||||
|
return class_object(**new_params)
|
||||||
|
# If there are docs in the params, create a new index
|
||||||
|
if "texts" in params:
|
||||||
|
params["documents"] = params.pop("texts")
|
||||||
|
|
||||||
|
return class_object.from_documents(**params)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_faiss(class_object: Type[FAISS], params: dict):
|
||||||
|
"""Initialize faiss and return the class object"""
|
||||||
|
|
||||||
|
if not docs_in_params(params):
|
||||||
|
return class_object.load_local
|
||||||
|
|
||||||
|
save_local = params.get("save_local")
|
||||||
|
faiss_index = class_object(**params)
|
||||||
|
if save_local:
|
||||||
|
faiss_index.save_local(folder_path=save_local)
|
||||||
|
return faiss_index
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_pinecone(class_object: Type[Pinecone], params: dict):
|
||||||
|
"""Initialize pinecone and return the class object"""
|
||||||
|
|
||||||
|
import pinecone # type: ignore
|
||||||
|
|
||||||
|
pinecone_api_key = params.get("pinecone_api_key")
|
||||||
|
pinecone_env = params.get("pinecone_env")
|
||||||
|
|
||||||
|
if pinecone_api_key is None or pinecone_env is None:
|
||||||
|
if os.getenv("PINECONE_API_KEY") is not None:
|
||||||
|
pinecone_api_key = os.getenv("PINECONE_API_KEY")
|
||||||
|
if os.getenv("PINECONE_ENV") is not None:
|
||||||
|
pinecone_env = os.getenv("PINECONE_ENV")
|
||||||
|
|
||||||
|
if pinecone_api_key is None or pinecone_env is None:
|
||||||
|
raise ValueError(
|
||||||
|
"Pinecone API key and environment must be provided in the params"
|
||||||
|
)
|
||||||
|
|
||||||
|
# initialize pinecone
|
||||||
|
pinecone.init(
|
||||||
|
api_key=pinecone_api_key, # find at app.pinecone.io
|
||||||
|
environment=pinecone_env, # next to api key in console
|
||||||
|
)
|
||||||
|
|
||||||
|
# If there are no docs in the params, return an existing index
|
||||||
|
# but first remove any texts or docs keys from the params
|
||||||
|
if not docs_in_params(params):
|
||||||
|
existing_index_params = {
|
||||||
|
"embedding": params.pop("embedding"),
|
||||||
|
}
|
||||||
|
if "index_name" in params:
|
||||||
|
existing_index_params["index_name"] = params.pop("index_name")
|
||||||
|
if "namespace" in params:
|
||||||
|
existing_index_params["namespace"] = params.pop("namespace")
|
||||||
|
|
||||||
|
return class_object.from_existing_index(**existing_index_params)
|
||||||
|
# If there are docs in the params, create a new index
|
||||||
|
if "texts" in params:
|
||||||
|
params["documents"] = params.pop("texts")
|
||||||
|
return class_object.from_documents(**params)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_chroma(class_object: Type[Chroma], params: dict):
|
||||||
|
"""Initialize a ChromaDB object from the params"""
|
||||||
|
persist = params.pop("persist", False)
|
||||||
|
if not docs_in_params(params):
|
||||||
|
params.pop("documents", None)
|
||||||
|
params.pop("texts", None)
|
||||||
|
params["embedding_function"] = params.pop("embedding")
|
||||||
|
chromadb = class_object(**params)
|
||||||
|
else:
|
||||||
|
if "texts" in params:
|
||||||
|
params["documents"] = params.pop("texts")
|
||||||
|
for doc in params["documents"]:
|
||||||
|
if doc.metadata is None:
|
||||||
|
doc.metadata = {}
|
||||||
|
for key, value in doc.metadata.items():
|
||||||
|
if value is None:
|
||||||
|
doc.metadata[key] = ""
|
||||||
|
chromadb = class_object.from_documents(**params)
|
||||||
|
if persist:
|
||||||
|
chromadb.persist()
|
||||||
|
return chromadb
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_qdrant(class_object: Type[Qdrant], params: dict):
|
||||||
|
if not docs_in_params(params):
|
||||||
|
if "location" not in params and "api_key" not in params:
|
||||||
|
raise ValueError("Location and API key must be provided in the params")
|
||||||
|
from qdrant_client import QdrantClient
|
||||||
|
|
||||||
|
client_params = {
|
||||||
|
"location": params.pop("location"),
|
||||||
|
"api_key": params.pop("api_key"),
|
||||||
|
}
|
||||||
|
lc_params = {
|
||||||
|
"collection_name": params.pop("collection_name"),
|
||||||
|
"embeddings": params.pop("embedding"),
|
||||||
|
}
|
||||||
|
client = QdrantClient(**client_params)
|
||||||
|
|
||||||
|
return class_object(client=client, **lc_params)
|
||||||
|
|
||||||
|
return class_object.from_documents(**params)
|
||||||
|
|
||||||
|
|
||||||
|
vecstore_initializer: Dict[str, Callable[[Type[Any], dict], Any]] = {
|
||||||
|
"Pinecone": initialize_pinecone,
|
||||||
|
"Chroma": initialize_chroma,
|
||||||
|
"Qdrant": initialize_qdrant,
|
||||||
|
"Weaviate": initialize_weaviate,
|
||||||
|
"FAISS": initialize_faiss,
|
||||||
|
"SupabaseVectorStore": initialize_supabase,
|
||||||
|
"MongoDBAtlasVectorSearch": initialize_mongodb,
|
||||||
|
}
|
||||||
|
|
@ -1,22 +1,8 @@
|
||||||
from langflow.cache.utils import compute_dict_hash, load_cache, memoize_dict
|
from langflow.cache.utils import memoize_dict
|
||||||
from langflow.graph import Graph
|
from langflow.graph import Graph
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
|
|
||||||
|
|
||||||
def load_langchain_object(data_graph, is_first_message=False):
|
|
||||||
"""
|
|
||||||
Load langchain object from cache if it exists, otherwise build it.
|
|
||||||
"""
|
|
||||||
computed_hash = compute_dict_hash(data_graph)
|
|
||||||
if is_first_message:
|
|
||||||
langchain_object = build_langchain_object(data_graph)
|
|
||||||
else:
|
|
||||||
logger.debug("Loading langchain object from cache")
|
|
||||||
langchain_object = load_cache(computed_hash)
|
|
||||||
|
|
||||||
return computed_hash, langchain_object
|
|
||||||
|
|
||||||
|
|
||||||
@memoize_dict(maxsize=10)
|
@memoize_dict(maxsize=10)
|
||||||
def build_langchain_object_with_caching(data_graph):
|
def build_langchain_object_with_caching(data_graph):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import re
|
||||||
import yaml
|
import yaml
|
||||||
from langchain.base_language import BaseLanguageModel
|
from langchain.base_language import BaseLanguageModel
|
||||||
from PIL.Image import Image
|
from PIL.Image import Image
|
||||||
|
from langflow.utils.logger import logger
|
||||||
|
|
||||||
|
|
||||||
def load_file_into_dict(file_path: str) -> dict:
|
def load_file_into_dict(file_path: str) -> dict:
|
||||||
|
|
@ -58,3 +59,22 @@ def try_setting_streaming_options(langchain_object, websocket):
|
||||||
def extract_input_variables_from_prompt(prompt: str) -> list[str]:
|
def extract_input_variables_from_prompt(prompt: str) -> list[str]:
|
||||||
"""Extract input variables from prompt."""
|
"""Extract input variables from prompt."""
|
||||||
return re.findall(r"{(.*?)}", prompt)
|
return re.findall(r"{(.*?)}", prompt)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_llm_caching():
|
||||||
|
"""Setup LLM caching."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
import langchain
|
||||||
|
from langflow.settings import settings
|
||||||
|
from langflow.interface.importing.utils import import_class
|
||||||
|
|
||||||
|
cache_class = import_class(f"langchain.cache.{settings.cache}")
|
||||||
|
|
||||||
|
logger.debug(f"Setting up LLM caching with {cache_class.__name__}")
|
||||||
|
langchain.llm_cache = cache_class()
|
||||||
|
logger.info(f"LLM caching setup with {cache_class.__name__}")
|
||||||
|
except ImportError:
|
||||||
|
logger.warning(f"Could not import {settings.cache}. ")
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning(f"Could not setup LLM caching. Error: {exc}")
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,12 @@ from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
from langflow.api import router
|
from langflow.api import router
|
||||||
from langflow.database.base import create_db_and_tables
|
from langflow.database.base import create_db_and_tables
|
||||||
|
from langflow.interface.utils import setup_llm_caching
|
||||||
|
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
"""Create the FastAPI app and include the router."""
|
"""Create the FastAPI app and include the router."""
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
origins = [
|
origins = [
|
||||||
|
|
@ -27,6 +29,7 @@ def create_app():
|
||||||
|
|
||||||
app.include_router(router)
|
app.include_router(router)
|
||||||
app.on_event("startup")(create_db_and_tables)
|
app.on_event("startup")(create_db_and_tables)
|
||||||
|
app.on_event("startup")(setup_llm_caching)
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import contextlib
|
from pathlib import Path
|
||||||
import io
|
|
||||||
from langchain.schema import AgentAction
|
from langchain.schema import AgentAction
|
||||||
import json
|
import json
|
||||||
from langflow.interface.run import (
|
from langflow.interface.run import (
|
||||||
|
|
@ -9,9 +8,9 @@ from langflow.interface.run import (
|
||||||
)
|
)
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
from langflow.graph import Graph
|
from langflow.graph import Graph
|
||||||
|
from langchain.chains.base import Chain
|
||||||
|
from langchain.vectorstores.base import VectorStore
|
||||||
from typing import Any, Dict, List, Tuple
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
|
|
||||||
def fix_memory_inputs(langchain_object):
|
def fix_memory_inputs(langchain_object):
|
||||||
|
|
@ -20,22 +19,23 @@ def fix_memory_inputs(langchain_object):
|
||||||
object's input variables. If so, it does nothing. Otherwise, it gets a possible new memory key using the
|
object's input variables. If so, it does nothing. Otherwise, it gets a possible new memory key using the
|
||||||
get_memory_key function and updates the memory keys using the update_memory_keys function.
|
get_memory_key function and updates the memory keys using the update_memory_keys function.
|
||||||
"""
|
"""
|
||||||
if hasattr(langchain_object, "memory") and langchain_object.memory is not None:
|
if not hasattr(langchain_object, "memory") or langchain_object.memory is None:
|
||||||
try:
|
return
|
||||||
if langchain_object.memory.memory_key in langchain_object.input_variables:
|
try:
|
||||||
return
|
if langchain_object.memory.memory_key in langchain_object.input_variables:
|
||||||
except AttributeError:
|
return
|
||||||
input_variables = (
|
except AttributeError:
|
||||||
langchain_object.prompt.input_variables
|
input_variables = (
|
||||||
if hasattr(langchain_object, "prompt")
|
langchain_object.prompt.input_variables
|
||||||
else langchain_object.input_keys
|
if hasattr(langchain_object, "prompt")
|
||||||
)
|
else langchain_object.input_keys
|
||||||
if langchain_object.memory.memory_key in input_variables:
|
)
|
||||||
return
|
if langchain_object.memory.memory_key in input_variables:
|
||||||
|
return
|
||||||
|
|
||||||
possible_new_mem_key = get_memory_key(langchain_object)
|
possible_new_mem_key = get_memory_key(langchain_object)
|
||||||
if possible_new_mem_key is not None:
|
if possible_new_mem_key is not None:
|
||||||
update_memory_keys(langchain_object, possible_new_mem_key)
|
update_memory_keys(langchain_object, possible_new_mem_key)
|
||||||
|
|
||||||
|
|
||||||
def format_actions(actions: List[Tuple[AgentAction, str]]) -> str:
|
def format_actions(actions: List[Tuple[AgentAction, str]]) -> str:
|
||||||
|
|
@ -54,79 +54,42 @@ def format_actions(actions: List[Tuple[AgentAction, str]]) -> str:
|
||||||
return "\n".join(output)
|
return "\n".join(output)
|
||||||
|
|
||||||
|
|
||||||
def get_result_and_thought(langchain_object, message: str):
|
def get_result_and_thought(langchain_object: Any, inputs: dict):
|
||||||
"""Get result and thought from extracted json"""
|
"""Get result and thought from extracted json"""
|
||||||
try:
|
try:
|
||||||
if hasattr(langchain_object, "verbose"):
|
if hasattr(langchain_object, "verbose"):
|
||||||
langchain_object.verbose = True
|
langchain_object.verbose = True
|
||||||
chat_input = None
|
|
||||||
memory_key = ""
|
|
||||||
if hasattr(langchain_object, "memory") and langchain_object.memory is not None:
|
|
||||||
memory_key = langchain_object.memory.memory_key
|
|
||||||
|
|
||||||
if hasattr(langchain_object, "input_keys"):
|
|
||||||
for key in langchain_object.input_keys:
|
|
||||||
if key not in [memory_key, "chat_history"]:
|
|
||||||
chat_input = {key: message}
|
|
||||||
else:
|
|
||||||
chat_input = message # type: ignore
|
|
||||||
|
|
||||||
if hasattr(langchain_object, "return_intermediate_steps"):
|
if hasattr(langchain_object, "return_intermediate_steps"):
|
||||||
# https://github.com/hwchase17/langchain/issues/2068
|
langchain_object.return_intermediate_steps = True
|
||||||
# Deactivating until we have a frontend solution
|
|
||||||
# to display intermediate steps
|
|
||||||
langchain_object.return_intermediate_steps = False
|
|
||||||
|
|
||||||
fix_memory_inputs(langchain_object)
|
fix_memory_inputs(langchain_object)
|
||||||
|
|
||||||
with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer):
|
try:
|
||||||
try:
|
output = langchain_object(inputs, return_only_outputs=True)
|
||||||
# if hasattr(langchain_object, "acall"):
|
except ValueError as exc:
|
||||||
# output = await langchain_object.acall(chat_input)
|
# make the error message more informative
|
||||||
# else:
|
logger.debug(f"Error: {str(exc)}")
|
||||||
output = langchain_object(chat_input)
|
output = langchain_object.run(inputs)
|
||||||
except ValueError as exc:
|
|
||||||
# make the error message more informative
|
|
||||||
logger.debug(f"Error: {str(exc)}")
|
|
||||||
output = langchain_object.run(chat_input)
|
|
||||||
|
|
||||||
intermediate_steps = (
|
|
||||||
output.get("intermediate_steps", []) if isinstance(output, dict) else []
|
|
||||||
)
|
|
||||||
|
|
||||||
result = (
|
|
||||||
output.get(langchain_object.output_keys[0])
|
|
||||||
if isinstance(output, dict)
|
|
||||||
else output
|
|
||||||
)
|
|
||||||
if intermediate_steps:
|
|
||||||
thought = format_actions(intermediate_steps)
|
|
||||||
else:
|
|
||||||
thought = output_buffer.getvalue()
|
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise ValueError(f"Error: {str(exc)}") from exc
|
raise ValueError(f"Error: {str(exc)}") from exc
|
||||||
return result, thought
|
return output
|
||||||
|
|
||||||
|
|
||||||
def load_or_build_langchain_object(data_graph, is_first_message=False):
|
def get_input_str_if_only_one_input(inputs: dict) -> Optional[str]:
|
||||||
"""
|
"""Get input string if only one input is provided"""
|
||||||
Load langchain object from cache if it exists, otherwise build it.
|
return list(inputs.values())[0] if len(inputs) == 1 else None
|
||||||
"""
|
|
||||||
if is_first_message:
|
|
||||||
build_langchain_object_with_caching.clear_cache()
|
|
||||||
return build_langchain_object_with_caching(data_graph)
|
|
||||||
|
|
||||||
|
|
||||||
def process_graph_cached(data_graph: Dict[str, Any], message: str):
|
def process_graph_cached(data_graph: Dict[str, Any], inputs: Optional[dict] = None):
|
||||||
"""
|
"""
|
||||||
Process graph by extracting input variables and replacing ZeroShotPrompt
|
Process graph by extracting input variables and replacing ZeroShotPrompt
|
||||||
with PromptTemplate,then run the graph and return the result and thought.
|
with PromptTemplate,then run the graph and return the result and thought.
|
||||||
"""
|
"""
|
||||||
# Load langchain object
|
# Load langchain object
|
||||||
is_first_message = len(data_graph.get("chatHistory", [])) == 0
|
langchain_object = build_langchain_object_with_caching(data_graph)
|
||||||
langchain_object = load_or_build_langchain_object(data_graph, is_first_message)
|
logger.debug("Loaded LangChain object")
|
||||||
logger.debug("Loaded langchain object")
|
|
||||||
|
|
||||||
if langchain_object is None:
|
if langchain_object is None:
|
||||||
# Raise user facing error
|
# Raise user facing error
|
||||||
|
|
@ -135,60 +98,121 @@ def process_graph_cached(data_graph: Dict[str, Any], message: str):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Generate result and thought
|
# Generate result and thought
|
||||||
logger.debug("Generating result and thought")
|
if isinstance(langchain_object, Chain):
|
||||||
result, thought = get_result_and_thought(langchain_object, message)
|
if inputs is None:
|
||||||
logger.debug("Generated result and thought")
|
raise ValueError("Inputs must be provided for a Chain")
|
||||||
return {"result": str(result), "thought": thought.strip()}
|
logger.debug("Generating result and thought")
|
||||||
|
result = get_result_and_thought(langchain_object, inputs)
|
||||||
|
logger.debug("Generated result and thought")
|
||||||
|
elif isinstance(langchain_object, VectorStore):
|
||||||
|
class_name = langchain_object.__class__.__name__
|
||||||
|
result = {"message": f"Processed {class_name} successfully"}
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def load_flow_from_json(path: str, build=True):
|
def load_flow_from_json(
|
||||||
"""Load flow from json file"""
|
input: Union[Path, str, dict], tweaks: Optional[dict] = None, build=True
|
||||||
# This is done to avoid circular imports
|
):
|
||||||
|
"""
|
||||||
|
Load flow from a JSON file or a JSON object.
|
||||||
|
|
||||||
with open(path, "r", encoding="utf-8") as f:
|
:param input: JSON file path or JSON object
|
||||||
flow_graph = json.load(f)
|
:param tweaks: Optional tweaks to be processed
|
||||||
data_graph = flow_graph["data"]
|
:param build: If True, build the graph, otherwise return the graph object
|
||||||
nodes = data_graph["nodes"]
|
:return: Langchain object or Graph object depending on the build parameter
|
||||||
# Substitute ZeroShotPrompt with PromptTemplate
|
"""
|
||||||
# nodes = replace_zero_shot_prompt_with_prompt_template(nodes)
|
# If input is a file path, load JSON from the file
|
||||||
# Add input variables
|
if isinstance(input, (str, Path)):
|
||||||
# nodes = payload.extract_input_variables(nodes)
|
with open(input, "r", encoding="utf-8") as f:
|
||||||
|
flow_graph = json.load(f)
|
||||||
|
# If input is a dictionary, assume it's a JSON object
|
||||||
|
elif isinstance(input, dict):
|
||||||
|
flow_graph = input
|
||||||
|
else:
|
||||||
|
raise TypeError(
|
||||||
|
"Input must be either a file path (str) or a JSON object (dict)"
|
||||||
|
)
|
||||||
|
|
||||||
# Nodes, edges and root node
|
graph_data = flow_graph["data"]
|
||||||
edges = data_graph["edges"]
|
if tweaks is not None:
|
||||||
|
graph_data = process_tweaks(graph_data, tweaks)
|
||||||
|
nodes = graph_data["nodes"]
|
||||||
|
edges = graph_data["edges"]
|
||||||
graph = Graph(nodes, edges)
|
graph = Graph(nodes, edges)
|
||||||
|
|
||||||
if build:
|
if build:
|
||||||
langchain_object = graph.build()
|
langchain_object = graph.build()
|
||||||
|
|
||||||
if hasattr(langchain_object, "verbose"):
|
if hasattr(langchain_object, "verbose"):
|
||||||
langchain_object.verbose = True
|
langchain_object.verbose = True
|
||||||
|
|
||||||
if hasattr(langchain_object, "return_intermediate_steps"):
|
if hasattr(langchain_object, "return_intermediate_steps"):
|
||||||
# https://github.com/hwchase17/langchain/issues/2068
|
|
||||||
# Deactivating until we have a frontend solution
|
# Deactivating until we have a frontend solution
|
||||||
# to display intermediate steps
|
# to display intermediate steps
|
||||||
langchain_object.return_intermediate_steps = False
|
langchain_object.return_intermediate_steps = False
|
||||||
|
|
||||||
fix_memory_inputs(langchain_object)
|
fix_memory_inputs(langchain_object)
|
||||||
return langchain_object
|
return langchain_object
|
||||||
|
|
||||||
return graph
|
return graph
|
||||||
|
|
||||||
|
|
||||||
def process_tweaks(graph_data: dict, tweaks: dict):
|
def validate_input(
|
||||||
"""This function is used to tweak the graph data using the node id and the tweaks dict"""
|
graph_data: Dict[str, Any], tweaks: Dict[str, Dict[str, Any]]
|
||||||
# the tweaks dict is a dict of dicts
|
) -> List[Dict[str, Any]]:
|
||||||
# the key is the node id and the value is a dict of the tweaks
|
if not isinstance(graph_data, dict) or not isinstance(tweaks, dict):
|
||||||
# the dict of tweaks contains the name of a certain parameter and the value to be tweaked
|
raise ValueError("graph_data and tweaks should be dictionaries")
|
||||||
|
|
||||||
|
nodes = graph_data.get("data", {}).get("nodes") or graph_data.get("nodes")
|
||||||
|
|
||||||
|
if not isinstance(nodes, list):
|
||||||
|
raise ValueError(
|
||||||
|
"graph_data should contain a list of nodes under 'data' key or directly under 'nodes' key"
|
||||||
|
)
|
||||||
|
|
||||||
|
return nodes
|
||||||
|
|
||||||
|
|
||||||
|
def apply_tweaks(node: Dict[str, Any], node_tweaks: Dict[str, Any]) -> None:
|
||||||
|
template_data = node.get("data", {}).get("node", {}).get("template")
|
||||||
|
|
||||||
|
if not isinstance(template_data, dict):
|
||||||
|
logger.warning(
|
||||||
|
f"Template data for node {node.get('id')} should be a dictionary"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
for tweak_name, tweak_value in node_tweaks.items():
|
||||||
|
if tweak_name and tweak_value and tweak_name in template_data:
|
||||||
|
key = tweak_name if tweak_name == "file_path" else "value"
|
||||||
|
template_data[tweak_name][key] = tweak_value
|
||||||
|
|
||||||
|
|
||||||
|
def process_tweaks(
|
||||||
|
graph_data: Dict[str, Any], tweaks: Dict[str, Dict[str, Any]]
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
This function is used to tweak the graph data using the node id and the tweaks dict.
|
||||||
|
|
||||||
|
:param graph_data: The dictionary containing the graph data. It must contain a 'data' key with
|
||||||
|
'nodes' as its child or directly contain 'nodes' key. Each node should have an 'id' and 'data'.
|
||||||
|
:param tweaks: A dictionary where the key is the node id and the value is a dictionary of the tweaks.
|
||||||
|
The inner dictionary contains the name of a certain parameter as the key and the value to be tweaked.
|
||||||
|
|
||||||
|
:return: The modified graph_data dictionary.
|
||||||
|
|
||||||
|
:raises ValueError: If the input is not in the expected format.
|
||||||
|
"""
|
||||||
|
nodes = validate_input(graph_data, tweaks)
|
||||||
|
|
||||||
# We need to process the graph data to add the tweaks
|
|
||||||
nodes = graph_data["data"]["nodes"]
|
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
node_id = node["id"]
|
if isinstance(node, dict) and isinstance(node.get("id"), str):
|
||||||
if node_id in tweaks:
|
node_id = node["id"]
|
||||||
node_tweaks = tweaks[node_id]
|
if node_tweaks := tweaks.get(node_id):
|
||||||
template_data = node["data"]["node"]["template"]
|
apply_tweaks(node, node_tweaks)
|
||||||
for tweak_name, tweake_value in node_tweaks.items():
|
else:
|
||||||
if tweak_name in template_data:
|
logger.warning(
|
||||||
template_data[tweak_name]["value"] = tweake_value
|
"Each node should be a dictionary with an 'id' key of type str"
|
||||||
print(
|
)
|
||||||
f"Something changed in node {node_id} with tweak {tweak_name} and value {tweake_value}"
|
|
||||||
)
|
|
||||||
return graph_data
|
return graph_data
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,8 @@ class Settings(BaseSettings):
|
||||||
utilities: List[str] = []
|
utilities: List[str] = []
|
||||||
dev: bool = False
|
dev: bool = False
|
||||||
database_url: str = "sqlite:///./langflow.db"
|
database_url: str = "sqlite:///./langflow.db"
|
||||||
save_api_keys: bool = True
|
cache: str = "InMemoryCache"
|
||||||
|
remove_api_keys: bool = False
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
validate_assignment = True
|
validate_assignment = True
|
||||||
|
|
@ -48,7 +49,6 @@ class Settings(BaseSettings):
|
||||||
self.textsplitters = new_settings.textsplitters or []
|
self.textsplitters = new_settings.textsplitters or []
|
||||||
self.utilities = new_settings.utilities or []
|
self.utilities = new_settings.utilities or []
|
||||||
self.dev = dev
|
self.dev = dev
|
||||||
self.save_api_keys = new_settings.save_api_keys
|
|
||||||
|
|
||||||
def update_settings(self, **kwargs):
|
def update_settings(self, **kwargs):
|
||||||
for key, value in kwargs.items():
|
for key, value in kwargs.items():
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ class TemplateFieldCreator(BaseModel, ABC):
|
||||||
suffixes: list[str] = []
|
suffixes: list[str] = []
|
||||||
fileTypes: list[str] = []
|
fileTypes: list[str] = []
|
||||||
file_types: list[str] = []
|
file_types: list[str] = []
|
||||||
content: Union[str, None] = None
|
file_path: Union[str, None] = None
|
||||||
password: bool = False
|
password: bool = False
|
||||||
options: list[str] = []
|
options: list[str] = []
|
||||||
name: str = ""
|
name: str = ""
|
||||||
|
|
@ -35,7 +35,7 @@ class TemplateFieldCreator(BaseModel, ABC):
|
||||||
result["fileTypes"] = result.pop("file_types")
|
result["fileTypes"] = result.pop("file_types")
|
||||||
|
|
||||||
if self.field_type == "file":
|
if self.field_type == "file":
|
||||||
result["content"] = self.content
|
result["file_path"] = self.file_path
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,7 @@ class CSVAgentNode(FrontendNode):
|
||||||
|
|
||||||
class InitializeAgentNode(FrontendNode):
|
class InitializeAgentNode(FrontendNode):
|
||||||
name: str = "AgentInitializer"
|
name: str = "AgentInitializer"
|
||||||
|
display_name: str = "AgentInitializer"
|
||||||
template: Template = Template(
|
template: Template = Template(
|
||||||
type_name="initialize_agent",
|
type_name="initialize_agent",
|
||||||
fields=[
|
fields=[
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,37 @@ class ChainFrontendNode(FrontendNode):
|
||||||
self.template.add_field(
|
self.template.add_field(
|
||||||
TemplateField(
|
TemplateField(
|
||||||
field_type="BaseChatMemory",
|
field_type="BaseChatMemory",
|
||||||
required=False,
|
required=True,
|
||||||
show=True,
|
show=True,
|
||||||
name="memory",
|
name="memory",
|
||||||
advanced=False,
|
advanced=False,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
# add return_source_documents
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="bool",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="return_source_documents",
|
||||||
|
advanced=False,
|
||||||
|
value=True,
|
||||||
|
display_name="Return source documents",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
is_list=True,
|
||||||
|
show=True,
|
||||||
|
multiline=False,
|
||||||
|
options=QA_CHAIN_TYPES,
|
||||||
|
value=QA_CHAIN_TYPES[0],
|
||||||
|
name="chain_type",
|
||||||
|
advanced=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
|
@ -33,13 +58,21 @@ class ChainFrontendNode(FrontendNode):
|
||||||
field.show = True
|
field.show = True
|
||||||
field.advanced = True
|
field.advanced = True
|
||||||
|
|
||||||
|
# We should think of a way to deal with this later
|
||||||
|
# if field.field_type == "PromptTemplate":
|
||||||
|
# field.field_type = "str"
|
||||||
|
# field.multiline = True
|
||||||
|
# field.show = True
|
||||||
|
# field.advanced = False
|
||||||
|
# field.value = field.value.template
|
||||||
|
|
||||||
# Separated for possible future changes
|
# Separated for possible future changes
|
||||||
if field.name == "prompt" and field.value is None:
|
if field.name == "prompt" and field.value is None:
|
||||||
field.required = True
|
field.required = True
|
||||||
field.show = True
|
field.show = True
|
||||||
field.advanced = False
|
field.advanced = False
|
||||||
if field.name == "memory":
|
if field.name == "memory":
|
||||||
field.required = False
|
# field.required = False
|
||||||
field.show = True
|
field.show = True
|
||||||
field.advanced = False
|
field.advanced = False
|
||||||
if field.name == "verbose":
|
if field.name == "verbose":
|
||||||
|
|
@ -51,6 +84,12 @@ class ChainFrontendNode(FrontendNode):
|
||||||
field.show = True
|
field.show = True
|
||||||
field.advanced = False
|
field.advanced = False
|
||||||
|
|
||||||
|
if field.name == "return_source_documents":
|
||||||
|
field.required = False
|
||||||
|
field.show = True
|
||||||
|
field.advanced = True
|
||||||
|
field.value = True
|
||||||
|
|
||||||
|
|
||||||
class SeriesCharacterChainNode(FrontendNode):
|
class SeriesCharacterChainNode(FrontendNode):
|
||||||
name: str = "SeriesCharacterChain"
|
name: str = "SeriesCharacterChain"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
|
from typing import Optional
|
||||||
from langflow.template.field.base import TemplateField
|
from langflow.template.field.base import TemplateField
|
||||||
from langflow.template.frontend_node.base import FrontendNode
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
|
|
||||||
|
|
||||||
def build_template(
|
def build_file_field(
|
||||||
suffixes: list, fileTypes: list, name: str = "file_path"
|
suffixes: list, fileTypes: list, name: str = "file_path"
|
||||||
) -> TemplateField:
|
) -> TemplateField:
|
||||||
"""Build a template field for a document loader."""
|
"""Build a template field for a document loader."""
|
||||||
|
|
@ -19,39 +20,90 @@ def build_template(
|
||||||
|
|
||||||
class DocumentLoaderFrontNode(FrontendNode):
|
class DocumentLoaderFrontNode(FrontendNode):
|
||||||
file_path_templates = {
|
file_path_templates = {
|
||||||
"AirbyteJSONLoader": build_template(suffixes=[".json"], fileTypes=["json"]),
|
"AirbyteJSONLoader": build_file_field(suffixes=[".json"], fileTypes=["json"]),
|
||||||
"CoNLLULoader": build_template(suffixes=[".csv"], fileTypes=["csv"]),
|
"CoNLLULoader": build_file_field(suffixes=[".csv"], fileTypes=["csv"]),
|
||||||
"CSVLoader": build_template(suffixes=[".csv"], fileTypes=["csv"]),
|
"CSVLoader": build_file_field(suffixes=[".csv"], fileTypes=["csv"]),
|
||||||
"UnstructuredEmailLoader": build_template(suffixes=[".eml"], fileTypes=["eml"]),
|
"UnstructuredEmailLoader": build_file_field(
|
||||||
"EverNoteLoader": build_template(suffixes=[".xml"], fileTypes=["xml"]),
|
suffixes=[".eml"], fileTypes=["eml"]
|
||||||
"FacebookChatLoader": build_template(suffixes=[".json"], fileTypes=["json"]),
|
),
|
||||||
"GutenbergLoader": build_template(suffixes=[".txt"], fileTypes=["txt"]),
|
"SlackDirectoryLoader": build_file_field(suffixes=[".zip"], fileTypes=["zip"]),
|
||||||
"BSHTMLLoader": build_template(suffixes=[".html"], fileTypes=["html"]),
|
"EverNoteLoader": build_file_field(suffixes=[".xml"], fileTypes=["xml"]),
|
||||||
"UnstructuredHTMLLoader": build_template(
|
"FacebookChatLoader": build_file_field(suffixes=[".json"], fileTypes=["json"]),
|
||||||
|
"GutenbergLoader": build_file_field(suffixes=[".txt"], fileTypes=["txt"]),
|
||||||
|
"BSHTMLLoader": build_file_field(suffixes=[".html"], fileTypes=["html"]),
|
||||||
|
"UnstructuredHTMLLoader": build_file_field(
|
||||||
suffixes=[".html"], fileTypes=["html"]
|
suffixes=[".html"], fileTypes=["html"]
|
||||||
),
|
),
|
||||||
"UnstructuredImageLoader": build_template(
|
"UnstructuredImageLoader": build_file_field(
|
||||||
suffixes=[".jpg", ".jpeg", ".png", ".gif", ".bmp"],
|
suffixes=[".jpg", ".jpeg", ".png", ".gif", ".bmp"],
|
||||||
fileTypes=["jpg", "jpeg", "png", "gif", "bmp"],
|
fileTypes=["jpg", "jpeg", "png", "gif", "bmp"],
|
||||||
),
|
),
|
||||||
"UnstructuredMarkdownLoader": build_template(
|
"UnstructuredMarkdownLoader": build_file_field(
|
||||||
suffixes=[".md"], fileTypes=["md"]
|
suffixes=[".md"], fileTypes=["md"]
|
||||||
),
|
),
|
||||||
"PyPDFLoader": build_template(suffixes=[".pdf"], fileTypes=["pdf"]),
|
"PyPDFLoader": build_file_field(suffixes=[".pdf"], fileTypes=["pdf"]),
|
||||||
"UnstructuredPowerPointLoader": build_template(
|
"UnstructuredPowerPointLoader": build_file_field(
|
||||||
suffixes=[".pptx", ".ppt"], fileTypes=["pptx", "ppt"]
|
suffixes=[".pptx", ".ppt"], fileTypes=["pptx", "ppt"]
|
||||||
),
|
),
|
||||||
"SRTLoader": build_template(suffixes=[".srt"], fileTypes=["srt"]),
|
"SRTLoader": build_file_field(suffixes=[".srt"], fileTypes=["srt"]),
|
||||||
"TelegramChatLoader": build_template(suffixes=[".json"], fileTypes=["json"]),
|
"TelegramChatLoader": build_file_field(suffixes=[".json"], fileTypes=["json"]),
|
||||||
"TextLoader": build_template(suffixes=[".txt"], fileTypes=["txt"]),
|
"TextLoader": build_file_field(suffixes=[".txt"], fileTypes=["txt"]),
|
||||||
"UnstructuredWordDocumentLoader": build_template(
|
"UnstructuredWordDocumentLoader": build_file_field(
|
||||||
suffixes=[".docx", ".doc"], fileTypes=["docx", "doc"]
|
suffixes=[".docx", ".doc"], fileTypes=["docx", "doc"]
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
def add_extra_fields(self) -> None:
|
def add_extra_fields(self) -> None:
|
||||||
name = None
|
name = None
|
||||||
if self.template.type_name in self.file_path_templates:
|
display_name = "Web Page"
|
||||||
|
if self.template.type_name in {"GitLoader"}:
|
||||||
|
# Add fields repo_path, clone_url, branch and file_filter
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="repo_path",
|
||||||
|
value="",
|
||||||
|
display_name="Path to repository",
|
||||||
|
advanced=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="clone_url",
|
||||||
|
value="",
|
||||||
|
display_name="Clone URL",
|
||||||
|
advanced=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="branch",
|
||||||
|
value="",
|
||||||
|
display_name="Branch",
|
||||||
|
advanced=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="file_filter",
|
||||||
|
value="",
|
||||||
|
display_name="File extensions (comma-separated)",
|
||||||
|
advanced=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
elif self.template.type_name in self.file_path_templates:
|
||||||
self.template.add_field(self.file_path_templates[self.template.type_name])
|
self.template.add_field(self.file_path_templates[self.template.type_name])
|
||||||
elif self.template.type_name in {
|
elif self.template.type_name in {
|
||||||
"WebBaseLoader",
|
"WebBaseLoader",
|
||||||
|
|
@ -64,8 +116,13 @@ class DocumentLoaderFrontNode(FrontendNode):
|
||||||
name = "web_path"
|
name = "web_path"
|
||||||
elif self.template.type_name in {"GitbookLoader"}:
|
elif self.template.type_name in {"GitbookLoader"}:
|
||||||
name = "web_page"
|
name = "web_page"
|
||||||
elif self.template.type_name in {"ReadTheDocsLoader"}:
|
elif self.template.type_name in {
|
||||||
|
"DirectoryLoader",
|
||||||
|
"ReadTheDocsLoader",
|
||||||
|
"NotionDirectoryLoader",
|
||||||
|
}:
|
||||||
name = "path"
|
name = "path"
|
||||||
|
display_name = "Local directory"
|
||||||
if name:
|
if name:
|
||||||
self.template.add_field(
|
self.template.add_field(
|
||||||
TemplateField(
|
TemplateField(
|
||||||
|
|
@ -74,6 +131,37 @@ class DocumentLoaderFrontNode(FrontendNode):
|
||||||
show=True,
|
show=True,
|
||||||
name=name,
|
name=name,
|
||||||
value="",
|
value="",
|
||||||
display_name="Web Page",
|
display_name=display_name,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
if self.template.type_name in {"DirectoryLoader"}:
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="glob",
|
||||||
|
value="**/*.txt",
|
||||||
|
display_name="glob",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# add a metadata field of type dict
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="code",
|
||||||
|
required=True,
|
||||||
|
show=True,
|
||||||
|
name="metadata",
|
||||||
|
value="{}",
|
||||||
|
display_name="Metadata",
|
||||||
|
multiline=False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
FrontendNode.format_field(field, name)
|
||||||
|
if field.name == "metadata":
|
||||||
|
field.show = True
|
||||||
|
field.advanced = False
|
||||||
|
field.show = True
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,27 @@ class MemoryFrontendNode(FrontendNode):
|
||||||
value=False,
|
value=False,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
# add input_key and output_key str fields
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="input_key",
|
||||||
|
advanced=True,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.template.add_field(
|
||||||
|
TemplateField(
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="output_key",
|
||||||
|
advanced=True,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
|
|
@ -36,3 +57,10 @@ class MemoryFrontendNode(FrontendNode):
|
||||||
field.required = False
|
field.required = False
|
||||||
field.show = True
|
field.show = True
|
||||||
field.advanced = False
|
field.advanced = False
|
||||||
|
if field.name in ["input_key", "output_key"]:
|
||||||
|
field.required = False
|
||||||
|
field.show = True
|
||||||
|
field.advanced = False
|
||||||
|
field.value = ""
|
||||||
|
if field.name == "memory_key":
|
||||||
|
field.value = "chat_history"
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,16 @@ class PythonFunctionToolNode(FrontendNode):
|
||||||
name="code",
|
name="code",
|
||||||
advanced=False,
|
advanced=False,
|
||||||
),
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="bool",
|
||||||
|
required=True,
|
||||||
|
placeholder="",
|
||||||
|
is_list=False,
|
||||||
|
show=True,
|
||||||
|
multiline=False,
|
||||||
|
value=False,
|
||||||
|
name="return_direct",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
description: str = "Python function to be executed."
|
description: str = "Python function to be executed."
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from langflow.template.field.base import TemplateField
|
from langflow.template.field.base import TemplateField
|
||||||
from langflow.template.frontend_node.base import FrontendNode
|
from langflow.template.frontend_node.base import FrontendNode
|
||||||
|
|
@ -6,6 +6,19 @@ from langflow.template.frontend_node.base import FrontendNode
|
||||||
|
|
||||||
class VectorStoreFrontendNode(FrontendNode):
|
class VectorStoreFrontendNode(FrontendNode):
|
||||||
def add_extra_fields(self) -> None:
|
def add_extra_fields(self) -> None:
|
||||||
|
extra_fields: List[TemplateField] = []
|
||||||
|
# Add search_kwargs field
|
||||||
|
extra_field = TemplateField(
|
||||||
|
name="search_kwargs",
|
||||||
|
field_type="code",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
value="{}",
|
||||||
|
)
|
||||||
|
extra_fields.append(extra_field)
|
||||||
if self.template.type_name == "Weaviate":
|
if self.template.type_name == "Weaviate":
|
||||||
extra_field = TemplateField(
|
extra_field = TemplateField(
|
||||||
name="weaviate_url",
|
name="weaviate_url",
|
||||||
|
|
@ -17,8 +30,174 @@ class VectorStoreFrontendNode(FrontendNode):
|
||||||
multiline=False,
|
multiline=False,
|
||||||
value="http://localhost:8080",
|
value="http://localhost:8080",
|
||||||
)
|
)
|
||||||
|
# Add client_kwargs field
|
||||||
|
extra_field2 = TemplateField(
|
||||||
|
name="client_kwargs",
|
||||||
|
field_type="code",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
value="{}",
|
||||||
|
)
|
||||||
|
extra_fields.extend((extra_field, extra_field2))
|
||||||
|
|
||||||
self.template.add_field(extra_field)
|
elif self.template.type_name == "Chroma":
|
||||||
|
# New bool field for persist parameter
|
||||||
|
extra_field = TemplateField(
|
||||||
|
name="persist",
|
||||||
|
field_type="bool",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
advanced=False,
|
||||||
|
value=True,
|
||||||
|
display_name="Persist",
|
||||||
|
)
|
||||||
|
extra_fields.append(extra_field)
|
||||||
|
elif self.template.type_name == "Pinecone":
|
||||||
|
# add pinecone_api_key and pinecone_env
|
||||||
|
extra_field = TemplateField(
|
||||||
|
name="pinecone_api_key",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_field2 = TemplateField(
|
||||||
|
name="pinecone_env",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_fields.extend((extra_field, extra_field2))
|
||||||
|
elif self.template.type_name == "FAISS":
|
||||||
|
extra_field = TemplateField(
|
||||||
|
name="folder_path",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
display_name="Local Path",
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_field2 = TemplateField(
|
||||||
|
name="index_name",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
advanced=False,
|
||||||
|
value="",
|
||||||
|
display_name="Index Name",
|
||||||
|
)
|
||||||
|
extra_fields.extend((extra_field, extra_field2))
|
||||||
|
elif self.template.type_name == "SupabaseVectorStore":
|
||||||
|
self.display_name = "Supabase"
|
||||||
|
# Add table_name and query_name
|
||||||
|
extra_field = TemplateField(
|
||||||
|
name="table_name",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_field2 = TemplateField(
|
||||||
|
name="query_name",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
# Add supabase_url and supabase_service_key
|
||||||
|
extra_field3 = TemplateField(
|
||||||
|
name="supabase_url",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_field4 = TemplateField(
|
||||||
|
name="supabase_service_key",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_fields.extend((extra_field, extra_field2, extra_field3, extra_field4))
|
||||||
|
|
||||||
|
elif self.template.type_name == "MongoDBAtlasVectorSearch":
|
||||||
|
self.display_name = "MongoDB Atlas"
|
||||||
|
|
||||||
|
extra_field = TemplateField(
|
||||||
|
name="mongodb_atlas_cluster_uri",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
display_name="MongoDB Atlas Cluster URI",
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_field2 = TemplateField(
|
||||||
|
name="collection_name",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
display_name="Collection Name",
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_field3 = TemplateField(
|
||||||
|
name="db_name",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
display_name="Database Name",
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_field4 = TemplateField(
|
||||||
|
name="index_name",
|
||||||
|
field_type="str",
|
||||||
|
required=False,
|
||||||
|
placeholder="",
|
||||||
|
show=True,
|
||||||
|
advanced=True,
|
||||||
|
multiline=False,
|
||||||
|
display_name="Index Name",
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
extra_fields.extend((extra_field, extra_field2, extra_field3, extra_field4))
|
||||||
|
|
||||||
|
if extra_fields:
|
||||||
|
for field in extra_fields:
|
||||||
|
self.template.add_field(field)
|
||||||
|
|
||||||
def add_extra_base_classes(self) -> None:
|
def add_extra_base_classes(self) -> None:
|
||||||
self.base_classes.append("BaseRetriever")
|
self.base_classes.append("BaseRetriever")
|
||||||
|
|
@ -27,7 +206,25 @@ class VectorStoreFrontendNode(FrontendNode):
|
||||||
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
def format_field(field: TemplateField, name: Optional[str] = None) -> None:
|
||||||
FrontendNode.format_field(field, name)
|
FrontendNode.format_field(field, name)
|
||||||
# Define common field attributes
|
# Define common field attributes
|
||||||
basic_fields = ["work_dir", "collection_name", "api_key", "location"]
|
basic_fields = [
|
||||||
|
"work_dir",
|
||||||
|
"collection_name",
|
||||||
|
"api_key",
|
||||||
|
"location",
|
||||||
|
"persist_directory",
|
||||||
|
"persist",
|
||||||
|
"weaviate_url",
|
||||||
|
"index_name",
|
||||||
|
"namespace",
|
||||||
|
"folder_path",
|
||||||
|
"table_name",
|
||||||
|
"query_name",
|
||||||
|
"supabase_url",
|
||||||
|
"supabase_service_key",
|
||||||
|
"mongodb_atlas_cluster_uri",
|
||||||
|
"collection_name",
|
||||||
|
"db_name",
|
||||||
|
]
|
||||||
advanced_fields = [
|
advanced_fields = [
|
||||||
"n_dim",
|
"n_dim",
|
||||||
"key",
|
"key",
|
||||||
|
|
@ -43,14 +240,21 @@ class VectorStoreFrontendNode(FrontendNode):
|
||||||
"https",
|
"https",
|
||||||
"prefer_grpc",
|
"prefer_grpc",
|
||||||
"grpc_port",
|
"grpc_port",
|
||||||
|
"pinecone_api_key",
|
||||||
|
"pinecone_env",
|
||||||
|
"client_kwargs",
|
||||||
|
"search_kwargs",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Check and set field attributes
|
# Check and set field attributes
|
||||||
if field.name == "texts":
|
if field.name == "texts":
|
||||||
|
# if field.name is "texts" it has to be replaced
|
||||||
|
# when instantiating the vectorstores
|
||||||
field.name = "documents"
|
field.name = "documents"
|
||||||
|
|
||||||
field.field_type = "TextSplitter"
|
field.field_type = "TextSplitter"
|
||||||
field.display_name = "Text Splitter"
|
field.display_name = "Documents"
|
||||||
field.required = True
|
field.required = False
|
||||||
field.show = True
|
field.show = True
|
||||||
field.advanced = False
|
field.advanced = False
|
||||||
|
|
||||||
|
|
@ -78,5 +282,6 @@ class VectorStoreFrontendNode(FrontendNode):
|
||||||
field.advanced = True
|
field.advanced = True
|
||||||
if "key" in field.name:
|
if "key" in field.name:
|
||||||
field.password = False
|
field.password = False
|
||||||
# TODO: Weaviate requires weaviate_url to be passed as it is not part of
|
|
||||||
# the class or from_texts method. We need the add_extra_fields to fix this
|
elif field.name == "text_key":
|
||||||
|
field.show = False
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ from typing import Callable, Optional, Union
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from langflow.template.field.base import TemplateField
|
from langflow.template.field.base import TemplateField
|
||||||
|
from langflow.utils.constants import DIRECT_TYPES
|
||||||
|
|
||||||
|
|
||||||
class Template(BaseModel):
|
class Template(BaseModel):
|
||||||
|
|
@ -18,8 +19,15 @@ class Template(BaseModel):
|
||||||
for field in self.fields:
|
for field in self.fields:
|
||||||
format_field_func(field, name)
|
format_field_func(field, name)
|
||||||
|
|
||||||
|
def sort_fields(self):
|
||||||
|
# first sort alphabetically
|
||||||
|
# then sort fields so that fields that have .field_type in DIRECT_TYPES are first
|
||||||
|
self.fields.sort(key=lambda x: x.name)
|
||||||
|
self.fields.sort(key=lambda x: x.field_type in DIRECT_TYPES, reverse=False)
|
||||||
|
|
||||||
def to_dict(self, format_field_func=None):
|
def to_dict(self, format_field_func=None):
|
||||||
self.process_fields(self.type_name, format_field_func)
|
self.process_fields(self.type_name, format_field_func)
|
||||||
|
self.sort_fields()
|
||||||
result = {field.name: field.to_dict() for field in self.fields}
|
result = {field.name: field.to_dict() for field in self.fields}
|
||||||
result["_type"] = self.type_name # type: ignore
|
result["_type"] = self.type_name # type: ignore
|
||||||
return result
|
return result
|
||||||
|
|
|
||||||
|
|
@ -36,3 +36,4 @@ def python_function(text: str) -> str:
|
||||||
\"\"\"This is a default python function that returns the input text\"\"\"
|
\"\"\"This is a default python function that returns the input text\"\"\"
|
||||||
return text
|
return text
|
||||||
"""
|
"""
|
||||||
|
DIRECT_TYPES = ["str", "bool", "code", "int", "float", "Any", "prompt"]
|
||||||
|
|
|
||||||
|
|
@ -299,12 +299,15 @@ def format_dict(d, name: Optional[str] = None):
|
||||||
if name == "OpenAI" and key == "model_name":
|
if name == "OpenAI" and key == "model_name":
|
||||||
value["options"] = constants.OPENAI_MODELS
|
value["options"] = constants.OPENAI_MODELS
|
||||||
value["list"] = True
|
value["list"] = True
|
||||||
|
value["value"] = constants.OPENAI_MODELS[0]
|
||||||
elif name == "ChatOpenAI" and key == "model_name":
|
elif name == "ChatOpenAI" and key == "model_name":
|
||||||
value["options"] = constants.CHAT_OPENAI_MODELS
|
value["options"] = constants.CHAT_OPENAI_MODELS
|
||||||
value["list"] = True
|
value["list"] = True
|
||||||
|
value["value"] = constants.CHAT_OPENAI_MODELS[0]
|
||||||
elif (name == "Anthropic" or name == "ChatAnthropic") and key == "model_name":
|
elif (name == "Anthropic" or name == "ChatAnthropic") and key == "model_name":
|
||||||
value["options"] = constants.ANTHROPIC_MODELS
|
value["options"] = constants.ANTHROPIC_MODELS
|
||||||
value["list"] = True
|
value["list"] = True
|
||||||
|
value["value"] = constants.ANTHROPIC_MODELS[0]
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import ast
|
import ast
|
||||||
|
import contextlib
|
||||||
import importlib
|
import importlib
|
||||||
import types
|
import types
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
@ -147,11 +148,8 @@ def create_function(code, function_name):
|
||||||
code_obj = compile(
|
code_obj = compile(
|
||||||
ast.Module(body=[function_code], type_ignores=[]), "<string>", "exec"
|
ast.Module(body=[function_code], type_ignores=[]), "<string>", "exec"
|
||||||
)
|
)
|
||||||
try:
|
with contextlib.suppress(Exception):
|
||||||
exec(code_obj, exec_globals, locals())
|
exec(code_obj, exec_globals, locals())
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
exec_globals[function_name] = locals()[function_name]
|
exec_globals[function_name] = locals()[function_name]
|
||||||
|
|
||||||
# Return a function that imports necessary modules and calls the target function
|
# Return a function that imports necessary modules and calls the target function
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
/usr/lib/node_modules/opencommit/out/cli.cjs
|
|
||||||
3623
src/frontend/package-lock.json
generated
3623
src/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -8,15 +8,16 @@
|
||||||
"@headlessui/react": "^1.7.10",
|
"@headlessui/react": "^1.7.10",
|
||||||
"@heroicons/react": "^2.0.15",
|
"@heroicons/react": "^2.0.15",
|
||||||
"@mui/material": "^5.11.9",
|
"@mui/material": "^5.11.9",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
|
||||||
"@radix-ui/react-menubar": "^1.0.3",
|
|
||||||
"@radix-ui/react-separator": "^1.0.3",
|
|
||||||
"@radix-ui/react-slot": "^1.0.2",
|
|
||||||
"@radix-ui/react-tabs": "^1.0.4",
|
|
||||||
"@radix-ui/react-checkbox": "^1.0.4",
|
"@radix-ui/react-checkbox": "^1.0.4",
|
||||||
"@radix-ui/react-dialog": "^1.0.4",
|
"@radix-ui/react-dialog": "^1.0.4",
|
||||||
|
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
|
"@radix-ui/react-menubar": "^1.0.3",
|
||||||
|
"@radix-ui/react-progress": "^1.0.3",
|
||||||
|
"@radix-ui/react-separator": "^1.0.3",
|
||||||
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
"@radix-ui/react-switch": "^1.0.3",
|
"@radix-ui/react-switch": "^1.0.3",
|
||||||
|
"@radix-ui/react-tabs": "^1.0.4",
|
||||||
"@radix-ui/react-tooltip": "^1.0.6",
|
"@radix-ui/react-tooltip": "^1.0.6",
|
||||||
"@tabler/icons-react": "^2.18.0",
|
"@tabler/icons-react": "^2.18.0",
|
||||||
"@tailwindcss/forms": "^0.5.3",
|
"@tailwindcss/forms": "^0.5.3",
|
||||||
|
|
@ -98,9 +99,10 @@
|
||||||
"@types/uuid": "^9.0.1",
|
"@types/uuid": "^9.0.1",
|
||||||
"@vitejs/plugin-react-swc": "^3.0.0",
|
"@vitejs/plugin-react-swc": "^3.0.0",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
|
"daisyui": "^3.1.1",
|
||||||
"postcss": "^8.4.23",
|
"postcss": "^8.4.23",
|
||||||
"tailwindcss": "^3.3.2",
|
"tailwindcss": "^3.3.2",
|
||||||
"typescript": "^5.0.2",
|
"typescript": "^5.0.2",
|
||||||
"vite": "^4.3.5"
|
"vite": "^4.3.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,13 +47,6 @@ export default function App() {
|
||||||
}>
|
}>
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
// Initialize state variable for the version
|
|
||||||
const [version, setVersion] = useState("");
|
|
||||||
useEffect(() => {
|
|
||||||
getVersion().then((data) => {
|
|
||||||
setVersion(data.version);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
// Use effect hook to update alertsList when a new alert is added
|
// Use effect hook to update alertsList when a new alert is added
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// If there is an error alert open with data, add it to the alertsList
|
// If there is an error alert open with data, add it to the alertsList
|
||||||
|
|
@ -157,14 +150,6 @@ export default function App() {
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<a
|
|
||||||
target={"_blank"}
|
|
||||||
href="https://logspace.ai/"
|
|
||||||
className="absolute left-7 bottom-2 flex h-6 cursor-pointer flex-col items-center justify-start overflow-hidden rounded-lg bg-foreground px-2 text-center font-sans text-xs tracking-wide text-secondary transition-all duration-500 ease-in-out hover:h-12"
|
|
||||||
>
|
|
||||||
{version && <div className="mt-1">⛓️ LangFlow v{version}</div>}
|
|
||||||
<div className={version ? "mt-2" : "mt-1"}>Created by Logspace</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import { Handle, Position, useUpdateNodeInternals } from "reactflow";
|
import { Handle, Position, useUpdateNodeInternals } from "reactflow";
|
||||||
import {
|
import {
|
||||||
classNames,
|
classNames,
|
||||||
|
getRandomKeyByssmm,
|
||||||
groupByFamily,
|
groupByFamily,
|
||||||
isValidConnection,
|
isValidConnection,
|
||||||
|
nodeIconsLucide,
|
||||||
} from "../../../../utils";
|
} from "../../../../utils";
|
||||||
import { useContext, useEffect, useRef, useState } from "react";
|
import { useContext, useEffect, useRef, useState } from "react";
|
||||||
import InputComponent from "../../../../components/inputComponent";
|
import InputComponent from "../../../../components/inputComponent";
|
||||||
|
|
@ -17,7 +19,7 @@ import InputFileComponent from "../../../../components/inputFileComponent";
|
||||||
import { TabsContext } from "../../../../contexts/tabsContext";
|
import { TabsContext } from "../../../../contexts/tabsContext";
|
||||||
import IntComponent from "../../../../components/intComponent";
|
import IntComponent from "../../../../components/intComponent";
|
||||||
import PromptAreaComponent from "../../../../components/promptComponent";
|
import PromptAreaComponent from "../../../../components/promptComponent";
|
||||||
import { nodeNames, nodeIcons } from "../../../../utils";
|
import { nodeNames } from "../../../../utils";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { nodeColors } from "../../../../utils";
|
import { nodeColors } from "../../../../utils";
|
||||||
import ShadTooltip from "../../../../components/ShadTooltipComponent";
|
import ShadTooltip from "../../../../components/ShadTooltipComponent";
|
||||||
|
|
@ -40,7 +42,7 @@ export default function ParameterComponent({
|
||||||
const updateNodeInternals = useUpdateNodeInternals();
|
const updateNodeInternals = useUpdateNodeInternals();
|
||||||
const [position, setPosition] = useState(0);
|
const [position, setPosition] = useState(0);
|
||||||
const { closePopUp } = useContext(PopUpContext);
|
const { closePopUp } = useContext(PopUpContext);
|
||||||
const { setTabsState, tabId } = useContext(TabsContext);
|
const { setTabsState, tabId, save } = useContext(TabsContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ref.current && ref.current.offsetTop && ref.current.clientHeight) {
|
if (ref.current && ref.current.offsetTop && ref.current.clientHeight) {
|
||||||
|
|
@ -82,18 +84,18 @@ export default function ParameterComponent({
|
||||||
|
|
||||||
refHtml.current = groupedObj.map((item, i) => (
|
refHtml.current = groupedObj.map((item, i) => (
|
||||||
<span
|
<span
|
||||||
key={i}
|
key={getRandomKeyByssmm()}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
i > 0 ? "items-center flex mt-3" : "items-center flex"
|
i > 0 ? "items-center flex mt-3" : "items-center flex"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="h-5 w-5"
|
className="h-6 w-6"
|
||||||
style={{
|
style={{
|
||||||
color: nodeColors[item.family],
|
color: nodeColors[item.family],
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{React.createElement(nodeIcons[item.family])}
|
{React.createElement(nodeIconsLucide[item.family])}
|
||||||
</div>
|
</div>
|
||||||
<span className="ps-2 text-gray-950">
|
<span className="ps-2 text-gray-950">
|
||||||
{nodeNames[item.family] ?? ""}{" "}
|
{nodeNames[item.family] ?? ""}{" "}
|
||||||
|
|
@ -102,14 +104,14 @@ export default function ParameterComponent({
|
||||||
-
|
-
|
||||||
{item.type.split(", ").length > 2
|
{item.type.split(", ").length > 2
|
||||||
? item.type.split(", ").map((el, i) => (
|
? item.type.split(", ").map((el, i) => (
|
||||||
<>
|
<React.Fragment key={el + i}>
|
||||||
<span key={i}>
|
<span>
|
||||||
{i == item.type.split(", ").length - 1
|
{i === item.type.split(", ").length - 1
|
||||||
? el
|
? el
|
||||||
: (el += `, `)}
|
: (el += `, `)}
|
||||||
</span>
|
</span>
|
||||||
{i % 2 == 0 && i > 0 && <br></br>}
|
{i % 2 === 0 && i > 0 && <br />}
|
||||||
</>
|
</React.Fragment>
|
||||||
))
|
))
|
||||||
: item.type}
|
: item.type}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -239,7 +241,8 @@ export default function ParameterComponent({
|
||||||
fileTypes={data.node.template[name].fileTypes}
|
fileTypes={data.node.template[name].fileTypes}
|
||||||
suffixes={data.node.template[name].suffixes}
|
suffixes={data.node.template[name].suffixes}
|
||||||
onFileChange={(t: string) => {
|
onFileChange={(t: string) => {
|
||||||
data.node.template[name].content = t;
|
data.node.template[name].file_path = t;
|
||||||
|
save();
|
||||||
}}
|
}}
|
||||||
></InputFileComponent>
|
></InputFileComponent>
|
||||||
) : left === true && type === "int" ? (
|
) : left === true && type === "int" ? (
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,21 @@
|
||||||
import { classNames, nodeColors, nodeIcons, toTitleCase } from "../../utils";
|
import {
|
||||||
|
classNames,
|
||||||
|
nodeColors,
|
||||||
|
nodeIconsLucide,
|
||||||
|
toTitleCase,
|
||||||
|
} from "../../utils";
|
||||||
import ParameterComponent from "./components/parameterComponent";
|
import ParameterComponent from "./components/parameterComponent";
|
||||||
import { typesContext } from "../../contexts/typesContext";
|
import { typesContext } from "../../contexts/typesContext";
|
||||||
import { useContext, useState, useEffect, useRef } from "react";
|
import {
|
||||||
|
useContext,
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
ForwardRefExoticComponent,
|
||||||
|
ComponentType,
|
||||||
|
SVGProps,
|
||||||
|
ReactNode,
|
||||||
|
} from "react";
|
||||||
import { NodeDataType } from "../../types/flow";
|
import { NodeDataType } from "../../types/flow";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
import { PopUpContext } from "../../contexts/popUpContext";
|
import { PopUpContext } from "../../contexts/popUpContext";
|
||||||
|
|
@ -12,6 +26,7 @@ import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarCom
|
||||||
|
|
||||||
import ShadTooltip from "../../components/ShadTooltipComponent";
|
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||||
import { useSSE } from "../../contexts/SSEContext";
|
import { useSSE } from "../../contexts/SSEContext";
|
||||||
|
import { ReactElement } from "react-markdown/lib/react-markdown";
|
||||||
|
|
||||||
export default function GenericNode({
|
export default function GenericNode({
|
||||||
data,
|
data,
|
||||||
|
|
@ -25,11 +40,12 @@ export default function GenericNode({
|
||||||
const { types, deleteNode } = useContext(typesContext);
|
const { types, deleteNode } = useContext(typesContext);
|
||||||
|
|
||||||
const { closePopUp, openPopUp } = useContext(PopUpContext);
|
const { closePopUp, openPopUp } = useContext(PopUpContext);
|
||||||
|
// any to avoid type conflict
|
||||||
const Icon = nodeIcons[data.type] || nodeIcons[types[data.type]];
|
const Icon: any =
|
||||||
|
nodeIconsLucide[data.type] || nodeIconsLucide[types[data.type]];
|
||||||
const [validationStatus, setValidationStatus] = useState(null);
|
const [validationStatus, setValidationStatus] = useState(null);
|
||||||
// State for outline color
|
// State for outline color
|
||||||
const { sseData } = useSSE();
|
const { sseData, isBuilding } = useSSE();
|
||||||
|
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
// if (reactFlowInstance) {
|
// if (reactFlowInstance) {
|
||||||
|
|
@ -88,8 +104,13 @@ export default function GenericNode({
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="ml-2 truncate">
|
<div className="ml-2 truncate">
|
||||||
<ShadTooltip delayDuration={1500} content={data.type}>
|
<ShadTooltip
|
||||||
<div className="ml-2 truncate text-foreground">{data.type}</div>
|
delayDuration={1500}
|
||||||
|
content={data.node.display_name}
|
||||||
|
>
|
||||||
|
<div className="ml-2 truncate text-gray-800">
|
||||||
|
{data.node.display_name}
|
||||||
|
</div>
|
||||||
</ShadTooltip>
|
</ShadTooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -137,10 +158,10 @@ export default function GenericNode({
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
!validationStatus
|
!validationStatus || isBuilding
|
||||||
? "w-4 h-4 rounded-full bg-status-yellow opacity-100"
|
? "w-4 h-4 rounded-full bg-yellow-500 opacity-100"
|
||||||
: "w-4 h-4 rounded-full bg-ring opacity-0 hidden animate-spin",
|
: "w-4 h-4 rounded-full bg-gray-500 opacity-0 hidden animate-spin",
|
||||||
"absolute w-4 hover:text-ring hover: transition-all ease-in-out duration-200"
|
"absolute w-4 hover:text-gray-500 hover:dark:text-gray-300 transition-all ease-in-out duration-200"
|
||||||
)}
|
)}
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,8 @@
|
||||||
import {
|
|
||||||
XCircleIcon,
|
|
||||||
XMarkIcon,
|
|
||||||
InformationCircleIcon,
|
|
||||||
CheckCircleIcon,
|
|
||||||
} from "@heroicons/react/24/outline";
|
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { SingleAlertComponentType } from "../../../../types/alerts";
|
import { SingleAlertComponentType } from "../../../../types/alerts";
|
||||||
|
import { X, CheckCircle2, Info, XCircle } from "lucide-react";
|
||||||
|
|
||||||
export default function SingleAlert({
|
export default function SingleAlert({
|
||||||
dropItem,
|
dropItem,
|
||||||
|
|
@ -34,8 +29,8 @@ export default function SingleAlert({
|
||||||
key={dropItem.id}
|
key={dropItem.id}
|
||||||
>
|
>
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<XCircleIcon
|
<XCircle
|
||||||
className="h-5 w-5 text-red-400"
|
className="h-5 w-5 text-red-400 dark:text-red-50"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -70,7 +65,7 @@ export default function SingleAlert({
|
||||||
className="inline-flex rounded-md bg-red-50 p-1.5 text-red-500"
|
className="inline-flex rounded-md bg-red-50 p-1.5 text-red-500"
|
||||||
>
|
>
|
||||||
<span className="sr-only">Dismiss</span>
|
<span className="sr-only">Dismiss</span>
|
||||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
<X className="h-5 w-5" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -81,8 +76,8 @@ export default function SingleAlert({
|
||||||
key={dropItem.id}
|
key={dropItem.id}
|
||||||
>
|
>
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<InformationCircleIcon
|
<Info
|
||||||
className="h-5 w-5 text-blue-400 "
|
className="h-5 w-5 text-blue-400 dark:text-blue-50"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -116,7 +111,7 @@ export default function SingleAlert({
|
||||||
className="inline-flex rounded-md bg-blue-50 p-1.5 text-blue-500 "
|
className="inline-flex rounded-md bg-blue-50 p-1.5 text-blue-500 "
|
||||||
>
|
>
|
||||||
<span className="sr-only">Dismiss</span>
|
<span className="sr-only">Dismiss</span>
|
||||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
<X className="h-5 w-5" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -127,8 +122,8 @@ export default function SingleAlert({
|
||||||
key={dropItem.id}
|
key={dropItem.id}
|
||||||
>
|
>
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<CheckCircleIcon
|
<CheckCircle2
|
||||||
className="h-5 w-5 text-green-400 "
|
className="h-5 w-5 text-green-400 dark:text-green-50"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -150,7 +145,7 @@ export default function SingleAlert({
|
||||||
className="inline-flex rounded-md bg-green-50 p-1.5 text-green-500 "
|
className="inline-flex rounded-md bg-green-50 p-1.5 text-green-500 "
|
||||||
>
|
>
|
||||||
<span className="sr-only">Dismiss</span>
|
<span className="sr-only">Dismiss</span>
|
||||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
<X className="h-5 w-5" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { useContext, useEffect, useRef } from "react";
|
import { useContext, useEffect, useRef } from "react";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
import { XMarkIcon } from "@heroicons/react/24/solid";
|
|
||||||
import { TrashIcon } from "@heroicons/react/24/outline";
|
|
||||||
import SingleAlert from "./components/singleAlertComponent";
|
import SingleAlert from "./components/singleAlertComponent";
|
||||||
import { AlertDropdownType } from "../../types/alerts";
|
import { AlertDropdownType } from "../../types/alerts";
|
||||||
import { PopUpContext } from "../../contexts/popUpContext";
|
import { PopUpContext } from "../../contexts/popUpContext";
|
||||||
import { useOnClickOutside } from "../hooks/useOnClickOutside";
|
import { useOnClickOutside } from "../hooks/useOnClickOutside";
|
||||||
|
import { X, Trash2 } from "lucide-react";
|
||||||
|
|
||||||
export default function AlertDropdown({}: AlertDropdownType) {
|
export default function AlertDropdown({}: AlertDropdownType) {
|
||||||
const { closePopUp } = useContext(PopUpContext);
|
const { closePopUp } = useContext(PopUpContext);
|
||||||
const componentRef = useRef<HTMLDivElement>(null);
|
const componentRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
@ -36,13 +36,13 @@ export default function AlertDropdown({}: AlertDropdownType) {
|
||||||
setTimeout(clearNotificationList, 100);
|
setTimeout(clearNotificationList, 100);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TrashIcon className="w-[1.1rem] h-[1.1rem]" />
|
<Trash2 className="w-[1.1rem] h-[1.1rem]" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="text-foreground hover:text-status-red"
|
className="text-foreground hover:text-status-red"
|
||||||
onClick={closePopUp}
|
onClick={closePopUp}
|
||||||
>
|
>
|
||||||
<XMarkIcon className="h-5 w-5" />
|
<X className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
import { XCircleIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { ErrorAlertType } from "../../types/alerts";
|
import { ErrorAlertType } from "../../types/alerts";
|
||||||
|
import { XCircle } from "lucide-react";
|
||||||
|
|
||||||
export default function ErrorAlert({
|
export default function ErrorAlert({
|
||||||
title,
|
title,
|
||||||
|
|
@ -43,8 +43,8 @@ export default function ErrorAlert({
|
||||||
>
|
>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<XCircleIcon
|
<XCircle
|
||||||
className="h-5 w-5 text-error-background"
|
className="h-5 w-5 text-red-400 dark:text-red-50"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
import { InformationCircleIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { NoticeAlertType } from "../../types/alerts";
|
import { NoticeAlertType } from "../../types/alerts";
|
||||||
|
import { Info } from "lucide-react";
|
||||||
|
|
||||||
export default function NoticeAlert({
|
export default function NoticeAlert({
|
||||||
title,
|
title,
|
||||||
|
|
@ -40,8 +40,8 @@ export default function NoticeAlert({
|
||||||
>
|
>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<InformationCircleIcon
|
<Info
|
||||||
className="h-5 w-5 text-medium-blue"
|
className="h-5 w-5 text-blue-400 dark:text-blue-50"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
import { CheckCircleIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { SuccessAlertType } from "../../types/alerts";
|
import { SuccessAlertType } from "../../types/alerts";
|
||||||
|
import { CheckCircle2 } from "lucide-react";
|
||||||
|
|
||||||
export default function SuccessAlert({
|
export default function SuccessAlert({
|
||||||
title,
|
title,
|
||||||
|
|
@ -38,8 +38,8 @@ export default function SuccessAlert({
|
||||||
>
|
>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<CheckCircleIcon
|
<CheckCircle2
|
||||||
className="h-5 w-5 text-status-green"
|
className="h-5 w-5 text-green-400 dark:text-green-50"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { Disclosure } from "@headlessui/react";
|
import { Disclosure } from "@headlessui/react";
|
||||||
import { ChevronLeftIcon } from "@heroicons/react/24/outline";
|
|
||||||
import { useContext, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { classNames } from "../../utils";
|
import { classNames } from "../../utils";
|
||||||
|
|
|
||||||
6
src/frontend/src/components/LoadingSpinner/index.tsx
Normal file
6
src/frontend/src/components/LoadingSpinner/index.tsx
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { useContext, useEffect, useRef, useState } from "react";
|
||||||
|
import { RadialProgressType } from "../../types/components";
|
||||||
|
|
||||||
|
export default function LoadingSpinner({}) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
21
src/frontend/src/components/ProgressBarComponent/index.tsx
Normal file
21
src/frontend/src/components/ProgressBarComponent/index.tsx
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { ReactElement, useContext, useEffect, useRef, useState } from "react";
|
||||||
|
import { ProgressBarType } from "../../types/components";
|
||||||
|
import { Progress } from "../../components/ui/progress";
|
||||||
|
import { progressContext } from "../../contexts/ProgressContext";
|
||||||
|
import { setInterval } from "timers/promises";
|
||||||
|
|
||||||
|
export default function ProgressBarComponent({
|
||||||
|
value,
|
||||||
|
children,
|
||||||
|
}: ProgressBarType) {
|
||||||
|
const ref = useRef(0);
|
||||||
|
const reff = useRef();
|
||||||
|
const { progress } = useContext(progressContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
ref.current = progress * 100;
|
||||||
|
console.log(progress);
|
||||||
|
}, [progress]);
|
||||||
|
|
||||||
|
return <Progress className="h-2.5" value={ref.current} />;
|
||||||
|
}
|
||||||
19
src/frontend/src/components/RadialProgress/index.tsx
Normal file
19
src/frontend/src/components/RadialProgress/index.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { useContext, useEffect, useRef, useState } from "react";
|
||||||
|
import { RadialProgressType } from "../../types/components";
|
||||||
|
|
||||||
|
export default function RadialProgressComponent({
|
||||||
|
value,
|
||||||
|
color,
|
||||||
|
}: RadialProgressType) {
|
||||||
|
const style = {
|
||||||
|
"--value": value * 100,
|
||||||
|
"--size": "1.5rem",
|
||||||
|
"--thickness": "2px",
|
||||||
|
} as React.CSSProperties;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={"radial-progress " + color} style={style}>
|
||||||
|
<strong className="text-[8px]">{Math.trunc(value * 100)}%</strong>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -29,13 +29,15 @@ export const CardComponent = ({
|
||||||
<Card className="group">
|
<Card className="group">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex w-full items-center gap-4">
|
<CardTitle className="flex w-full items-center gap-4">
|
||||||
<span
|
<span
|
||||||
className={
|
className={
|
||||||
"rounded-full w-7 h-7 flex items-center justify-center text-2xl " +
|
"rounded-full w-7 h-7 flex items-center justify-center text-2xl " +
|
||||||
gradients[parseInt(flow.id.slice(0, 12), 16) % gradients.length]
|
gradients[parseInt(flow.id.slice(0, 12), 16) % gradients.length]
|
||||||
}
|
}
|
||||||
></span>
|
></span>
|
||||||
<span className="flex-1 w-full inline-block truncate-doubleline break-words">{flow.name}</span>
|
<span className="flex-1 w-full inline-block truncate-doubleline break-words">
|
||||||
|
{flow.name}
|
||||||
|
</span>
|
||||||
{onDelete && (
|
{onDelete && (
|
||||||
<button className="flex self-start" onClick={onDelete}>
|
<button className="flex self-start" onClick={onDelete}>
|
||||||
<Trash2 className="w-4 h-4 text-primary opacity-0 group-hover:opacity-100 transition-all" />
|
<Trash2 className="w-4 h-4 text-primary opacity-0 group-hover:opacity-100 transition-all" />
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState, useContext } from "react";
|
import { useContext, useState } from "react";
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
import { Zap } from "lucide-react";
|
import { Zap } from "lucide-react";
|
||||||
import { validateNodes } from "../../../utils";
|
import { validateNodes } from "../../../utils";
|
||||||
|
|
@ -10,6 +10,8 @@ import { alertContext } from "../../../contexts/alertContext";
|
||||||
import { postBuildInit } from "../../../controllers/API";
|
import { postBuildInit } from "../../../controllers/API";
|
||||||
import ShadTooltip from "../../ShadTooltipComponent";
|
import ShadTooltip from "../../ShadTooltipComponent";
|
||||||
|
|
||||||
|
import RadialProgressComponent from "../../RadialProgress";
|
||||||
|
|
||||||
export default function BuildTrigger({
|
export default function BuildTrigger({
|
||||||
open,
|
open,
|
||||||
flow,
|
flow,
|
||||||
|
|
@ -21,11 +23,12 @@ export default function BuildTrigger({
|
||||||
setIsBuilt: any;
|
setIsBuilt: any;
|
||||||
isBuilt: boolean;
|
isBuilt: boolean;
|
||||||
}) {
|
}) {
|
||||||
const [isBuilding, setIsBuilding] = useState(false);
|
const { updateSSEData, isBuilding, setIsBuilding, sseData } = useSSE();
|
||||||
|
|
||||||
const { updateSSEData } = useSSE();
|
|
||||||
const { reactFlowInstance } = useContext(typesContext);
|
const { reactFlowInstance } = useContext(typesContext);
|
||||||
const { setErrorData } = useContext(alertContext);
|
const { setErrorData, setSuccessData } = useContext(alertContext);
|
||||||
|
const [isIconTouched, setIsIconTouched] = useState(false);
|
||||||
|
const eventClick = isBuilding ? "pointer-events-none" : "";
|
||||||
|
const [progress, setProgress] = useState(0);
|
||||||
|
|
||||||
async function handleBuild(flow: FlowType) {
|
async function handleBuild(flow: FlowType) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -47,10 +50,12 @@ export default function BuildTrigger({
|
||||||
const allNodesValid = await streamNodeData(flow);
|
const allNodesValid = await streamNodeData(flow);
|
||||||
await enforceMinimumLoadingTime(startTime, minimumLoadingTime);
|
await enforceMinimumLoadingTime(startTime, minimumLoadingTime);
|
||||||
setIsBuilt(allNodesValid);
|
setIsBuilt(allNodesValid);
|
||||||
if(!allNodesValid) {
|
if (!allNodesValid) {
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title: "Oops! Looks like you missed something",
|
title: "Oops! Looks like you missed something",
|
||||||
list: ["Check nodes and retry. Hover over 🔴 node for status."],
|
list: [
|
||||||
|
"Check components and retry. Hover over component status icon 🔴 to inspect.",
|
||||||
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -59,12 +64,10 @@ export default function BuildTrigger({
|
||||||
setIsBuilding(false);
|
setIsBuilding(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function streamNodeData(flow: FlowType) {
|
async function streamNodeData(flow: FlowType) {
|
||||||
// Step 1: Make a POST request to send the flow data and receive a unique session ID
|
// Step 1: Make a POST request to send the flow data and receive a unique session ID
|
||||||
const response = await postBuildInit(flow);
|
const response = await postBuildInit(flow);
|
||||||
const { flowId } = response.data;
|
const { flowId } = response.data;
|
||||||
|
|
||||||
// Step 2: Use the session ID to establish an SSE connection using EventSource
|
// Step 2: Use the session ID to establish an SSE connection using EventSource
|
||||||
let validationResults = [];
|
let validationResults = [];
|
||||||
let finished = false;
|
let finished = false;
|
||||||
|
|
@ -82,15 +85,25 @@ export default function BuildTrigger({
|
||||||
eventSource.close();
|
eventSource.close();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
} else if (parsedData.log) {
|
||||||
|
// If the event is a log, log it
|
||||||
|
setSuccessData({ title: parsedData.log });
|
||||||
|
} else {
|
||||||
|
// Otherwise, process the data
|
||||||
|
const isValid = processStreamResult(parsedData);
|
||||||
|
setProgress(parsedData.progress);
|
||||||
|
validationResults.push(isValid);
|
||||||
}
|
}
|
||||||
// Otherwise, process the data
|
|
||||||
const isValid = processStreamResult(parsedData);
|
|
||||||
validationResults.push(isValid);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
eventSource.onerror = (error) => {
|
eventSource.onerror = (error: any) => {
|
||||||
console.error("EventSource failed:", error);
|
console.error("EventSource failed:", error);
|
||||||
eventSource.close();
|
eventSource.close();
|
||||||
|
if (error.data) {
|
||||||
|
const parsedData = JSON.parse(error.data);
|
||||||
|
setErrorData({ title: parsedData.error });
|
||||||
|
setIsBuilding(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// Step 3: Wait for the stream to finish
|
// Step 3: Wait for the stream to finish
|
||||||
while (!finished) {
|
while (!finished) {
|
||||||
|
|
@ -124,6 +137,14 @@ export default function BuildTrigger({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleMouseEnter = () => {
|
||||||
|
setIsIconTouched(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
setIsIconTouched(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition
|
<Transition
|
||||||
show={!open}
|
show={!open}
|
||||||
|
|
@ -136,30 +157,30 @@ export default function BuildTrigger({
|
||||||
leaveTo="translate-y-96"
|
leaveTo="translate-y-96"
|
||||||
>
|
>
|
||||||
<div className={`fixed right-4` + (isBuilt ? " bottom-20" : " bottom-4")}>
|
<div className={`fixed right-4` + (isBuilt ? " bottom-20" : " bottom-4")}>
|
||||||
<ShadTooltip
|
<div
|
||||||
delayDuration={500}
|
className={`${eventClick} flex justify-center align-center py-1 px-3 w-12 h-12 rounded-full shadow-md shadow-[#0000002a] hover:shadow-[#00000032] bg-[#E2E7EE] dark:border-gray-600 cursor-pointer`}
|
||||||
content="Build Flow"
|
onClick={() => {
|
||||||
side="left"
|
handleBuild(flow);
|
||||||
|
}}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
>
|
>
|
||||||
<div
|
<button>
|
||||||
className="flex justify-center align-center py-1 px-3 w-12 h-12 rounded-full shadow-md hover:shadow-sm shadow-btn-shadow hover:shadow-btn-shadow
|
<div className="flex gap-3 items-center">
|
||||||
bg-buildBackground cursor-pointer"
|
{isBuilding && progress < 1 ? (
|
||||||
onClick={() => {
|
// Render your loading animation here when isBuilding is true
|
||||||
handleBuild(flow);
|
<RadialProgressComponent
|
||||||
}}
|
color={"text-orange-400"}
|
||||||
>
|
value={progress}
|
||||||
<button>
|
></RadialProgressComponent>
|
||||||
<div className="flex gap-3 items-center">
|
) : isBuilding ? (
|
||||||
{isBuilding ? (
|
<Loading strokeWidth={1.5} style={{ color: "#fb923c" }} />
|
||||||
// Render your loading animation here when isBuilding is true
|
) : (
|
||||||
<Loading strokeWidth={1.5} className="text-build" />
|
<Zap className="sh-6 w-6 fill-orange-400 stroke-1 stroke-orange-400" />
|
||||||
) : (
|
)}
|
||||||
<Zap className="sh-6 w-6 fill-build stroke-1 stroke-build"/>
|
</div>
|
||||||
)}
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</ShadTooltip>
|
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,9 @@
|
||||||
import {
|
|
||||||
ChatBubbleLeftEllipsisIcon,
|
|
||||||
ChatBubbleOvalLeftEllipsisIcon,
|
|
||||||
PlusSmallIcon,
|
|
||||||
} from "@heroicons/react/24/outline";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { ChatMessageType } from "../../../types/chat";
|
import { ChatMessageType } from "../../../types/chat";
|
||||||
import { nodeColors } from "../../../utils";
|
import { nodeColors } from "../../../utils";
|
||||||
import Convert from "ansi-to-html";
|
import Convert from "ansi-to-html";
|
||||||
const convert = new Convert({ newline: true });
|
const convert = new Convert({ newline: true });
|
||||||
|
import { MessageCircle } from "lucide-react";
|
||||||
|
|
||||||
export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
|
export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
|
||||||
const [hidden, setHidden] = useState(true);
|
const [hidden, setHidden] = useState(true);
|
||||||
|
|
@ -24,7 +20,7 @@ export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
|
||||||
onClick={() => setHidden((prev) => !prev)}
|
onClick={() => setHidden((prev) => !prev)}
|
||||||
className="absolute top-2 right-2 cursor-pointer"
|
className="absolute top-2 right-2 cursor-pointer"
|
||||||
>
|
>
|
||||||
<ChatBubbleOvalLeftEllipsisIcon className="w-5 h-5 animate-bounce" />
|
<MessageCircle className="w-5 h-5 animate-bounce" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{chat.thought && chat.thought !== "" && !hidden && (
|
{chat.thought && chat.thought !== "" && !hidden && (
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
import { MessagesSquare } from "lucide-react";
|
import { MessagesSquare } from "lucide-react";
|
||||||
|
|
||||||
import { alertContext } from "../../../contexts/alertContext";
|
import { alertContext } from "../../../contexts/alertContext";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import ShadTooltip from "../../ShadTooltipComponent";
|
import ShadTooltip from "../../ShadTooltipComponent";
|
||||||
|
|
@ -30,26 +31,21 @@ export default function ChatTrigger({ open, setOpen, isBuilt }) {
|
||||||
leaveTo="translate-y-96"
|
leaveTo="translate-y-96"
|
||||||
>
|
>
|
||||||
<div className="absolute bottom-4 right-3">
|
<div className="absolute bottom-4 right-3">
|
||||||
<ShadTooltip
|
<div
|
||||||
delayDuration={500}
|
className="flex justify-center align-center py-1 px-3 w-12 h-12 rounded-full shadow-md shadow-[#0000002a] hover:shadow-[#00000032]
|
||||||
content="Chat Interface"
|
bg-[#E2E7EE] dark:border-gray-600 cursor-pointer"
|
||||||
side="left"
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<div
|
<button>
|
||||||
className="border flex justify-center items-center py-1 px-3 w-12 h-12 rounded-full bg-almost-dark-blue cursor-pointer"
|
<div className="flex gap-3">
|
||||||
onClick={handleClick}
|
<MessagesSquare
|
||||||
>
|
className="pth-6 w-6 fill-[#5c8be1] stroke-1 stroke-[#5c8be1]"
|
||||||
<button>
|
style={{ color: "white" }}
|
||||||
<div className="flex gap-3">
|
strokeWidth={1.5}
|
||||||
<MessagesSquare
|
/>
|
||||||
className="h-6 w-6 text-medium-light-blue fill-medium-light-blue"
|
</div>
|
||||||
style={{ color: "white" }}
|
</button>
|
||||||
strokeWidth={1.5}
|
</div>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</ShadTooltip>
|
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@ export default function Chat({ flow }: ChatType) {
|
||||||
const handleKeyDown = (event: KeyboardEvent) => {
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
if (
|
if (
|
||||||
(event.key === "K" || event.key === "k") &&
|
(event.key === "K" || event.key === "k") &&
|
||||||
(event.metaKey || event.ctrlKey)
|
(event.metaKey || event.ctrlKey) &&
|
||||||
|
isBuilt
|
||||||
) {
|
) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setOpen((oldState) => !oldState);
|
setOpen((oldState) => !oldState);
|
||||||
|
|
@ -26,7 +27,7 @@ export default function Chat({ flow }: ChatType) {
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("keydown", handleKeyDown);
|
document.removeEventListener("keydown", handleKeyDown);
|
||||||
};
|
};
|
||||||
}, []);
|
}, [isBuilt]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Define an async function within the useEffect hook
|
// Define an async function within the useEffect hook
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline";
|
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { PopUpContext } from "../../contexts/popUpContext";
|
import { PopUpContext } from "../../contexts/popUpContext";
|
||||||
import CodeAreaModal from "../../modals/codeAreaModal";
|
import CodeAreaModal from "../../modals/codeAreaModal";
|
||||||
import TextAreaModal from "../../modals/textAreaModal";
|
import TextAreaModal from "../../modals/textAreaModal";
|
||||||
import { TextAreaComponentType } from "../../types/components";
|
import { TextAreaComponentType } from "../../types/components";
|
||||||
import { INPUT_STYLE } from "../../constants";
|
import { INPUT_STYLE } from "../../constants";
|
||||||
|
import { ExternalLink } from "lucide-react";
|
||||||
|
|
||||||
export default function CodeAreaComponent({
|
export default function CodeAreaComponent({
|
||||||
value,
|
value,
|
||||||
|
|
@ -69,7 +69,7 @@ export default function CodeAreaComponent({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!editNode && (
|
{!editNode && (
|
||||||
<ArrowTopRightOnSquareIcon className="w-6 h-6 hover:text-ring" />
|
<ExternalLink className="w-6 h-6 hover:text-ring dark:text-gray-300 ml-3" />
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { Listbox, Transition } from "@headlessui/react";
|
import { Listbox, Transition } from "@headlessui/react";
|
||||||
import { ChevronUpDownIcon, CheckIcon } from "@heroicons/react/24/outline";
|
import { Fragment, useEffect, useState } from "react";
|
||||||
import { Fragment, useState } from "react";
|
|
||||||
import { DropDownComponentType } from "../../types/components";
|
import { DropDownComponentType } from "../../types/components";
|
||||||
import { classNames } from "../../utils";
|
import { classNames } from "../../utils";
|
||||||
import { INPUT_STYLE } from "../../constants";
|
import { INPUT_STYLE } from "../../constants";
|
||||||
|
import { ChevronsUpDown, Check } from "lucide-react";
|
||||||
|
|
||||||
export default function Dropdown({
|
export default function Dropdown({
|
||||||
value,
|
value,
|
||||||
|
|
@ -15,6 +15,9 @@ export default function Dropdown({
|
||||||
let [internalValue, setInternalValue] = useState(
|
let [internalValue, setInternalValue] = useState(
|
||||||
value === "" || !value ? "Choose an option" : value
|
value === "" || !value ? "Choose an option" : value
|
||||||
);
|
);
|
||||||
|
useEffect(() => {
|
||||||
|
setInternalValue(value === "" || !value ? "Choose an option" : value);
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -43,8 +46,8 @@ export default function Dropdown({
|
||||||
"pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
|
"pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ChevronUpDownIcon
|
<ChevronsUpDown
|
||||||
className="h-5 w-5 text-ring"
|
className="h-5 w-5 text-gray-400"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -97,7 +100,7 @@ export default function Dropdown({
|
||||||
"absolute inset-y-0 right-0 flex items-center pr-4"
|
"absolute inset-y-0 right-0 flex items-center pr-4"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<CheckIcon
|
<Check
|
||||||
className={
|
className={
|
||||||
active
|
active
|
||||||
? "h-5 w-5 text-black"
|
? "h-5 w-5 text-black"
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export const MenuBar = ({ flows, tabId }) => {
|
||||||
</Link>
|
</Link>
|
||||||
<div className="flex items-center font-medium text-sm rounded-md py-1 px-1.5 gap-0.5">
|
<div className="flex items-center font-medium text-sm rounded-md py-1 px-1.5 gap-0.5">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
className="gap-2 flex items-center max-w-[200px]"
|
className="gap-2 flex items-center max-w-[200px]"
|
||||||
variant="primary"
|
variant="primary"
|
||||||
|
|
@ -64,11 +64,21 @@ export const MenuBar = ({ flows, tabId }) => {
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="w-44">
|
<DropdownMenuContent className="w-44">
|
||||||
<DropdownMenuLabel>Edit</DropdownMenuLabel>
|
<DropdownMenuLabel>Options</DropdownMenuLabel>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => {
|
||||||
|
handleAddFlow();
|
||||||
|
}}
|
||||||
|
className="cursor-pointer"
|
||||||
|
>
|
||||||
|
<Plus className="w-4 h-4 mr-2" />
|
||||||
|
New
|
||||||
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
openPopUp(<FlowSettingsModal />);
|
openPopUp(<FlowSettingsModal />);
|
||||||
}}
|
}}
|
||||||
|
className="cursor-pointer"
|
||||||
>
|
>
|
||||||
<Settings2 className="w-4 h-4 mr-2 " />
|
<Settings2 className="w-4 h-4 mr-2 " />
|
||||||
Settings
|
Settings
|
||||||
|
|
@ -77,6 +87,7 @@ export const MenuBar = ({ flows, tabId }) => {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
undo();
|
undo();
|
||||||
}}
|
}}
|
||||||
|
className="cursor-pointer"
|
||||||
>
|
>
|
||||||
<Undo className="w-4 h-4 mr-2 " />
|
<Undo className="w-4 h-4 mr-2 " />
|
||||||
Undo
|
Undo
|
||||||
|
|
@ -85,13 +96,14 @@ export const MenuBar = ({ flows, tabId }) => {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
redo();
|
redo();
|
||||||
}}
|
}}
|
||||||
|
className="cursor-pointer"
|
||||||
>
|
>
|
||||||
<Redo className="w-4 h-4 mr-2 " />
|
<Redo className="w-4 h-4 mr-2 " />
|
||||||
Redo
|
Redo
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuLabel>Projects</DropdownMenuLabel>
|
{/* <DropdownMenuLabel>Projects</DropdownMenuLabel> */}
|
||||||
<DropdownMenuRadioGroup
|
{/* <DropdownMenuRadioGroup className="max-h-full overflow-scroll"
|
||||||
value={tabId}
|
value={tabId}
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
setTabId(value);
|
setTabId(value);
|
||||||
|
|
@ -99,22 +111,20 @@ export const MenuBar = ({ flows, tabId }) => {
|
||||||
>
|
>
|
||||||
{flows.map((flow, idx) => {
|
{flows.map((flow, idx) => {
|
||||||
return (
|
return (
|
||||||
<Link to={"/flow/" + flow.id} className="flex w-full items-center">
|
<Link
|
||||||
<DropdownMenuRadioItem value={flow.id} className="flex-1 w-full inline-block truncate break-words mr-2">
|
to={"/flow/" + flow.id}
|
||||||
|
className="flex w-full items-center"
|
||||||
|
>
|
||||||
|
<DropdownMenuRadioItem
|
||||||
|
value={flow.id}
|
||||||
|
className="flex-1 w-full inline-block truncate break-words mr-2"
|
||||||
|
>
|
||||||
{flow.name}
|
{flow.name}
|
||||||
</DropdownMenuRadioItem>
|
</DropdownMenuRadioItem>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</DropdownMenuRadioGroup>
|
</DropdownMenuRadioGroup> */}
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => {
|
|
||||||
handleAddFlow();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Plus className="w-4 h-4 mr-2" />
|
|
||||||
New Project
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { SunIcon, MoonIcon, BellIcon, Home, Users2 } from "lucide-react";
|
import { BellIcon, Home, MoonIcon, SunIcon, Users2 } from "lucide-react";
|
||||||
import { useContext, useState, useEffect } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { FaGithub } from "react-icons/fa";
|
import { FaDiscord, FaGithub, FaTwitter } from "react-icons/fa";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { TabsContext } from "../../contexts/tabsContext";
|
import { TabsContext } from "../../contexts/tabsContext";
|
||||||
import AlertDropdown from "../../alerts/alertDropDown";
|
import AlertDropdown from "../../alerts/alertDropDown";
|
||||||
|
|
@ -10,6 +10,10 @@ import { PopUpContext } from "../../contexts/popUpContext";
|
||||||
import { typesContext } from "../../contexts/typesContext";
|
import { typesContext } from "../../contexts/typesContext";
|
||||||
import MenuBar from "./components/menuBar";
|
import MenuBar from "./components/menuBar";
|
||||||
import { Link, useLocation, useParams } from "react-router-dom";
|
import { Link, useLocation, useParams } from "react-router-dom";
|
||||||
|
import { USER_PROJECTS_HEADER } from "../../constants";
|
||||||
|
import { getRepoStars } from "../../controllers/API";
|
||||||
|
import { Separator } from "../ui/separator";
|
||||||
|
import { Bell } from "lucide-react";
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
const { flows, addFlow, tabId } = useContext(TabsContext);
|
const { flows, addFlow, tabId } = useContext(TabsContext);
|
||||||
|
|
@ -21,6 +25,16 @@ export default function Header() {
|
||||||
const { notificationCenter, setNotificationCenter, setErrorData } =
|
const { notificationCenter, setNotificationCenter, setErrorData } =
|
||||||
useContext(alertContext);
|
useContext(alertContext);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
|
const [stars, setStars] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function fetchStars() {
|
||||||
|
const starsCount = await getRepoStars("logspace-ai", "langflow");
|
||||||
|
setStars(starsCount);
|
||||||
|
}
|
||||||
|
fetchStars();
|
||||||
|
}, []);
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-12 flex justify-between items-center border-b bg-muted">
|
<div className="w-full h-12 flex justify-between items-center border-b bg-muted">
|
||||||
<div className="flex gap-2 justify-start items-center w-96">
|
<div className="flex gap-2 justify-start items-center w-96">
|
||||||
|
|
@ -39,7 +53,7 @@ export default function Header() {
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
<Home className="w-4 h-4" />
|
<Home className="w-4 h-4" />
|
||||||
<div className="flex-1">My Projects</div>
|
<div className="flex-1">{USER_PROJECTS_HEADER}</div>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/community">
|
<Link to="/community">
|
||||||
|
|
@ -56,24 +70,38 @@ export default function Header() {
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end px-2 w-96">
|
<div className="flex justify-end px-2 w-96">
|
||||||
<div className="ml-auto mr-2 flex gap-5">
|
<div className="ml-auto mr-2 flex gap-5 items-center">
|
||||||
<Button
|
<a
|
||||||
asChild
|
href="https://github.com/logspace-ai/langflow"
|
||||||
variant="outline"
|
target="_blank"
|
||||||
className="text-muted-foreground "
|
rel="noreferrer"
|
||||||
|
className="inline-flex shadow-sm items-center justify-center text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background text-gray-600 dark:text-gray-300 border border-input hover:bg-accent hover:text-accent-foreground h-9 px-3 pr-0 rounded-md"
|
||||||
>
|
>
|
||||||
<a
|
<FaGithub className="h-5 w-5 mr-2" />
|
||||||
href="https://github.com/logspace-ai/langflow"
|
Star
|
||||||
target="_blank"
|
<div className="ml-2 flex text-sm bg-background rounded-md rounded-l-none border px-2 h-9 -mr-px items-center justify-center">
|
||||||
rel="noreferrer"
|
{stars}
|
||||||
className="flex"
|
</div>
|
||||||
>
|
</a>
|
||||||
<FaGithub className="h-5 w-5 mr-2" />
|
<a
|
||||||
Join The Community
|
href="https://twitter.com/logspace_ai"
|
||||||
</a>
|
target="_blank"
|
||||||
</Button>
|
rel="noreferrer"
|
||||||
|
className="text-muted-foreground"
|
||||||
|
>
|
||||||
|
<FaTwitter className="h-5 w-5" />
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://discord.gg/EqksyE2EX9"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-muted-foreground"
|
||||||
|
>
|
||||||
|
<FaDiscord className="h-5 w-5" />
|
||||||
|
</a>
|
||||||
|
{/* <Separator orientation="vertical" />
|
||||||
<button
|
<button
|
||||||
className="text-gray-600 hover:text-gray-500"
|
className="text-gray-600 hover:text-gray-500 dark:text-gray-300 dark:hover:text-gray-200"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDark(!dark);
|
setDark(!dark);
|
||||||
}}
|
}}
|
||||||
|
|
@ -107,14 +135,8 @@ export default function Header() {
|
||||||
{notificationCenter && (
|
{notificationCenter && (
|
||||||
<div className="absolute w-1.5 h-1.5 rounded-full bg-destructive right-[3px]"></div>
|
<div className="absolute w-1.5 h-1.5 rounded-full bg-destructive right-[3px]"></div>
|
||||||
)}
|
)}
|
||||||
<BellIcon className="h-5 w-5" aria-hidden="true" />
|
<Bell className="h-5 w-5" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
{/* <button>
|
|
||||||
<img
|
|
||||||
src="https://github.com/shadcn.png"
|
|
||||||
className="rounded-full w-8"
|
|
||||||
/>
|
|
||||||
</button> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import { DocumentMagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
import { FileComponentType } from "../../types/components";
|
import { FileComponentType } from "../../types/components";
|
||||||
|
import { TabsContext } from "../../contexts/tabsContext";
|
||||||
import { INPUT_STYLE } from "../../constants";
|
import { INPUT_STYLE } from "../../constants";
|
||||||
|
import { FileSearch2 } from "lucide-react";
|
||||||
|
import { uploadFile } from "../../controllers/API";
|
||||||
|
|
||||||
export default function InputFileComponent({
|
export default function InputFileComponent({
|
||||||
value,
|
value,
|
||||||
|
|
@ -14,7 +16,9 @@ export default function InputFileComponent({
|
||||||
editNode = false,
|
editNode = false,
|
||||||
}: FileComponentType) {
|
}: FileComponentType) {
|
||||||
const [myValue, setMyValue] = useState(value);
|
const [myValue, setMyValue] = useState(value);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
const { setErrorData } = useContext(alertContext);
|
const { setErrorData } = useContext(alertContext);
|
||||||
|
const { tabId } = useContext(TabsContext);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
setMyValue("");
|
setMyValue("");
|
||||||
|
|
@ -23,12 +27,6 @@ export default function InputFileComponent({
|
||||||
}
|
}
|
||||||
}, [disabled, onChange]);
|
}, [disabled, onChange]);
|
||||||
|
|
||||||
function attachFile(fileReadEvent: ProgressEvent<FileReader>) {
|
|
||||||
fileReadEvent.preventDefault();
|
|
||||||
const file = fileReadEvent.target.result;
|
|
||||||
onFileChange(file as string);
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkFileType(fileName: string): boolean {
|
function checkFileType(fileName: string): boolean {
|
||||||
for (let index = 0; index < suffixes.length; index++) {
|
for (let index = 0; index < suffixes.length; index++) {
|
||||||
if (fileName.endsWith(suffixes[index])) {
|
if (fileName.endsWith(suffixes[index])) {
|
||||||
|
|
@ -43,27 +41,54 @@ export default function InputFileComponent({
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
const handleButtonClick = () => {
|
const handleButtonClick = () => {
|
||||||
|
// Create a file input element
|
||||||
const input = document.createElement("input");
|
const input = document.createElement("input");
|
||||||
input.type = "file";
|
input.type = "file";
|
||||||
input.accept = suffixes.join(",");
|
input.accept = suffixes.join(",");
|
||||||
input.style.display = "none";
|
input.style.display = "none"; // Hidden from view
|
||||||
input.multiple = false;
|
input.multiple = false; // Allow only one file selection
|
||||||
|
|
||||||
input.onchange = (e: Event) => {
|
input.onchange = (e: Event) => {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
// Get the selected file
|
||||||
const file = (e.target as HTMLInputElement).files?.[0];
|
const file = (e.target as HTMLInputElement).files?.[0];
|
||||||
const fileData = new FileReader();
|
|
||||||
fileData.onload = attachFile;
|
// Check if the file type is correct
|
||||||
if (file && checkFileType(file.name)) {
|
if (file && checkFileType(file.name)) {
|
||||||
fileData.readAsDataURL(file);
|
// Upload the file
|
||||||
setMyValue(file.name);
|
uploadFile(file, tabId)
|
||||||
onChange(file.name);
|
.then((res) => res.data)
|
||||||
|
.then((data) => {
|
||||||
|
console.log("File uploaded successfully");
|
||||||
|
// Get the file name from the response
|
||||||
|
const { file_path } = data;
|
||||||
|
console.log("File name:", file_path);
|
||||||
|
|
||||||
|
// Update the state and callback with the name of the file
|
||||||
|
// sets the value to the user
|
||||||
|
setMyValue(file.name);
|
||||||
|
onChange(file.name);
|
||||||
|
// sets the value that goes to the backend
|
||||||
|
onFileChange(file_path);
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.error("Error occurred while uploading file");
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
|
// Show an error if the file type is not allowed
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title:
|
title:
|
||||||
"Please select a valid file. Only files this files are allowed:",
|
"Please select a valid file. Only these file types are allowed:",
|
||||||
list: fileTypes,
|
list: fileTypes,
|
||||||
});
|
});
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Trigger the file selection dialog
|
||||||
input.click();
|
input.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -73,7 +98,7 @@ export default function InputFileComponent({
|
||||||
disabled ? "pointer-events-none cursor-not-allowed w-full" : "w-full"
|
disabled ? "pointer-events-none cursor-not-allowed w-full" : "w-full"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="w-full flex items-center gap-3">
|
<div className="w-full flex items-center gap-2">
|
||||||
<span
|
<span
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
className={
|
className={
|
||||||
|
|
@ -88,8 +113,11 @@ export default function InputFileComponent({
|
||||||
{myValue !== "" ? myValue : "No file"}
|
{myValue !== "" ? myValue : "No file"}
|
||||||
</span>
|
</span>
|
||||||
<button onClick={handleButtonClick}>
|
<button onClick={handleButtonClick}>
|
||||||
{!editNode && (
|
{!editNode && !loading && (
|
||||||
<DocumentMagnifyingGlassIcon className="w-8 h-8 hover:text-ring" />
|
<FileSearch2 className="w-6 h-6 hover:text-ring" />
|
||||||
|
)}
|
||||||
|
{!editNode && loading && (
|
||||||
|
<span className="loading loading-spinner loading-sm pl-3 h-8 pointer-events-none"></span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { InputListComponentType } from "../../types/components";
|
import { InputListComponentType } from "../../types/components";
|
||||||
import { TabsContext } from "../../contexts/tabsContext";
|
import { TabsContext } from "../../contexts/tabsContext";
|
||||||
|
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { INPUT_STYLE } from "../../constants";
|
import { INPUT_STYLE } from "../../constants";
|
||||||
|
import { X, Plus } from "lucide-react";
|
||||||
|
|
||||||
export default function InputListComponent({
|
export default function InputListComponent({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
|
|
@ -59,7 +60,7 @@ export default function InputListComponent({
|
||||||
onChange(inputList);
|
onChange(inputList);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PlusIcon className={"w-4 h-4 hover:text-ring"} />
|
<Plus className={"w-4 h-4 hover:text-ring"} />
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
|
|
@ -72,7 +73,7 @@ export default function InputListComponent({
|
||||||
onChange(inputList);
|
onChange(inputList);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<XMarkIcon className="w-4 h-4 hover:text-destructive" />
|
<X className="w-4 h-4 hover:text-red-600" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline";
|
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { PopUpContext } from "../../contexts/popUpContext";
|
import { PopUpContext } from "../../contexts/popUpContext";
|
||||||
import { TextAreaComponentType } from "../../types/components";
|
import { TextAreaComponentType } from "../../types/components";
|
||||||
import GenericModal from "../../modals/genericModal";
|
import GenericModal from "../../modals/genericModal";
|
||||||
import { TypeModal } from "../../utils";
|
import { TypeModal } from "../../utils";
|
||||||
import { INPUT_STYLE } from "../../constants";
|
import { INPUT_STYLE } from "../../constants";
|
||||||
|
import { ExternalLink } from "lucide-react";
|
||||||
|
|
||||||
export default function PromptAreaComponent({
|
export default function PromptAreaComponent({
|
||||||
value,
|
value,
|
||||||
|
|
@ -74,7 +74,7 @@ export default function PromptAreaComponent({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!editNode && (
|
{!editNode && (
|
||||||
<ArrowTopRightOnSquareIcon className="w-6 h-6 hover:text-ring " />
|
<ExternalLink className="w-6 h-6 hover:text-ring dark:text-gray-300" />
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline";
|
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { PopUpContext } from "../../contexts/popUpContext";
|
import { PopUpContext } from "../../contexts/popUpContext";
|
||||||
import { TextAreaComponentType } from "../../types/components";
|
import { TextAreaComponentType } from "../../types/components";
|
||||||
import GenericModal from "../../modals/genericModal";
|
import GenericModal from "../../modals/genericModal";
|
||||||
import { TypeModal } from "../../utils";
|
import { TypeModal } from "../../utils";
|
||||||
import { INPUT_STYLE } from "../../constants";
|
import { INPUT_STYLE } from "../../constants";
|
||||||
|
import { ExternalLink } from "lucide-react";
|
||||||
|
|
||||||
export default function TextAreaComponent({
|
export default function TextAreaComponent({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
|
|
@ -76,7 +77,7 @@ export default function TextAreaComponent({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!editNode && (
|
{!editNode && (
|
||||||
<ArrowTopRightOnSquareIcon className="w-6 h-6 hover:text-ring " />
|
<ExternalLink className="w-6 h-6 hover:text-ring dark:text-gray-300" />
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ const CardDescription = React.forwardRef<
|
||||||
HTMLParagraphElement,
|
HTMLParagraphElement,
|
||||||
React.HTMLAttributes<HTMLParagraphElement>
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<p
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("text-sm text-muted-foreground", className)}
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
27
src/frontend/src/components/ui/progress.tsx
Normal file
27
src/frontend/src/components/ui/progress.tsx
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import * as ProgressPrimitive from "@radix-ui/react-progress";
|
||||||
|
import { cn } from "../../utils";
|
||||||
|
|
||||||
|
const Progress = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ProgressPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
|
||||||
|
>(({ className, value, ...props }, ref) => (
|
||||||
|
<ProgressPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative h-4 w-full overflow-hidden rounded-full bg-secondary",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ProgressPrimitive.Indicator
|
||||||
|
className="h-full w-full flex-1 bg-primary transition-all"
|
||||||
|
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
||||||
|
/>
|
||||||
|
</ProgressPrimitive.Root>
|
||||||
|
));
|
||||||
|
Progress.displayName = ProgressPrimitive.Root.displayName;
|
||||||
|
|
||||||
|
export { Progress };
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
||||||
import { cn } from "../../utils"
|
import { cn } from "../../utils";
|
||||||
|
|
||||||
const Tabs = TabsPrimitive.Root
|
const Tabs = TabsPrimitive.Root;
|
||||||
|
|
||||||
const TabsList = React.forwardRef<
|
const TabsList = React.forwardRef<
|
||||||
React.ElementRef<typeof TabsPrimitive.List>,
|
React.ElementRef<typeof TabsPrimitive.List>,
|
||||||
|
|
@ -18,8 +18,8 @@ const TabsList = React.forwardRef<
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
TabsList.displayName = TabsPrimitive.List.displayName
|
TabsList.displayName = TabsPrimitive.List.displayName;
|
||||||
|
|
||||||
const TabsTrigger = React.forwardRef<
|
const TabsTrigger = React.forwardRef<
|
||||||
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||||
|
|
@ -33,8 +33,8 @@ const TabsTrigger = React.forwardRef<
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
||||||
|
|
||||||
const TabsContent = React.forwardRef<
|
const TabsContent = React.forwardRef<
|
||||||
React.ElementRef<typeof TabsPrimitive.Content>,
|
React.ElementRef<typeof TabsPrimitive.Content>,
|
||||||
|
|
@ -48,7 +48,7 @@ const TabsContent = React.forwardRef<
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
TabsContent.displayName = TabsPrimitive.Content.displayName
|
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
||||||
|
|
||||||
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ export const getPythonApiCode = (flow: FlowType): string => {
|
||||||
|
|
||||||
BASE_API_URL = "${window.location.protocol}//${
|
BASE_API_URL = "${window.location.protocol}//${
|
||||||
window.location.host
|
window.location.host
|
||||||
}/ap1/v1/predict"
|
}/api/v1/process"
|
||||||
FLOW_ID = "${flowId}"
|
FLOW_ID = "${flowId}"
|
||||||
# You can tweak the flow by adding a tweaks dictionary
|
# You can tweak the flow by adding a tweaks dictionary
|
||||||
# e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}}
|
# e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}}
|
||||||
|
|
@ -83,7 +83,7 @@ def run_flow(message: str, flow_id: str, tweaks: dict = None) -> dict:
|
||||||
"""
|
"""
|
||||||
api_url = f"{BASE_API_URL}/{flow_id}"
|
api_url = f"{BASE_API_URL}/{flow_id}"
|
||||||
|
|
||||||
payload = {"message": message}
|
payload = {"inputs": {"input": message}}
|
||||||
|
|
||||||
if tweaks:
|
if tweaks:
|
||||||
payload["tweaks"] = tweaks
|
payload["tweaks"] = tweaks
|
||||||
|
|
@ -106,9 +106,9 @@ export const getCurlCode = (flow: FlowType): string => {
|
||||||
return `curl -X POST \\
|
return `curl -X POST \\
|
||||||
${window.location.protocol}//${
|
${window.location.protocol}//${
|
||||||
window.location.host
|
window.location.host
|
||||||
}/api/v1/predict/${flowId} \\
|
}/api/v1/process/${flowId} \\
|
||||||
-H 'Content-Type: application/json' \\
|
-H 'Content-Type: application/json' \\
|
||||||
-d '{"message": "Your message", "tweaks": ${JSON.stringify(
|
-d '{"inputs": {"input": message}, "tweaks": ${JSON.stringify(
|
||||||
tweaks,
|
tweaks,
|
||||||
null,
|
null,
|
||||||
2
|
2
|
||||||
|
|
@ -121,11 +121,12 @@ export const getCurlCode = (flow: FlowType): string => {
|
||||||
*/
|
*/
|
||||||
export const getPythonCode = (flow: FlowType): string => {
|
export const getPythonCode = (flow: FlowType): string => {
|
||||||
const flowName = flow.name;
|
const flowName = flow.name;
|
||||||
|
const tweaks = buildTweaks(flow);
|
||||||
return `from langflow import load_flow_from_json
|
return `from langflow import load_flow_from_json
|
||||||
|
TWEAKS = ${JSON.stringify(tweaks, null, 2)}
|
||||||
flow = load_flow_from_json("${flowName}.json")
|
flow = load_flow_from_json("${flowName}.json", tweaks=TWEAKS)
|
||||||
# Now you can use it like any chain
|
# Now you can use it like any chain
|
||||||
flow("Hey, have you heard of LangFlow?")`;
|
flow("Hey, have you heard of LangFlow?")`;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -170,3 +171,262 @@ export const NAV_DISPLAY_STYLE =
|
||||||
*/
|
*/
|
||||||
export const BUTTON_DIV_STYLE =
|
export const BUTTON_DIV_STYLE =
|
||||||
" flex gap-2 ";
|
" flex gap-2 ";
|
||||||
|
" focus:ring-1 focus:ring-offset-1 focus:ring-ring focus:outline-none ";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default description for the flow
|
||||||
|
* @constant
|
||||||
|
*/
|
||||||
|
export const DESCRIPTIONS: string[] = [
|
||||||
|
"Chain the Words, Master Language!",
|
||||||
|
"Language Architect at Work!",
|
||||||
|
"Empowering Language Engineering.",
|
||||||
|
"Craft Language Connections Here.",
|
||||||
|
"Create, Connect, Converse.",
|
||||||
|
"Smart Chains, Smarter Conversations.",
|
||||||
|
"Bridging Prompts for Brilliance.",
|
||||||
|
"Language Models, Unleashed.",
|
||||||
|
"Your Hub for Text Generation.",
|
||||||
|
"Promptly Ingenious!",
|
||||||
|
"Building Linguistic Labyrinths.",
|
||||||
|
"LangFlow: Create, Chain, Communicate.",
|
||||||
|
"Connect the Dots, Craft Language.",
|
||||||
|
"Interactive Language Weaving.",
|
||||||
|
"Generate, Innovate, Communicate.",
|
||||||
|
"Conversation Catalyst Engine.",
|
||||||
|
"Language Chainlink Master.",
|
||||||
|
"Design Dialogues with LangFlow.",
|
||||||
|
"Nurture NLP Nodes Here.",
|
||||||
|
"Conversational Cartography Unlocked.",
|
||||||
|
"Design, Develop, Dialogize.",
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjectives for the name of the flow
|
||||||
|
* @constant
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const ADJECTIVES: string[] = [
|
||||||
|
"admiring",
|
||||||
|
"adoring",
|
||||||
|
"agitated",
|
||||||
|
"amazing",
|
||||||
|
"angry",
|
||||||
|
"awesome",
|
||||||
|
"backstabbing",
|
||||||
|
"berserk",
|
||||||
|
"big",
|
||||||
|
"boring",
|
||||||
|
"clever",
|
||||||
|
"cocky",
|
||||||
|
"compassionate",
|
||||||
|
"condescending",
|
||||||
|
"cranky",
|
||||||
|
"desperate",
|
||||||
|
"determined",
|
||||||
|
"distracted",
|
||||||
|
"dreamy",
|
||||||
|
"drunk",
|
||||||
|
"ecstatic",
|
||||||
|
"elated",
|
||||||
|
"elegant",
|
||||||
|
"evil",
|
||||||
|
"fervent",
|
||||||
|
"focused",
|
||||||
|
"furious",
|
||||||
|
"gigantic",
|
||||||
|
"gloomy",
|
||||||
|
"goofy",
|
||||||
|
"grave",
|
||||||
|
"happy",
|
||||||
|
"high",
|
||||||
|
"hopeful",
|
||||||
|
"hungry",
|
||||||
|
"insane",
|
||||||
|
"jolly",
|
||||||
|
"jovial",
|
||||||
|
"kickass",
|
||||||
|
"lonely",
|
||||||
|
"loving",
|
||||||
|
"mad",
|
||||||
|
"modest",
|
||||||
|
"naughty",
|
||||||
|
"nauseous",
|
||||||
|
"nostalgic",
|
||||||
|
"pedantic",
|
||||||
|
"pensive",
|
||||||
|
"prickly",
|
||||||
|
"reverent",
|
||||||
|
"romantic",
|
||||||
|
"sad",
|
||||||
|
"serene",
|
||||||
|
"sharp",
|
||||||
|
"sick",
|
||||||
|
"silly",
|
||||||
|
"sleepy",
|
||||||
|
"small",
|
||||||
|
"stoic",
|
||||||
|
"stupefied",
|
||||||
|
"suspicious",
|
||||||
|
"tender",
|
||||||
|
"thirsty",
|
||||||
|
"tiny",
|
||||||
|
"trusting",
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nouns for the name of the flow
|
||||||
|
* @constant
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const NOUNS: string[] = [
|
||||||
|
"albattani",
|
||||||
|
"allen",
|
||||||
|
"almeida",
|
||||||
|
"archimedes",
|
||||||
|
"ardinghelli",
|
||||||
|
"aryabhata",
|
||||||
|
"austin",
|
||||||
|
"babbage",
|
||||||
|
"banach",
|
||||||
|
"bardeen",
|
||||||
|
"bartik",
|
||||||
|
"bassi",
|
||||||
|
"bell",
|
||||||
|
"bhabha",
|
||||||
|
"bhaskara",
|
||||||
|
"blackwell",
|
||||||
|
"bohr",
|
||||||
|
"booth",
|
||||||
|
"borg",
|
||||||
|
"bose",
|
||||||
|
"boyd",
|
||||||
|
"brahmagupta",
|
||||||
|
"brattain",
|
||||||
|
"brown",
|
||||||
|
"carson",
|
||||||
|
"chandrasekhar",
|
||||||
|
"colden",
|
||||||
|
"cori",
|
||||||
|
"cray",
|
||||||
|
"curie",
|
||||||
|
"darwin",
|
||||||
|
"davinci",
|
||||||
|
"dijkstra",
|
||||||
|
"dubinsky",
|
||||||
|
"easley",
|
||||||
|
"einstein",
|
||||||
|
"elion",
|
||||||
|
"engelbart",
|
||||||
|
"euclid",
|
||||||
|
"euler",
|
||||||
|
"fermat",
|
||||||
|
"fermi",
|
||||||
|
"feynman",
|
||||||
|
"franklin",
|
||||||
|
"galileo",
|
||||||
|
"gates",
|
||||||
|
"goldberg",
|
||||||
|
"goldstine",
|
||||||
|
"goldwasser",
|
||||||
|
"golick",
|
||||||
|
"goodall",
|
||||||
|
"hamilton",
|
||||||
|
"hawking",
|
||||||
|
"heisenberg",
|
||||||
|
"heyrovsky",
|
||||||
|
"hodgkin",
|
||||||
|
"hoover",
|
||||||
|
"hopper",
|
||||||
|
"hugle",
|
||||||
|
"hypatia",
|
||||||
|
"jang",
|
||||||
|
"jennings",
|
||||||
|
"jepsen",
|
||||||
|
"joliot",
|
||||||
|
"jones",
|
||||||
|
"kalam",
|
||||||
|
"kare",
|
||||||
|
"keller",
|
||||||
|
"khorana",
|
||||||
|
"kilby",
|
||||||
|
"kirch",
|
||||||
|
"knuth",
|
||||||
|
"kowalevski",
|
||||||
|
"lalande",
|
||||||
|
"lamarr",
|
||||||
|
"leakey",
|
||||||
|
"leavitt",
|
||||||
|
"lichterman",
|
||||||
|
"liskov",
|
||||||
|
"lovelace",
|
||||||
|
"lumiere",
|
||||||
|
"mahavira",
|
||||||
|
"mayer",
|
||||||
|
"mccarthy",
|
||||||
|
"mcclintock",
|
||||||
|
"mclean",
|
||||||
|
"mcnulty",
|
||||||
|
"meitner",
|
||||||
|
"meninsky",
|
||||||
|
"mestorf",
|
||||||
|
"minsky",
|
||||||
|
"mirzakhani",
|
||||||
|
"morse",
|
||||||
|
"murdock",
|
||||||
|
"newton",
|
||||||
|
"nobel",
|
||||||
|
"noether",
|
||||||
|
"northcutt",
|
||||||
|
"noyce",
|
||||||
|
"panini",
|
||||||
|
"pare",
|
||||||
|
"pasteur",
|
||||||
|
"payne",
|
||||||
|
"perlman",
|
||||||
|
"pike",
|
||||||
|
"poincare",
|
||||||
|
"poitras",
|
||||||
|
"ptolemy",
|
||||||
|
"raman",
|
||||||
|
"ramanujan",
|
||||||
|
"ride",
|
||||||
|
"ritchie",
|
||||||
|
"roentgen",
|
||||||
|
"rosalind",
|
||||||
|
"saha",
|
||||||
|
"sammet",
|
||||||
|
"shaw",
|
||||||
|
"shirley",
|
||||||
|
"shockley",
|
||||||
|
"sinoussi",
|
||||||
|
"snyder",
|
||||||
|
"spence",
|
||||||
|
"stallman",
|
||||||
|
"stonebraker",
|
||||||
|
"swanson",
|
||||||
|
"swartz",
|
||||||
|
"swirles",
|
||||||
|
"tesla",
|
||||||
|
"thompson",
|
||||||
|
"torvalds",
|
||||||
|
"turing",
|
||||||
|
"varahamihira",
|
||||||
|
"visvesvaraya",
|
||||||
|
"volhard",
|
||||||
|
"wescoff",
|
||||||
|
"williams",
|
||||||
|
"wilson",
|
||||||
|
"wing",
|
||||||
|
"wozniak",
|
||||||
|
"wright",
|
||||||
|
"yalow",
|
||||||
|
"yonath",
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Header text for user projects
|
||||||
|
* @constant
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const USER_PROJECTS_HEADER = "My Collection";
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ import {
|
||||||
const initialValue = {
|
const initialValue = {
|
||||||
updateSSEData: ({}) => {},
|
updateSSEData: ({}) => {},
|
||||||
sseData: {},
|
sseData: {},
|
||||||
|
isBuilding: false,
|
||||||
|
setIsBuilding: (isBuilding: boolean) => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const SSEContext = createContext(initialValue);
|
const SSEContext = createContext(initialValue);
|
||||||
|
|
@ -19,6 +21,7 @@ export function useSSE() {
|
||||||
|
|
||||||
export function SSEProvider({ children }) {
|
export function SSEProvider({ children }) {
|
||||||
const [sseData, setSSEData] = useState({});
|
const [sseData, setSSEData] = useState({});
|
||||||
|
const [isBuilding, setIsBuilding] = useState(false);
|
||||||
|
|
||||||
const updateSSEData = useCallback((newData: any) => {
|
const updateSSEData = useCallback((newData: any) => {
|
||||||
setSSEData((prevData) => ({
|
setSSEData((prevData) => ({
|
||||||
|
|
@ -28,7 +31,9 @@ export function SSEProvider({ children }) {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SSEContext.Provider value={{ sseData, updateSSEData }}>
|
<SSEContext.Provider
|
||||||
|
value={{ sseData, updateSSEData, isBuilding, setIsBuilding }}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</SSEContext.Provider>
|
</SSEContext.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ export function AlertProvider({ children }: { children: ReactNode }) {
|
||||||
function setErrorData(newState: { title: string; list?: Array<string> }) {
|
function setErrorData(newState: { title: string; list?: Array<string> }) {
|
||||||
setErrorDataState(newState);
|
setErrorDataState(newState);
|
||||||
setErrorOpen(true);
|
setErrorOpen(true);
|
||||||
if (newState.title) {
|
if (newState.title && newState.title !== "") {
|
||||||
setNotificationCenter(true);
|
setNotificationCenter(true);
|
||||||
pushNotificationList({
|
pushNotificationList({
|
||||||
type: "error",
|
type: "error",
|
||||||
|
|
@ -97,7 +97,7 @@ export function AlertProvider({ children }: { children: ReactNode }) {
|
||||||
function setNoticeData(newState: { title: string; link?: string }) {
|
function setNoticeData(newState: { title: string; link?: string }) {
|
||||||
setNoticeDataState(newState);
|
setNoticeDataState(newState);
|
||||||
setNoticeOpen(true);
|
setNoticeOpen(true);
|
||||||
if (newState.title) {
|
if (newState.title && newState.title !== "") {
|
||||||
// Add new notice to notification center
|
// Add new notice to notification center
|
||||||
setNotificationCenter(true);
|
setNotificationCenter(true);
|
||||||
pushNotificationList({
|
pushNotificationList({
|
||||||
|
|
@ -117,7 +117,7 @@ export function AlertProvider({ children }: { children: ReactNode }) {
|
||||||
setSuccessOpen(true); // open the success alert
|
setSuccessOpen(true); // open the success alert
|
||||||
|
|
||||||
// If the new state has a "title" property, add a new success notification to the list
|
// If the new state has a "title" property, add a new success notification to the list
|
||||||
if (newState.title) {
|
if (newState.title && newState.title !== "") {
|
||||||
setNotificationCenter(true); // show the notification center
|
setNotificationCenter(true); // show the notification center
|
||||||
pushNotificationList({
|
pushNotificationList({
|
||||||
// add the new notification to the list
|
// add the new notification to the list
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,15 @@ import {
|
||||||
} from "react";
|
} from "react";
|
||||||
import { FlowType, NodeType } from "../types/flow";
|
import { FlowType, NodeType } from "../types/flow";
|
||||||
import { TabsContextType, TabsState } from "../types/tabs";
|
import { TabsContextType, TabsState } from "../types/tabs";
|
||||||
import { updateIds, updateTemplate } from "../utils";
|
import {
|
||||||
|
updateIds,
|
||||||
|
updateTemplate,
|
||||||
|
getRandomDescription,
|
||||||
|
getRandomName,
|
||||||
|
} from "../utils";
|
||||||
import { alertContext } from "./alertContext";
|
import { alertContext } from "./alertContext";
|
||||||
import { typesContext } from "./typesContext";
|
import { typesContext } from "./typesContext";
|
||||||
import { APITemplateType } from "../types/api";
|
import { APIClassType, APITemplateType } from "../types/api";
|
||||||
import ShortUniqueId from "short-unique-id";
|
import ShortUniqueId from "short-unique-id";
|
||||||
import { addEdge } from "reactflow";
|
import { addEdge } from "reactflow";
|
||||||
import {
|
import {
|
||||||
|
|
@ -40,7 +45,7 @@ const TabsContextInitialValue: TabsContextType = {
|
||||||
uploadFlows: () => {},
|
uploadFlows: () => {},
|
||||||
uploadFlow: () => {},
|
uploadFlow: () => {},
|
||||||
hardReset: () => {},
|
hardReset: () => {},
|
||||||
saveFlow: async (flow:FlowType) => {},
|
saveFlow: async (flow: FlowType) => {},
|
||||||
disableCopyPaste: false,
|
disableCopyPaste: false,
|
||||||
setDisableCopyPaste: (state: boolean) => {},
|
setDisableCopyPaste: (state: boolean) => {},
|
||||||
lastCopiedSelection: null,
|
lastCopiedSelection: null,
|
||||||
|
|
@ -187,20 +192,26 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function processFlowEdges(flow) {
|
function processFlowEdges(flow) {
|
||||||
|
if(!flow.data || !flow.data.edges) return;
|
||||||
flow.data.edges.forEach((edge) => {
|
flow.data.edges.forEach((edge) => {
|
||||||
edge.className = "";
|
edge.className = "";
|
||||||
edge.style = { stroke: "#555555" };
|
edge.style = { stroke: "#555555" };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function updateDisplay_name(node:NodeType,template:APIClassType) {
|
||||||
|
node.data.node.display_name = template["display_name"]?template["display_name"]:node.data.type;
|
||||||
|
}
|
||||||
|
|
||||||
function processFlowNodes(flow) {
|
function processFlowNodes(flow) {
|
||||||
flow.data.nodes.forEach((node) => {
|
if(!flow.data || !flow.data.nodes) return;
|
||||||
|
flow.data.nodes.forEach((node:NodeType) => {
|
||||||
const template = templates[node.data.type];
|
const template = templates[node.data.type];
|
||||||
if (!template) {
|
if (!template) {
|
||||||
setErrorData({ title: `Unknown node type: ${node.data.type}` });
|
setErrorData({ title: `Unknown node type: ${node.data.type}` });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Object.keys(template["template"]).length > 0) {
|
if (Object.keys(template["template"]).length > 0) {
|
||||||
|
updateDisplay_name(node,template);
|
||||||
updateNodeBaseClasses(node, template);
|
updateNodeBaseClasses(node, template);
|
||||||
updateNodeEdges(flow, node, template);
|
updateNodeEdges(flow, node, template);
|
||||||
updateNodeDescription(node, template);
|
updateNodeDescription(node, template);
|
||||||
|
|
@ -209,11 +220,11 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateNodeBaseClasses(node, template) {
|
function updateNodeBaseClasses(node:NodeType,template:APIClassType) {
|
||||||
node.data.node.base_classes = template["base_classes"];
|
node.data.node.base_classes = template["base_classes"];
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateNodeEdges(flow, node, template) {
|
function updateNodeEdges(flow:FlowType, node:NodeType,template:APIClassType) {
|
||||||
flow.data.edges.forEach((edge) => {
|
flow.data.edges.forEach((edge) => {
|
||||||
if (edge.source === node.id) {
|
if (edge.source === node.id) {
|
||||||
edge.sourceHandle = edge.sourceHandle
|
edge.sourceHandle = edge.sourceHandle
|
||||||
|
|
@ -225,11 +236,11 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateNodeDescription(node, template) {
|
function updateNodeDescription(node:NodeType,template:APIClassType) {
|
||||||
node.data.node.description = template["description"];
|
node.data.node.description = template["description"];
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateNodeTemplate(node, template) {
|
function updateNodeTemplate(node:NodeType,template:APIClassType) {
|
||||||
node.data.node.template = updateTemplate(
|
node.data.node.template = updateTemplate(
|
||||||
template["template"] as unknown as APITemplateType,
|
template["template"] as unknown as APITemplateType,
|
||||||
node.data.node.template as APITemplateType
|
node.data.node.template as APITemplateType
|
||||||
|
|
@ -346,7 +357,6 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
*/
|
*/
|
||||||
function removeFlow(id: string) {
|
function removeFlow(id: string) {
|
||||||
const index = flows.findIndex((flow) => flow.id === id);
|
const index = flows.findIndex((flow) => flow.id === id);
|
||||||
console.log(index);
|
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
deleteFlowFromDatabase(id).then(() => {
|
deleteFlowFromDatabase(id).then(() => {
|
||||||
setFlows(flows.filter((flow) => flow.id !== id));
|
setFlows(flows.filter((flow) => flow.id !== id));
|
||||||
|
|
@ -454,11 +464,13 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
if (newProject) {
|
if (newProject) {
|
||||||
let flowData = extractDataFromFlow(flow);
|
let flowData = extractDataFromFlow(flow);
|
||||||
if (flowData.description == "") {
|
if (flowData.description == "") {
|
||||||
flowData.description = "This is a new flow.";
|
flowData.description = getRandomDescription();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new flow with a default name if no flow is provided.
|
// Create a new flow with a default name if no flow is provided.
|
||||||
const newFlow = createNewFlow(flowData, flow);
|
const newFlow = createNewFlow(flowData, flow);
|
||||||
|
processFlowEdges(newFlow);
|
||||||
|
processFlowNodes(newFlow);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { id } = await saveFlowToDatabase(newFlow);
|
const { id } = await saveFlowToDatabase(newFlow);
|
||||||
|
|
@ -536,7 +548,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
|
|
||||||
const createNewFlow = (flowData, flow) => ({
|
const createNewFlow = (flowData, flow) => ({
|
||||||
description: flowData.description,
|
description: flowData.description,
|
||||||
name: flow?.name ?? "New Flow",
|
name: flow?.name ?? getRandomName(),
|
||||||
data: flowData.data,
|
data: flowData.data,
|
||||||
id: "",
|
id: "",
|
||||||
});
|
});
|
||||||
|
|
@ -565,10 +577,10 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveFlow(newFlow: FlowType) {
|
async function saveFlow(newFlow: FlowType) {
|
||||||
try{
|
try {
|
||||||
// updates flow in db
|
// updates flow in db
|
||||||
const updatedFlow = await updateFlowInDatabase(newFlow);
|
const updatedFlow = await updateFlowInDatabase(newFlow);
|
||||||
if (updatedFlow){
|
if (updatedFlow) {
|
||||||
// updates flow in state
|
// updates flow in state
|
||||||
setFlows((prevState) => {
|
setFlows((prevState) => {
|
||||||
const newFlows = [...prevState];
|
const newFlows = [...prevState];
|
||||||
|
|
@ -590,8 +602,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
} catch (err) {
|
||||||
catch(err){
|
|
||||||
setErrorData(err);
|
setErrorData(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,16 +51,21 @@ export function TypesProvider({ children }: { children: ReactNode }) {
|
||||||
);
|
);
|
||||||
// Set the types by reducing over the keys of the result data and updating the accumulator.
|
// Set the types by reducing over the keys of the result data and updating the accumulator.
|
||||||
setTypes(
|
setTypes(
|
||||||
Object.keys(result.data).reduce((acc, curr) => {
|
// Reverse the keys so the tool world does not overlap
|
||||||
Object.keys(result.data[curr]).forEach((c: keyof APIKindType) => {
|
Object.keys(result.data)
|
||||||
acc[c] = curr;
|
.reverse()
|
||||||
// Add the base classes to the accumulator as well.
|
.reduce((acc, curr) => {
|
||||||
result.data[curr][c].base_classes?.forEach((b) => {
|
Object.keys(result.data[curr]).forEach(
|
||||||
acc[b] = curr;
|
(c: keyof APIKindType) => {
|
||||||
});
|
acc[c] = curr;
|
||||||
});
|
// Add the base classes to the accumulator as well.
|
||||||
return acc;
|
result.data[curr][c].base_classes?.forEach((b) => {
|
||||||
}, {})
|
acc[b] = curr;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Clear the interval if successful.
|
// Clear the interval if successful.
|
||||||
|
|
|
||||||
|
|
@ -66,8 +66,6 @@ export function UndoRedoProvider({ children }) {
|
||||||
|
|
||||||
const takeSnapshot = useCallback(() => {
|
const takeSnapshot = useCallback(() => {
|
||||||
// push the current graph to the past state
|
// push the current graph to the past state
|
||||||
console.log(past);
|
|
||||||
console.log(tabIndex);
|
|
||||||
setPast((old) => {
|
setPast((old) => {
|
||||||
let newPast = cloneDeep(old);
|
let newPast = cloneDeep(old);
|
||||||
newPast[tabIndex] = old[tabIndex].slice(
|
newPast[tabIndex] = old[tabIndex].slice(
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import {
|
||||||
PromptTypeAPI,
|
PromptTypeAPI,
|
||||||
errorsTypeAPI,
|
errorsTypeAPI,
|
||||||
InitTypeAPI,
|
InitTypeAPI,
|
||||||
|
UploadFileTypeAPI,
|
||||||
} from "./../../types/api/index";
|
} from "./../../types/api/index";
|
||||||
import { APIObjectType, sendAllProps } from "../../types/api/index";
|
import { APIObjectType, sendAllProps } from "../../types/api/index";
|
||||||
import axios, { AxiosResponse } from "axios";
|
import axios, { AxiosResponse } from "axios";
|
||||||
|
|
@ -18,6 +19,20 @@ export async function getAll(): Promise<AxiosResponse<APIObjectType>> {
|
||||||
return await axios.get(`/api/v1/all`);
|
return await axios.get(`/api/v1/all`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GITHUB_API_URL = "https://api.github.com";
|
||||||
|
|
||||||
|
export async function getRepoStars(owner, repo) {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`${GITHUB_API_URL}/repos/${owner}/${repo}`
|
||||||
|
);
|
||||||
|
return response.data.stargazers_count;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching repository data:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends data to the API for prediction.
|
* Sends data to the API for prediction.
|
||||||
*
|
*
|
||||||
|
|
@ -34,13 +49,6 @@ export async function postValidateCode(
|
||||||
return await axios.post("/api/v1/validate/code", { code });
|
return await axios.post("/api/v1/validate/code", { code });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function postValidateNode(
|
|
||||||
nodeId: string,
|
|
||||||
data: any
|
|
||||||
): Promise<AxiosResponse<string>> {
|
|
||||||
return await axios.post(`/api/v1/validate/node/${nodeId}`, { data });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the prompt for the code block by sending it to an API endpoint.
|
* Checks the prompt for the code block by sending it to an API endpoint.
|
||||||
*
|
*
|
||||||
|
|
@ -305,3 +313,21 @@ export async function postBuildInit(
|
||||||
): Promise<AxiosResponse<InitTypeAPI>> {
|
): Promise<AxiosResponse<InitTypeAPI>> {
|
||||||
return await axios.post(`/api/v1/build/init`, flow);
|
return await axios.post(`/api/v1/build/init`, flow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fetch(`/upload/${id}`, {
|
||||||
|
// method: "POST",
|
||||||
|
// body: formData,
|
||||||
|
// });
|
||||||
|
/**
|
||||||
|
* Uploads a file to the server.
|
||||||
|
* @param {File} file - The file to upload.
|
||||||
|
* @param {string} id - The ID of the flow to upload the file to.
|
||||||
|
*/
|
||||||
|
export async function uploadFile(
|
||||||
|
file: File,
|
||||||
|
id: string
|
||||||
|
): Promise<AxiosResponse<UploadFileTypeAPI>> {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append("file", file);
|
||||||
|
return await axios.post(`/api/v1/upload/${id}`, formData);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import React, { forwardRef } from "react";
|
import React, { forwardRef } from "react";
|
||||||
import { ReactComponent as MidjorneySVG } from "./Midjourney_Emblem.svg";
|
import { ReactComponent as MidjourneySVG } from "./Midjourney_Emblem.svg";
|
||||||
|
|
||||||
export const MidjorneyIcon = forwardRef<
|
export const MidjourneyIcon = forwardRef<
|
||||||
SVGSVGElement,
|
SVGSVGElement,
|
||||||
React.PropsWithChildren<{}>
|
React.PropsWithChildren<{}>
|
||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
return <MidjorneySVG ref={ref} {...props} />;
|
return <MidjourneySVG ref={ref} {...props} />;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
9
src/frontend/src/icons/MongoDB/index.tsx
Normal file
9
src/frontend/src/icons/MongoDB/index.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import React, { forwardRef } from "react";
|
||||||
|
import { ReactComponent as MongoDBSVG } from "./mongodb-icon.svg";
|
||||||
|
|
||||||
|
export const MongoDBIcon = forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.PropsWithChildren<{}>
|
||||||
|
>((props, ref) => {
|
||||||
|
return <MongoDBSVG ref={ref} {...props} />;
|
||||||
|
});
|
||||||
1
src/frontend/src/icons/MongoDB/mongodb-icon.svg
Normal file
1
src/frontend/src/icons/MongoDB/mongodb-icon.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 32 32"><path d="M15.9.087l.854 1.604c.192.296.4.558.645.802.715.715 1.394 1.464 2.004 2.266 1.447 1.9 2.423 4.01 3.12 6.292.418 1.394.645 2.824.662 4.27.07 4.323-1.412 8.035-4.4 11.12-.488.488-1.01.94-1.57 1.342-.296 0-.436-.227-.558-.436-.227-.383-.366-.82-.436-1.255-.105-.523-.174-1.046-.14-1.586v-.244C16.057 24.21 15.796.21 15.9.087z" fill="#599636"/><path d="M15.9.034c-.035-.07-.07-.017-.105.017.017.35-.105.662-.296.96-.21.296-.488.523-.767.767-1.55 1.342-2.77 2.963-3.747 4.776-1.3 2.44-1.97 5.055-2.16 7.808-.087.993.314 4.497.627 5.508.854 2.684 2.388 4.933 4.375 6.885.488.47 1.01.906 1.55 1.325.157 0 .174-.14.21-.244a4.78 4.78 0 0 0 .157-.68l.35-2.614L15.9.034z" fill="#6cac48"/><path d="M16.754 28.845c.035-.4.227-.732.436-1.063-.21-.087-.366-.26-.488-.453-.105-.174-.192-.383-.26-.575-.244-.732-.296-1.5-.366-2.248v-.453c-.087.07-.105.662-.105.75a17.37 17.37 0 0 1-.314 2.353c-.052.314-.087.627-.28.906 0 .035 0 .07.017.122.314.924.4 1.865.453 2.824v.35c0 .418-.017.33.33.47.14.052.296.07.436.174.105 0 .122-.087.122-.157l-.052-.575v-1.604c-.017-.28.035-.558.07-.82z" fill="#c2bfbf"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
9
src/frontend/src/icons/Pinecone/index.tsx
Normal file
9
src/frontend/src/icons/Pinecone/index.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import React, { forwardRef } from "react";
|
||||||
|
import { ReactComponent as PineconeSVG } from "./pinecone_logo.svg";
|
||||||
|
|
||||||
|
export const PineconeIcon = forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.PropsWithChildren<{}>
|
||||||
|
>((props, ref) => {
|
||||||
|
return <PineconeSVG ref={ref} {...props} />;
|
||||||
|
});
|
||||||
21
src/frontend/src/icons/Pinecone/pinecone_logo.svg
Normal file
21
src/frontend/src/icons/Pinecone/pinecone_logo.svg
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<svg width="32" height="35" viewBox="0 0 32 35" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M13.8555 34.2962C14.9325 34.2962 15.8055 33.4451 15.8055 32.3954C15.8055 31.3456 14.9325 30.4946 13.8555 30.4946C12.7786 30.4946 11.9055 31.3456 11.9055 32.3954C11.9055 33.4451 12.7786 34.2962 13.8555 34.2962Z" fill="black"/>
|
||||||
|
<path d="M18.4138 7.19675L19.2512 2.66005" stroke="black" stroke-width="2.11786" stroke-linecap="square"/>
|
||||||
|
<path d="M22.2656 5.5855L19.3466 2.11099L15.3748 4.37292" stroke="black" stroke-width="2.11786" stroke-linecap="square" stroke-linejoin="round"/>
|
||||||
|
<path d="M14.9202 26.5528L15.7337 22.0169" stroke="black" stroke-width="2.11786" stroke-linecap="square"/>
|
||||||
|
<path d="M18.7729 24.9304L15.83 21.4671L11.8701 23.741" stroke="black" stroke-width="2.11786" stroke-linecap="square" stroke-linejoin="round"/>
|
||||||
|
<path d="M16.6077 17.1996L17.4212 12.6633" stroke="black" stroke-width="2.11786" stroke-linecap="square"/>
|
||||||
|
<path d="M20.4587 15.58L17.5277 12.128L13.5679 14.3904" stroke="black" stroke-width="2.11786" stroke-linecap="square" stroke-linejoin="round"/>
|
||||||
|
<path d="M8.32871 26.1554L4.75171 28.5815" stroke="black" stroke-width="2.01017" stroke-linecap="square"/>
|
||||||
|
<path d="M8.54383 30.0865L4.3208 28.8738L4.63185 24.5944" stroke="black" stroke-width="2.01017" stroke-linecap="square" stroke-linejoin="round"/>
|
||||||
|
<path d="M21.3213 28.4299L23.8096 31.9282" stroke="black" stroke-width="2.01017" stroke-linecap="square"/>
|
||||||
|
<path d="M19.718 32.045L24.1085 32.3365L25.3527 28.2438" stroke="black" stroke-width="2.01017" stroke-linecap="square" stroke-linejoin="round"/>
|
||||||
|
<path d="M25.3999 21.3291L29.7784 22.0996" stroke="black" stroke-width="2.05804" stroke-linecap="square"/>
|
||||||
|
<path d="M26.9072 25.072L30.3048 22.1919L28.1634 18.3557" stroke="black" stroke-width="2.05804" stroke-linecap="square" stroke-linejoin="round"/>
|
||||||
|
<path d="M24.1196 12.8615L28.0197 10.763" stroke="black" stroke-width="2.05804" stroke-linecap="square"/>
|
||||||
|
<path d="M24.3357 8.83965L28.4869 10.5188L27.7093 14.8216" stroke="black" stroke-width="2.05804" stroke-linecap="square" stroke-linejoin="round"/>
|
||||||
|
<path d="M6.91639 18.1572L2.52588 17.4101" stroke="black" stroke-width="2.05804" stroke-linecap="square"/>
|
||||||
|
<path d="M4.17731 21.1645L2 17.328L5.36167 14.436" stroke="black" stroke-width="2.05804" stroke-linecap="square" stroke-linejoin="round"/>
|
||||||
|
<path d="M11.0799 10.6129L8.14893 7.34769" stroke="black" stroke-width="2.05804" stroke-linecap="square"/>
|
||||||
|
<path d="M12.2897 6.77496L7.80349 6.96156L7.01392 11.2649" stroke="black" stroke-width="2.05804" stroke-linecap="square" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.5 KiB |
9
src/frontend/src/icons/supabase/index.tsx
Normal file
9
src/frontend/src/icons/supabase/index.tsx
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import React, { forwardRef } from "react";
|
||||||
|
import { ReactComponent as SupabaseSvg } from "./supabase-icon.svg";
|
||||||
|
|
||||||
|
export const SupabaseIcon = forwardRef<
|
||||||
|
SVGSVGElement,
|
||||||
|
React.PropsWithChildren<{}>
|
||||||
|
>((props, ref) => {
|
||||||
|
return <SupabaseSvg ref={ref} {...props} />;
|
||||||
|
});
|
||||||
99
src/frontend/src/icons/supabase/supabase-icon.svg
Normal file
99
src/frontend/src/icons/supabase/supabase-icon.svg
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="64"
|
||||||
|
height="64"
|
||||||
|
viewBox="0 0 64 64"
|
||||||
|
version="1.1"
|
||||||
|
id="svg20"
|
||||||
|
sodipodi:docname="supabase-icon.svg"
|
||||||
|
style="fill:none"
|
||||||
|
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
|
||||||
|
<metadata
|
||||||
|
id="metadata24">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="1687"
|
||||||
|
inkscape:window-height="849"
|
||||||
|
id="namedview22"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="2.0884956"
|
||||||
|
inkscape:cx="54.5"
|
||||||
|
inkscape:cy="56.5"
|
||||||
|
inkscape:window-x="70"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="svg20" />
|
||||||
|
<path
|
||||||
|
d="m 37.41219,62.936701 c -1.634985,2.05896 -4.950068,0.93085 -4.989463,-1.69817 L 31.846665,22.786035 h 25.855406 c 4.683108,0 7.294967,5.409033 4.382927,9.07673 z"
|
||||||
|
id="path2"
|
||||||
|
style="fill:url(#paint0_linear);stroke-width:0.57177335"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
d="m 37.41219,62.936701 c -1.634985,2.05896 -4.950068,0.93085 -4.989463,-1.69817 L 31.846665,22.786035 h 25.855406 c 4.683108,0 7.294967,5.409033 4.382927,9.07673 z"
|
||||||
|
id="path4"
|
||||||
|
style="fill:url(#paint1_linear);fill-opacity:0.2;stroke-width:0.57177335"
|
||||||
|
inkscape:connector-curvature="0" />
|
||||||
|
<path
|
||||||
|
d="m 26.89694,1.0634102 c 1.634986,-2.05918508 4.950125,-0.93090008 4.989521,1.698149 L 32.138899,41.214003 H 6.607076 c -4.6832501,0 -7.29518376,-5.409032 -4.3830007,-9.07673 z"
|
||||||
|
id="path6"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#3ecf8e;stroke-width:0.57177335" />
|
||||||
|
<defs
|
||||||
|
id="defs18">
|
||||||
|
<linearGradient
|
||||||
|
id="paint0_linear"
|
||||||
|
x1="53.973801"
|
||||||
|
y1="54.973999"
|
||||||
|
x2="94.163498"
|
||||||
|
y2="71.829498"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(0.57177306,0,0,0.57177334,0.98590077,-0.12074988)">
|
||||||
|
<stop
|
||||||
|
stop-color="#249361"
|
||||||
|
id="stop8" />
|
||||||
|
<stop
|
||||||
|
offset="1"
|
||||||
|
stop-color="#3ECF8E"
|
||||||
|
id="stop10" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
id="paint1_linear"
|
||||||
|
x1="36.1558"
|
||||||
|
y1="30.577999"
|
||||||
|
x2="54.484402"
|
||||||
|
y2="65.080597"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
gradientTransform="matrix(0.57177306,0,0,0.57177334,0.98590077,-0.12074988)">
|
||||||
|
<stop
|
||||||
|
id="stop13" />
|
||||||
|
<stop
|
||||||
|
offset="1"
|
||||||
|
stop-opacity="0"
|
||||||
|
id="stop15" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.2 KiB |
|
|
@ -71,35 +71,35 @@
|
||||||
--almost-light-blue: #dbeafe; tailwind blue 100 */
|
--almost-light-blue: #dbeafe; tailwind blue 100 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: 224 71% 4%; /* hsl(224 71% 4%) */
|
--background: 224 71% 4%; /* hsl(224 71% 4%) */
|
||||||
--foreground: 213 31% 91%; /* hsl(213 31% 91%) */
|
--foreground: 213 31% 91%; /* hsl(213 31% 91%) */
|
||||||
|
|
||||||
--muted: 223 47% 11%; /* hsl(223 47% 11%) */
|
--muted: 223 47% 11%; /* hsl(223 47% 11%) */
|
||||||
--muted-foreground: 215.4 16.3% 56.9%; /* hsl(215 16% 56%) */
|
--muted-foreground: 215.4 16.3% 56.9%; /* hsl(215 16% 56%) */
|
||||||
|
|
||||||
--popover: 224 71% 4%; /* hsl(224 71% 4%) */
|
--popover: 224 71% 4%; /* hsl(224 71% 4%) */
|
||||||
--popover-foreground: 215 20.2% 65.1%; /* hsl(215 20% 65%) */
|
--popover-foreground: 215 20.2% 65.1%; /* hsl(215 20% 65%) */
|
||||||
|
|
||||||
--card: 224 71% 4%; /* hsl(224 71% 4%) */
|
--card: 224 71% 4%; /* hsl(224 71% 4%) */
|
||||||
--card-foreground: 213 31% 91%; /* hsl(213 31% 91%) */
|
--card-foreground: 213 31% 91%; /* hsl(213 31% 91%) */
|
||||||
|
|
||||||
--border: 216 34% 17%; /* hsl(216 34% 17%) */
|
--border: 216 34% 17%; /* hsl(216 34% 17%) */
|
||||||
--input: 216 34% 17%; /* hsl(216 34% 17%) */
|
--input: 216 34% 17%; /* hsl(216 34% 17%) */
|
||||||
|
|
||||||
--primary: 210 40% 98%; /* hsl(210 40% 98%) */
|
--primary: 210 40% 98%; /* hsl(210 40% 98%) */
|
||||||
--primary-foreground: 222.2 47.4% 1.2%; /* hsl(222 47% 1%) */
|
--primary-foreground: 222.2 47.4% 1.2%; /* hsl(222 47% 1%) */
|
||||||
|
|
||||||
--secondary: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
|
--secondary: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
|
||||||
--secondary-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
|
--secondary-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
|
||||||
|
|
||||||
--accent: 216 34% 17%; /* hsl(216 34% 17%) */
|
--accent: 216 34% 17%; /* hsl(216 34% 17%) */
|
||||||
--accent-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
|
--accent-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
|
||||||
|
|
||||||
--destructive: 0 63% 31%; /* hsl(0 63% 31%) */
|
--destructive: 0 63% 31%; /* hsl(0 63% 31%) */
|
||||||
--destructive-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
|
--destructive-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
|
||||||
|
|
||||||
--ring: 216 34% 17%; /* hsl(216 34% 17%) */
|
--ring: 216 34% 17%; /* hsl(216 34% 17%) */
|
||||||
|
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
|
|
||||||
|
|
@ -118,7 +118,7 @@
|
||||||
--card-foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
|
--card-foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
|
||||||
--border: 214.3 21.8% 91.4%; /* hsl(214 32% 91%) */
|
--border: 214.3 21.8% 91.4%; /* hsl(214 32% 91%) */
|
||||||
--input: 214.3 21.8% 91.4%; /* hsl(214 32% 91%) */
|
--input: 214.3 21.8% 91.4%; /* hsl(214 32% 91%) */
|
||||||
--primary: 222.2 27.0% 11.2%; /* hsl(222 27% 18%) */
|
--primary: 222.2 27% 11.2%; /* hsl(222 27% 18%) */
|
||||||
--primary-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
|
--primary-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
|
||||||
--secondary: 210 40% 96.1%; /* hsl(210 40% 96%) */
|
--secondary: 210 40% 96.1%; /* hsl(210 40% 96%) */
|
||||||
--secondary-foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
|
--secondary-foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { CodeBracketSquareIcon } from "@heroicons/react/24/outline";
|
|
||||||
import { useContext, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
import { PopUpContext } from "../../contexts/popUpContext";
|
import { PopUpContext } from "../../contexts/popUpContext";
|
||||||
import "ace-builds/src-noconflict/mode-python";
|
import "ace-builds/src-noconflict/mode-python";
|
||||||
|
|
@ -26,7 +25,7 @@ import {
|
||||||
TabsList,
|
TabsList,
|
||||||
TabsTrigger,
|
TabsTrigger,
|
||||||
} from "../../components/ui/tabs";
|
} from "../../components/ui/tabs";
|
||||||
import { Check, Clipboard } from "lucide-react";
|
import { Check, Clipboard, Code2 } from "lucide-react";
|
||||||
|
|
||||||
export default function ApiModal({ flow }: { flow: FlowType }) {
|
export default function ApiModal({ flow }: { flow: FlowType }) {
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
|
|
@ -88,8 +87,8 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center">
|
<DialogTitle className="flex items-center">
|
||||||
<span className="pr-2">Code</span>
|
<span className="pr-2">Code</span>
|
||||||
<CodeBracketSquareIcon
|
<Code2
|
||||||
className="h-6 w-6 text-foreground pl-1"
|
className="h-6 w-6 text-gray-800 pl-1 dark:text-white"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
|
|
@ -104,7 +103,7 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
|
||||||
<div className="flex items-center justify-between px-2">
|
<div className="flex items-center justify-between px-2">
|
||||||
<TabsList>
|
<TabsList>
|
||||||
{tabs.map((tab, index) => (
|
{tabs.map((tab, index) => (
|
||||||
<TabsTrigger value={index.toString()}>{tab.name}</TabsTrigger>
|
<TabsTrigger key={index} value={index.toString()}>{tab.name}</TabsTrigger>
|
||||||
))}
|
))}
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<div className="float-right">
|
<div className="float-right">
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import {
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "../../components/ui/table";
|
} from "../../components/ui/table";
|
||||||
import ToggleShadComponent from "../../components/toggleShadComponent";
|
import ToggleShadComponent from "../../components/toggleShadComponent";
|
||||||
import { VariableIcon } from "@heroicons/react/24/outline";
|
|
||||||
import InputListComponent from "../../components/inputListComponent";
|
import InputListComponent from "../../components/inputListComponent";
|
||||||
import TextAreaComponent from "../../components/textAreaComponent";
|
import TextAreaComponent from "../../components/textAreaComponent";
|
||||||
import InputComponent from "../../components/inputComponent";
|
import InputComponent from "../../components/inputComponent";
|
||||||
|
|
@ -33,6 +32,7 @@ import {
|
||||||
} from "../../components/ui/dialog";
|
} from "../../components/ui/dialog";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { Badge } from "../../components/ui/badge";
|
import { Badge } from "../../components/ui/badge";
|
||||||
|
import { Variable } from "lucide-react";
|
||||||
|
|
||||||
export default function EditNodeModal({ data }: { data: NodeDataType }) {
|
export default function EditNodeModal({ data }: { data: NodeDataType }) {
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
|
|
@ -79,8 +79,8 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={true} onOpenChange={setModalOpen}>
|
<Dialog open={true} onOpenChange={setModalOpen} >
|
||||||
<DialogTrigger></DialogTrigger>
|
<DialogTrigger asChild></DialogTrigger>
|
||||||
<DialogContent className="lg:max-w-[700px] ">
|
<DialogContent className="lg:max-w-[700px] ">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center">
|
<DialogTitle className="flex items-center">
|
||||||
|
|
@ -90,10 +90,8 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
{data.node?.description}
|
{data.node?.description}
|
||||||
<div className="flex pt-4">
|
<div className="flex pt-4">
|
||||||
<VariableIcon className="w-5 h-5 pe-1 text-foreground stroke-2">
|
<Variable className="w-5 h-5 pe-1 text-gray-700 stroke-2 dark:text-slate-200"></Variable>
|
||||||
|
<span className="text-sm font-semibold text-gray-800 dark:text-white">
|
||||||
</VariableIcon>
|
|
||||||
<span className="text-sm font-semibold text-foreground">
|
|
||||||
Parameters
|
Parameters
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,7 @@ export default function ModalField({
|
||||||
data.node.template[name].value = t;
|
data.node.template[name].value = t;
|
||||||
setEnabled(t);
|
setEnabled(t);
|
||||||
}}
|
}}
|
||||||
|
size="small"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : type === "float" ? (
|
) : type === "float" ? (
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { XMarkIcon } from "@heroicons/react/24/outline";
|
|
||||||
import { Fragment, useContext, useRef, useState } from "react";
|
import { Fragment, useContext, useRef, useState } from "react";
|
||||||
import { PopUpContext } from "../../contexts/popUpContext";
|
import { PopUpContext } from "../../contexts/popUpContext";
|
||||||
import { NodeDataType } from "../../types/flow";
|
import { NodeDataType } from "../../types/flow";
|
||||||
|
|
@ -13,6 +12,7 @@ import {
|
||||||
} from "../../utils";
|
} from "../../utils";
|
||||||
import { typesContext } from "../../contexts/typesContext";
|
import { typesContext } from "../../contexts/typesContext";
|
||||||
import ModalField from "./components/ModalField";
|
import ModalField from "./components/ModalField";
|
||||||
|
import { X } from "lucide-react";
|
||||||
|
|
||||||
export default function NodeModal({ data }: { data: NodeDataType }) {
|
export default function NodeModal({ data }: { data: NodeDataType }) {
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
|
|
@ -27,7 +27,8 @@ export default function NodeModal({ data }: { data: NodeDataType }) {
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const Icon = nodeIcons[types[data.type]];
|
// any to avoid type conflict
|
||||||
|
const Icon: any = nodeIcons[types[data.type]];
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={open} appear={true} as={Fragment}>
|
<Transition.Root show={open} appear={true} as={Fragment}>
|
||||||
<Dialog
|
<Dialog
|
||||||
|
|
@ -69,7 +70,7 @@ export default function NodeModal({ data }: { data: NodeDataType }) {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Close</span>
|
<span className="sr-only">Close</span>
|
||||||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
<X className="h-6 w-6" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-full w-full flex flex-col justify-center items-center">
|
<div className="h-full w-full flex flex-col justify-center items-center">
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { LockClosedIcon, PaperAirplaneIcon } from "@heroicons/react/24/outline";
|
|
||||||
import { classNames } from "../../../utils";
|
import { classNames } from "../../../utils";
|
||||||
import { useContext, useEffect, useRef, useState } from "react";
|
import { useContext, useEffect, useRef, useState } from "react";
|
||||||
import { TabsContext } from "../../../contexts/tabsContext";
|
import { TabsContext } from "../../../contexts/tabsContext";
|
||||||
import { INPUT_STYLE } from "../../../constants";
|
import { INPUT_STYLE } from "../../../constants";
|
||||||
|
import { Lock, Send } from "lucide-react";
|
||||||
|
|
||||||
export default function ChatInput({
|
export default function ChatInput({
|
||||||
lockChat,
|
lockChat,
|
||||||
chatValue,
|
chatValue,
|
||||||
|
|
@ -10,6 +11,12 @@ export default function ChatInput({
|
||||||
setChatValue,
|
setChatValue,
|
||||||
inputRef,
|
inputRef,
|
||||||
}) {
|
}) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (!lockChat && inputRef.current) {
|
||||||
|
inputRef.current.focus();
|
||||||
|
}
|
||||||
|
}, [lockChat, inputRef]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (inputRef.current) {
|
if (inputRef.current) {
|
||||||
inputRef.current.style.height = "inherit"; // Reset the height
|
inputRef.current.style.height = "inherit"; // Reset the height
|
||||||
|
|
@ -54,13 +61,13 @@ export default function ChatInput({
|
||||||
<div className="absolute bottom-0.5 right-3">
|
<div className="absolute bottom-0.5 right-3">
|
||||||
<button disabled={lockChat} onClick={() => sendMessage()}>
|
<button disabled={lockChat} onClick={() => sendMessage()}>
|
||||||
{lockChat ? (
|
{lockChat ? (
|
||||||
<LockClosedIcon
|
<Lock
|
||||||
className="h-5 w-5 text-ring animate-pulse"
|
className="h-5 w-5 text-gray-500 dark:hover:text-gray-300 animate-pulse"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<PaperAirplaneIcon
|
<Send
|
||||||
className="h-5 w-5 text-ring hover:text-muted-foreground"
|
className="h-5 w-5 text-gray-500 hover:text-gray-600 dark:hover:text-gray-300"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
import { ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline";
|
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { ChatMessageType } from "../../../types/chat";
|
import { ChatMessageType } from "../../../types/chat";
|
||||||
import { classNames } from "../../../utils";
|
import { classNames } from "../../../utils";
|
||||||
import AiIcon from "../../../assets/Gooey Ring-5s-271px.svg";
|
import AiIcon from "../../../assets/Gooey Ring-5s-271px.svg";
|
||||||
import AiIconStill from "../../../assets/froze-flow.png";
|
import AiIconStill from "../../../assets/froze-flow.png";
|
||||||
import { UserIcon } from "@heroicons/react/24/solid";
|
|
||||||
import FileCard from "../fileComponent";
|
import FileCard from "../fileComponent";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import rehypeMathjax from "rehype-mathjax";
|
import rehypeMathjax from "rehype-mathjax";
|
||||||
|
|
@ -12,6 +10,7 @@ import remarkGfm from "remark-gfm";
|
||||||
import remarkMath from "remark-math";
|
import remarkMath from "remark-math";
|
||||||
import { CodeBlock } from "./codeBlock";
|
import { CodeBlock } from "./codeBlock";
|
||||||
import Convert from "ansi-to-html";
|
import Convert from "ansi-to-html";
|
||||||
|
import { User2, MessageCircle } from "lucide-react";
|
||||||
|
|
||||||
export default function ChatMessage({
|
export default function ChatMessage({
|
||||||
chat,
|
chat,
|
||||||
|
|
@ -62,7 +61,7 @@ export default function ChatMessage({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{chat.isSend && (
|
{chat.isSend && (
|
||||||
<UserIcon className="w-6 h-6 -mb-1 text-foreground " />
|
<User2 className="w-6 h-6 -mb-1 text-gray-800 dark:text-gray-200" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!chat.isSend ? (
|
{!chat.isSend ? (
|
||||||
|
|
@ -73,7 +72,7 @@ export default function ChatMessage({
|
||||||
onClick={() => setHidden((prev) => !prev)}
|
onClick={() => setHidden((prev) => !prev)}
|
||||||
className="absolute -top-1 -left-2 cursor-pointer"
|
className="absolute -top-1 -left-2 cursor-pointer"
|
||||||
>
|
>
|
||||||
<ChatBubbleOvalLeftEllipsisIcon className="w-5 h-5 animate-bounce" />
|
<MessageCircle className="w-5 h-5 animate-bounce dark:text-white" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{chat.thought && chat.thought !== "" && !hidden && (
|
{chat.thought && chat.thought !== "" && !hidden && (
|
||||||
|
|
@ -152,7 +151,7 @@ export default function ChatMessage({
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full flex items-center">
|
<div className="w-full flex items-center">
|
||||||
<div className="text-start inline-block px-3 text-sm text-muted-foreground">
|
<div className="text-start inline-block px-3 text-gray-600 dark:text-white">
|
||||||
<span
|
<span
|
||||||
className="text-muted-foreground "
|
className="text-muted-foreground "
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { CloudArrowDownIcon, DocumentIcon } from "@heroicons/react/24/outline";
|
|
||||||
import * as base64js from "base64-js";
|
import * as base64js from "base64-js";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { DownloadCloud, File } from "lucide-react";
|
||||||
|
|
||||||
export default function FileCard({ fileName, content, fileType }) {
|
export default function FileCard({ fileName, content, fileType }) {
|
||||||
const handleDownload = () => {
|
const handleDownload = () => {
|
||||||
|
|
@ -43,7 +43,7 @@ export default function FileCard({ fileName, content, fileType }) {
|
||||||
className="text-ring py-1 px-2 "
|
className="text-ring py-1 px-2 "
|
||||||
onClick={handleDownload}
|
onClick={handleDownload}
|
||||||
>
|
>
|
||||||
<CloudArrowDownIcon className="hover:scale-110 w-5 h-5 text-current"></CloudArrowDownIcon>
|
<DownloadCloud className="hover:scale-110 w-5 h-5 text-current" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -65,14 +65,14 @@ export default function FileCard({ fileName, content, fileType }) {
|
||||||
className="w-8 h-8"
|
className="w-8 h-8"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<DocumentIcon className="w-8 h-8" />
|
<File className="w-8 h-8" />
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col items-start">
|
<div className="flex flex-col items-start">
|
||||||
{" "}
|
{" "}
|
||||||
<div className="truncate text-sm text-current">{fileName}</div>
|
<div className="truncate text-sm text-current">{fileName}</div>
|
||||||
<div className="truncate text-xs text-ring">{fileType}</div>
|
<div className="truncate text-xs text-ring">{fileType}</div>
|
||||||
</div>
|
</div>
|
||||||
<CloudArrowDownIcon className="w-6 h-6 text-current ml-auto" />
|
<DownloadCloud className="w-6 h-6 text-current ml-auto" />
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline";
|
|
||||||
import { Fragment, useContext, useEffect, useRef, useState } from "react";
|
import { Fragment, useContext, useEffect, useRef, useState } from "react";
|
||||||
import { FlowType } from "../../types/flow";
|
import { FlowType } from "../../types/flow";
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
import { validateNodes } from "../../utils";
|
import { validateNodes } from "../../utils";
|
||||||
import { typesContext } from "../../contexts/typesContext";
|
import { typesContext } from "../../contexts/typesContext";
|
||||||
import ChatMessage from "./chatMessage";
|
import ChatMessage from "./chatMessage";
|
||||||
import { FaEraser } from "react-icons/fa";
|
import { X, MessagesSquare, Eraser } from "lucide-react";
|
||||||
import { HiX } from "react-icons/hi";
|
|
||||||
import { sendAllProps } from "../../types/api";
|
import { sendAllProps } from "../../types/api";
|
||||||
import { ChatMessageType } from "../../types/chat";
|
import { ChatMessageType } from "../../types/chat";
|
||||||
import ChatInput from "./chatInput";
|
import ChatInput from "./chatInput";
|
||||||
|
|
@ -352,13 +350,13 @@ export default function ChatModal({
|
||||||
onClick={() => clearChat()}
|
onClick={() => clearChat()}
|
||||||
className="absolute top-2 right-10 hover:text-status-red text-muted-foreground z-30"
|
className="absolute top-2 right-10 hover:text-status-red text-muted-foreground z-30"
|
||||||
>
|
>
|
||||||
<FaEraser className="w-4 h-4" />
|
<Eraser className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setModalOpen(false)}
|
onClick={() => setModalOpen(false)}
|
||||||
className="absolute top-1.5 right-2 hover:text-status-red text-muted-foreground z-30"
|
className="absolute top-1.5 right-2 hover:text-status-red text-muted-foreground z-30"
|
||||||
>
|
>
|
||||||
<HiX className="w-5 h-5" />
|
<X className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
@ -387,7 +385,7 @@ export default function ChatModal({
|
||||||
<span className="text-base text-ring">
|
<span className="text-base text-ring">
|
||||||
Start a conversation and click the agent’s thoughts{" "}
|
Start a conversation and click the agent’s thoughts{" "}
|
||||||
<span>
|
<span>
|
||||||
<ChatBubbleOvalLeftEllipsisIcon className="w-6 h-6 inline animate-bounce " />
|
<MessagesSquare className="w-5 h-5 inline animate-bounce mx-1 " />
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
to inspect the chaining process.
|
to inspect the chaining process.
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import { XMarkIcon, CommandLineIcon } from "@heroicons/react/24/outline";
|
|
||||||
import { Fragment, useContext, useRef, useState } from "react";
|
import { Fragment, useContext, useRef, useState } from "react";
|
||||||
import { PopUpContext } from "../../contexts/popUpContext";
|
import { PopUpContext } from "../../contexts/popUpContext";
|
||||||
import AceEditor from "react-ace";
|
import AceEditor from "react-ace";
|
||||||
|
|
@ -22,6 +21,7 @@ import {
|
||||||
} from "../../components/ui/dialog";
|
} from "../../components/ui/dialog";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { CODE_PROMPT_DIALOG_SUBTITLE } from "../../constants";
|
import { CODE_PROMPT_DIALOG_SUBTITLE } from "../../constants";
|
||||||
|
import { TerminalSquare } from "lucide-react";
|
||||||
|
|
||||||
export default function CodeAreaModal({
|
export default function CodeAreaModal({
|
||||||
value,
|
value,
|
||||||
|
|
@ -51,8 +51,8 @@ export default function CodeAreaModal({
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center">
|
<DialogTitle className="flex items-center">
|
||||||
<span className="pr-2">Edit Code</span>
|
<span className="pr-2">Edit Code</span>
|
||||||
<CommandLineIcon
|
<TerminalSquare
|
||||||
className="h-6 w-6 text-foreground pl-1"
|
className="h-6 w-6 text-gray-800 pl-1 dark:text-white"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue