Merge branch 'dev'
This commit is contained in:
commit
004440740c
99 changed files with 26518 additions and 16990 deletions
34
.devcontainer/devcontainer.json
Normal file
34
.devcontainer/devcontainer.json
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/universal
|
||||
{
|
||||
"name": "Default Linux Universal",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"image": "mcr.microsoft.com/devcontainers/universal:2-linux",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/aws-cli:1": {}
|
||||
},
|
||||
"customizations": {
|
||||
"vscode": {"extensions": [
|
||||
"actboy168.tasks",
|
||||
"GitHub.copilot",
|
||||
"ms-python.python",
|
||||
"sourcery.sourcery",
|
||||
"eamodio.gitlens"
|
||||
]}
|
||||
}
|
||||
|
||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||
// "features": {},
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// "postCreateCommand": "uname -a",
|
||||
|
||||
// Configure tool-specific properties.
|
||||
// "customizations": {},
|
||||
|
||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "root"
|
||||
}
|
||||
130
.gitignore
vendored
130
.gitignore
vendored
|
|
@ -102,3 +102,133 @@ dist
|
|||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
notebooks
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
|
|
|||
66
Dockerfile
Normal file
66
Dockerfile
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# FROM logspace/backend_build as backend_build
|
||||
FROM logspace/frontend_build as frontend_build
|
||||
|
||||
# `python-base` sets up all our shared environment variables
|
||||
FROM python:3.10-slim as langflow_build
|
||||
|
||||
# python
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
# prevents python creating .pyc files
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
\
|
||||
# pip
|
||||
PIP_NO_CACHE_DIR=off \
|
||||
PIP_DISABLE_PIP_VERSION_CHECK=on \
|
||||
PIP_DEFAULT_TIMEOUT=100 \
|
||||
\
|
||||
# poetry
|
||||
# https://python-poetry.org/docs/configuration/#using-environment-variables
|
||||
POETRY_VERSION=1.4.0 \
|
||||
# make poetry install to this location
|
||||
POETRY_HOME="/opt/poetry" \
|
||||
# make poetry create the virtual environment in the project's root
|
||||
# it gets named `.venv`
|
||||
POETRY_VIRTUALENVS_IN_PROJECT=true \
|
||||
# do not ask any interactive question
|
||||
POETRY_NO_INTERACTION=1 \
|
||||
\
|
||||
# paths
|
||||
# this is where our requirements + virtual environment will live
|
||||
PYSETUP_PATH="/opt/pysetup" \
|
||||
VENV_PATH="/opt/pysetup/.venv"
|
||||
|
||||
# prepend poetry and venv to path
|
||||
ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH"
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends -y \
|
||||
# deps for installing poetry
|
||||
curl \
|
||||
# deps for building python deps
|
||||
build-essential libpq-dev git
|
||||
|
||||
# install poetry - respects $POETRY_VERSION & $POETRY_HOME
|
||||
# RUN curl -sSL https://install.python-poetry.org | python3 -
|
||||
|
||||
# copy project requirement files here to ensure they will be cached.
|
||||
WORKDIR /app
|
||||
COPY pyproject.toml ./
|
||||
# copy langflow
|
||||
COPY ./langflow ./langflow
|
||||
|
||||
# Copy files from frontend
|
||||
COPY --from=frontend_build /app/build /app/langflow/frontend/build/
|
||||
|
||||
|
||||
|
||||
# RUN pip install langflow-0.0.17-py3-none-any.whl
|
||||
RUN pip install .
|
||||
# RUN poetry add dist/langflow-0.0.17-py3-none-any.whl
|
||||
# RUN rm *.whl
|
||||
|
||||
# RUN poetry build
|
||||
|
||||
EXPOSE 5003
|
||||
|
||||
CMD [ "langflow" ]
|
||||
12
build_and_push
Executable file
12
build_and_push
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
#! /bin/bash
|
||||
|
||||
cd langflow/frontend
|
||||
docker build -t logspace/frontend_build -f build.Dockerfile .
|
||||
cd ../backend
|
||||
docker build -t logspace/backend_build -f build.Dockerfile .
|
||||
|
||||
cd ../../
|
||||
VERSION=$(toml get --toml-path pyproject.toml tool.poetry.version)
|
||||
docker build --build-arg VERSION=$VERSION -t ibiscp/langflow:$VERSION .
|
||||
# docker push ibiscp/langflow:$VERSION
|
||||
# poetry add --editable ../../../langchain
|
||||
20
dev.Dockerfile
Normal file
20
dev.Dockerfile
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.10
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install Poetry
|
||||
RUN apt-get update && apt-get install -y curl
|
||||
RUN curl -sSL https://install.python-poetry.org | python3 -
|
||||
# Add Poetry to PATH
|
||||
ENV PATH="${PATH}:/root/.local/bin"
|
||||
# Copy the pyproject.toml and poetry.lock files
|
||||
COPY poetry.lock pyproject.toml ./
|
||||
# Copy the rest of the application codes
|
||||
COPY ./ ./
|
||||
|
||||
|
||||
# install dependencies
|
||||
RUN poetry config virtualenvs.create false && poetry install --no-interaction --no-ansi
|
||||
|
||||
|
||||
CMD ["uvicorn", "langflow.cli:app", "--host", "0.0.0.0", "--port", "5003"]
|
||||
20
docker-compose.yml
Normal file
20
docker-compose.yml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
backend:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: ./dev.Dockerfile
|
||||
ports:
|
||||
- "5003:5003"
|
||||
volumes:
|
||||
- ./:/app
|
||||
environment:
|
||||
- PYTHONPATH=/langflow # add this line to set PYTHONPATH
|
||||
|
||||
frontend:
|
||||
build: ./langflow/frontend
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- ./langflow/frontend:/app
|
||||
0
langflow/__init__.py
Normal file
0
langflow/__init__.py
Normal file
130
langflow/backend/.gitignore
vendored
Normal file
130
langflow/backend/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
notebooks
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
14
langflow/backend/Dockerfile
Normal file
14
langflow/backend/Dockerfile
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
FROM logspace/backend_build as backend_build
|
||||
|
||||
FROM python:3.10-slim
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update && apt-get install git -y
|
||||
|
||||
COPY --from=backend_build /app/dist/*.whl /app/
|
||||
RUN pip install langflow-0.0.19-py3-none-any.whl
|
||||
RUN rm *.whl
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD [ "uvicorn", "--host", "0.0.0.0", "--port", "80", "langflow.backend.app:app" ]
|
||||
0
langflow/backend/__init__.py
Normal file
0
langflow/backend/__init__.py
Normal file
9
langflow/backend/allowed_components.py
Normal file
9
langflow/backend/allowed_components.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
CHAINS = ["LLMChain", "LLMMathChain", "LLMRequestsChain", "LLMBashChain"]
|
||||
|
||||
AGENTS = ["ZeroShotAgent"]
|
||||
|
||||
PROMPTS = ["PromptTemplate", "FewShotPromptTemplate"]
|
||||
|
||||
LLMS = ["OpenAI"]
|
||||
|
||||
TOOLS = ["Search", "requests_get", "PAL-MATH", "Calculator"]
|
||||
30
langflow/backend/app.py
Normal file
30
langflow/backend/app.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
from fastapi import FastAPI
|
||||
from langflow.backend.endpoints import router as endpoints_router
|
||||
from langflow.backend.list_endpoints import router as list_router
|
||||
from langflow.backend.signature import router as signatures_router
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
|
||||
def create_app():
|
||||
"""Create the FastAPI app and include the router."""
|
||||
app = FastAPI()
|
||||
|
||||
origins = [
|
||||
"*",
|
||||
]
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=origins,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
app.include_router(endpoints_router)
|
||||
app.include_router(list_router)
|
||||
app.include_router(signatures_router)
|
||||
return app
|
||||
|
||||
|
||||
app = create_app()
|
||||
54
langflow/backend/build.Dockerfile
Normal file
54
langflow/backend/build.Dockerfile
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# `python-base` sets up all our shared environment variables
|
||||
FROM python:3.10-slim
|
||||
|
||||
# python
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
# prevents python creating .pyc files
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
\
|
||||
# pip
|
||||
PIP_NO_CACHE_DIR=off \
|
||||
PIP_DISABLE_PIP_VERSION_CHECK=on \
|
||||
PIP_DEFAULT_TIMEOUT=100 \
|
||||
\
|
||||
# poetry
|
||||
# https://python-poetry.org/docs/configuration/#using-environment-variables
|
||||
POETRY_VERSION=1.4.0 \
|
||||
# make poetry install to this location
|
||||
POETRY_HOME="/opt/poetry" \
|
||||
# make poetry create the virtual environment in the project's root
|
||||
# it gets named `.venv`
|
||||
POETRY_VIRTUALENVS_IN_PROJECT=true \
|
||||
# do not ask any interactive question
|
||||
POETRY_NO_INTERACTION=1 \
|
||||
\
|
||||
# paths
|
||||
# this is where our requirements + virtual environment will live
|
||||
PYSETUP_PATH="/opt/pysetup" \
|
||||
VENV_PATH="/opt/pysetup/.venv"
|
||||
|
||||
# prepend poetry and venv to path
|
||||
ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH"
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends -y \
|
||||
# deps for installing poetry
|
||||
curl \
|
||||
# deps for building python deps
|
||||
build-essential libpq-dev
|
||||
|
||||
# install poetry - respects $POETRY_VERSION & $POETRY_HOME
|
||||
RUN curl -sSL https://install.python-poetry.org | python3 -
|
||||
|
||||
# copy project requirement files here to ensure they will be cached.
|
||||
WORKDIR /app
|
||||
COPY poetry.lock pyproject.toml ./
|
||||
|
||||
# install runtime deps - uses $POETRY_VIRTUALENVS_IN_PROJECT internally
|
||||
RUN poetry install --without dev
|
||||
RUN poetry add "git+https://github.com/ibiscp/langchain.git#ibis"
|
||||
|
||||
COPY *.py langflow/backend/
|
||||
RUN rm langflow/backend/dev.py
|
||||
|
||||
RUN poetry build
|
||||
8
langflow/backend/build_and_push
Executable file
8
langflow/backend/build_and_push
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
#! /bin/bash
|
||||
|
||||
poetry remove langchain
|
||||
docker build -t logspace/backend_build -f build.Dockerfile .
|
||||
VERSION=$(toml get --toml-path pyproject.toml tool.poetry.version)
|
||||
docker build --build-arg VERSION=$VERSION -t ibiscp/langflow_backend:$VERSION .
|
||||
docker push ibiscp/langflow_backend:$VERSION
|
||||
poetry add --editable ../../../langchain
|
||||
40
langflow/backend/customs.py
Normal file
40
langflow/backend/customs.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
from langchain.agents.mrkl import prompt
|
||||
|
||||
|
||||
def get_custom_prompts():
|
||||
return {
|
||||
"ZeroShotPrompt": {
|
||||
"template": {
|
||||
"_type": "zero_shot",
|
||||
"prefix": {
|
||||
"type": "str",
|
||||
"required": False,
|
||||
"placeholder": "",
|
||||
"list": False,
|
||||
"show": True,
|
||||
"multiline": True,
|
||||
"value": prompt.PREFIX,
|
||||
},
|
||||
"suffix": {
|
||||
"type": "str",
|
||||
"required": True,
|
||||
"placeholder": "",
|
||||
"list": False,
|
||||
"show": True,
|
||||
"multiline": True,
|
||||
"value": prompt.SUFFIX,
|
||||
},
|
||||
"format_instructions": {
|
||||
"type": "str",
|
||||
"required": False,
|
||||
"placeholder": "",
|
||||
"list": False,
|
||||
"show": True,
|
||||
"multiline": True,
|
||||
"value": prompt.FORMAT_INSTRUCTIONS,
|
||||
},
|
||||
},
|
||||
"description": "Prompt template for Zero Shot Agent.",
|
||||
"base_classes": ["BasePromptTemplate"],
|
||||
}
|
||||
}
|
||||
10
langflow/backend/dev.py
Normal file
10
langflow/backend/dev.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import uvicorn
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
path = Path(__file__)
|
||||
sys.path.append(str(path.parent.parent.parent))
|
||||
|
||||
from app import app
|
||||
|
||||
uvicorn.run(app, host="0.0.0.0", port=5003)
|
||||
203
langflow/backend/endpoints.py
Normal file
203
langflow/backend/endpoints.py
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
from fastapi import APIRouter
|
||||
from langflow.backend import signature
|
||||
from langflow.backend import list_endpoints
|
||||
from langflow.backend import payload
|
||||
from langchain.agents.loading import load_agent_executor_from_config
|
||||
from langchain.chains.loading import load_chain_from_config
|
||||
from langchain.llms.loading import load_llm_from_config
|
||||
|
||||
from langchain.prompts.loading import load_prompt_from_config
|
||||
from typing import Any
|
||||
import io
|
||||
import contextlib
|
||||
import re
|
||||
|
||||
|
||||
# build router
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def get_type_list():
|
||||
all_types = get_all()
|
||||
|
||||
all_types.pop("tools")
|
||||
|
||||
for key, value in all_types.items():
|
||||
all_types[key] = [item["template"]["_type"] for item in value.values()]
|
||||
|
||||
return all_types
|
||||
|
||||
|
||||
@router.get("/all")
|
||||
def get_all():
|
||||
return {
|
||||
"chains": {
|
||||
chain: signature.get_chain(chain) for chain in list_endpoints.list_chains()
|
||||
},
|
||||
"agents": {
|
||||
agent: signature.get_agent(agent) for agent in list_endpoints.list_agents()
|
||||
},
|
||||
"prompts": {
|
||||
prompt: signature.get_prompt(prompt)
|
||||
for prompt in list_endpoints.list_prompts()
|
||||
},
|
||||
"llms": {llm: signature.get_llm(llm) for llm in list_endpoints.list_llms()},
|
||||
# "utilities": {
|
||||
# "template": {
|
||||
# # utility: templates.utility(utility) for utility in list.list_utilities()
|
||||
# }
|
||||
# },
|
||||
"memories": {
|
||||
memory: signature.get_memory(memory)
|
||||
for memory in list_endpoints.list_memories()
|
||||
},
|
||||
# "document_loaders": {
|
||||
# "template": {
|
||||
# # memory: templates.document_loader(memory)
|
||||
# # for memory in list.list_document_loaders()
|
||||
# }
|
||||
# },
|
||||
# "vectorstores": {"template": {}},
|
||||
# "docstores": {"template": {}},
|
||||
# "tools": {
|
||||
# tool: {"template": signature.tool(tool), **values}
|
||||
# for tool, values in tools.items()
|
||||
# },
|
||||
"tools": {
|
||||
tool: signature.get_tool(tool) for tool in list_endpoints.list_tools()
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@router.post("/predict")
|
||||
def get_load(data: dict[str, Any]):
|
||||
# Get type list
|
||||
type_list = get_type_list()
|
||||
|
||||
# Substitute ZeroShotPromt with PromptTemplate
|
||||
for node in data["nodes"]:
|
||||
if node["data"]["type"] == "ZeroShotPrompt":
|
||||
# Build Prompt Template
|
||||
tools = [
|
||||
tool
|
||||
for tool in data["nodes"]
|
||||
if tool["type"] != "chatOutputNode"
|
||||
and "Tool" in tool["data"]["node"]["base_classes"]
|
||||
]
|
||||
node["data"] = build_prompt_template(prompt=node["data"], tools=tools)
|
||||
break
|
||||
|
||||
# Add input variables
|
||||
data = payload.extract_input_variables(data)
|
||||
|
||||
# Nodes, edges and root node
|
||||
message = data["message"]
|
||||
nodes = data["nodes"]
|
||||
edges = data["edges"]
|
||||
root = payload.get_root_node(data)
|
||||
|
||||
extracted_json = payload.build_json(root, nodes, edges)
|
||||
|
||||
# Build json
|
||||
if extracted_json["_type"] in type_list["agents"]:
|
||||
loaded = load_agent_executor_from_config(extracted_json)
|
||||
|
||||
with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer):
|
||||
result = loaded.run(message)
|
||||
thought = output_buffer.getvalue()
|
||||
|
||||
elif extracted_json["_type"] in type_list["chains"]:
|
||||
loaded = load_chain_from_config(extracted_json)
|
||||
|
||||
with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer):
|
||||
result = loaded.run(message)
|
||||
thought = output_buffer.getvalue()
|
||||
|
||||
elif extracted_json["_type"] in type_list["llms"]:
|
||||
loaded = load_llm_from_config(extracted_json)
|
||||
|
||||
with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer):
|
||||
result = loaded(message)
|
||||
thought = output_buffer.getvalue()
|
||||
else:
|
||||
result = "Error: Type should be either agent, chain or llm"
|
||||
thought = ""
|
||||
|
||||
return {
|
||||
"result": result,
|
||||
"thought": re.sub(
|
||||
r"\x1b\[([0-9,A-Z]{1,2}(;[0-9,A-Z]{1,2})?)?[m|K]", "", thought
|
||||
).strip(),
|
||||
}
|
||||
|
||||
|
||||
def build_prompt_template(prompt, tools):
|
||||
prefix = prompt["node"]["template"]["prefix"]["value"]
|
||||
suffix = prompt["node"]["template"]["suffix"]["value"]
|
||||
format_instructions = prompt["node"]["template"]["format_instructions"]["value"]
|
||||
|
||||
tool_strings = "\n".join(
|
||||
[
|
||||
f"{tool['data']['node']['name']}: {tool['data']['node']['description']}"
|
||||
for tool in tools
|
||||
]
|
||||
)
|
||||
tool_names = ", ".join([tool["data"]["node"]["name"] for tool in tools])
|
||||
format_instructions = format_instructions.format(tool_names=tool_names)
|
||||
value = "\n\n".join([prefix, tool_strings, format_instructions, suffix])
|
||||
|
||||
prompt["type"] = "PromptTemplate"
|
||||
|
||||
prompt["node"] = {
|
||||
"template": {
|
||||
"_type": "prompt",
|
||||
"input_variables": {
|
||||
"type": "str",
|
||||
"required": True,
|
||||
"placeholder": "",
|
||||
"list": True,
|
||||
"show": False,
|
||||
"multiline": False,
|
||||
},
|
||||
"output_parser": {
|
||||
"type": "BaseOutputParser",
|
||||
"required": False,
|
||||
"placeholder": "",
|
||||
"list": False,
|
||||
"show": False,
|
||||
"multline": False,
|
||||
"value": None,
|
||||
},
|
||||
"template": {
|
||||
"type": "str",
|
||||
"required": True,
|
||||
"placeholder": "",
|
||||
"list": False,
|
||||
"show": True,
|
||||
"multiline": True,
|
||||
"value": value,
|
||||
},
|
||||
"template_format": {
|
||||
"type": "str",
|
||||
"required": False,
|
||||
"placeholder": "",
|
||||
"list": False,
|
||||
"show": False,
|
||||
"multline": False,
|
||||
"value": "f-string",
|
||||
},
|
||||
"validate_template": {
|
||||
"type": "bool",
|
||||
"required": False,
|
||||
"placeholder": "",
|
||||
"list": False,
|
||||
"show": False,
|
||||
"multline": False,
|
||||
"value": True,
|
||||
},
|
||||
},
|
||||
"description": "Schema to represent a prompt for an LLM.",
|
||||
"base_classes": ["BasePromptTemplate"],
|
||||
}
|
||||
|
||||
return prompt
|
||||
127
langflow/backend/list_endpoints.py
Normal file
127
langflow/backend/list_endpoints.py
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
from fastapi import APIRouter
|
||||
|
||||
from langchain import chains
|
||||
from langchain import agents
|
||||
from langchain import prompts
|
||||
from langchain import llms
|
||||
from langchain.chains.conversation import memory as memories
|
||||
from langchain.agents.load_tools import get_all_tool_names
|
||||
from langflow.backend import util
|
||||
from langflow.backend import customs
|
||||
from langflow.backend import allowed_components
|
||||
|
||||
|
||||
# build router
|
||||
router = APIRouter(
|
||||
prefix="/list",
|
||||
tags=["list"],
|
||||
)
|
||||
|
||||
|
||||
@router.get("/")
|
||||
def read_items():
|
||||
"""List all components"""
|
||||
return [
|
||||
"chains",
|
||||
"agents",
|
||||
"prompts",
|
||||
"llms",
|
||||
# "utilities",
|
||||
# "memories",
|
||||
# "document_loaders",
|
||||
# "vectorstores",
|
||||
# "docstores",
|
||||
"tools",
|
||||
]
|
||||
|
||||
|
||||
@router.get("/chains")
|
||||
def list_chains():
|
||||
"""List all chain types"""
|
||||
return [
|
||||
chain.__annotations__["return"].__name__
|
||||
for chain in chains.loading.type_to_loader_dict.values()
|
||||
if chain.__annotations__["return"].__name__ in allowed_components.CHAINS
|
||||
]
|
||||
|
||||
|
||||
@router.get("/agents")
|
||||
def list_agents():
|
||||
"""List all agent types"""
|
||||
# return list(agents.loading.AGENT_TO_CLASS.keys())
|
||||
return [
|
||||
agent.__name__
|
||||
for agent in agents.loading.AGENT_TO_CLASS.values()
|
||||
if agent.__name__ in allowed_components.AGENTS
|
||||
]
|
||||
|
||||
|
||||
@router.get("/prompts")
|
||||
def list_prompts():
|
||||
"""List all prompt types"""
|
||||
custom_prompts = customs.get_custom_prompts()
|
||||
library_prompts = [
|
||||
prompt.__annotations__["return"].__name__
|
||||
for prompt in prompts.loading.type_to_loader_dict.values()
|
||||
if prompt.__annotations__["return"].__name__ in allowed_components.PROMPTS
|
||||
]
|
||||
return library_prompts + list(custom_prompts.keys())
|
||||
|
||||
|
||||
@router.get("/llms")
|
||||
def list_llms():
|
||||
"""List all llm types"""
|
||||
return [
|
||||
llm.__name__
|
||||
for llm in llms.type_to_cls_dict.values()
|
||||
if llm.__name__ in allowed_components.LLMS
|
||||
]
|
||||
|
||||
|
||||
@router.get("/memories")
|
||||
def list_memories():
|
||||
"""List all memory types"""
|
||||
return [memory.__name__ for memory in memories.type_to_cls_dict.values()]
|
||||
|
||||
|
||||
# @router.get("/utilities")
|
||||
# def list_utilities():
|
||||
# """List all utility types"""
|
||||
# return list(utilities.__all__)
|
||||
|
||||
|
||||
# @router.get("/document_loaders")
|
||||
# def list_document_loaders():
|
||||
# """List all document loader types"""
|
||||
# return list(document_loaders.__all__)
|
||||
|
||||
|
||||
# @router.get("/vectorstores")
|
||||
# def list_vectorstores():
|
||||
# """List all vector store types"""
|
||||
# return list(vectorstores.__all__)
|
||||
|
||||
|
||||
# @router.get("/docstores")
|
||||
# def list_docstores():
|
||||
# """List all document store types"""
|
||||
# return list(docstore.__all__)
|
||||
|
||||
|
||||
@router.get("/tools")
|
||||
def list_tools():
|
||||
"""List all load tools"""
|
||||
|
||||
tools = []
|
||||
|
||||
for tool in get_all_tool_names():
|
||||
tool_params = util.get_tool_params(util.get_tools_dict(tool))
|
||||
if tool_params and tool_params["name"] in allowed_components.TOOLS:
|
||||
tools.append(tool_params["name"])
|
||||
|
||||
return tools
|
||||
|
||||
# return [
|
||||
# util.get_tool_params(util.get_tools_dict(tool))["name"]
|
||||
# for tool in get_all_tool_names()
|
||||
# ]
|
||||
92
langflow/backend/payload.py
Normal file
92
langflow/backend/payload.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import re
|
||||
|
||||
|
||||
def extract_input_variables(data):
|
||||
"""
|
||||
Extracts input variables from the template and adds them to the input_variables field.
|
||||
"""
|
||||
for node in data["nodes"]:
|
||||
try:
|
||||
if "input_variables" in node["data"]["node"]["template"]:
|
||||
if node["data"]["node"]["template"]["_type"] == "prompt":
|
||||
variables = re.findall(
|
||||
r"\{(.*?)\}",
|
||||
node["data"]["node"]["template"]["template"]["value"],
|
||||
)
|
||||
elif node["data"]["node"]["template"]["_type"] == "few_shot":
|
||||
variables = re.findall(
|
||||
r"\{(.*?)\}",
|
||||
node["data"]["node"]["template"]["prefix"]["value"]
|
||||
+ node["data"]["node"]["template"]["suffix"]["value"],
|
||||
)
|
||||
else:
|
||||
variables = []
|
||||
node["data"]["node"]["template"]["input_variables"]["value"] = variables
|
||||
except:
|
||||
pass
|
||||
return data
|
||||
|
||||
|
||||
def get_root_node(data):
|
||||
"""
|
||||
Returns the root node of the template.
|
||||
"""
|
||||
root = None
|
||||
incoming_edges = {edge["source"] for edge in data["edges"]}
|
||||
for node in data["nodes"]:
|
||||
if node["id"] not in incoming_edges:
|
||||
root = node
|
||||
break
|
||||
return root
|
||||
|
||||
|
||||
def build_json(root, nodes, edges):
|
||||
edge_ids = [edge["source"] for edge in edges if edge["target"] == root["id"]]
|
||||
local_nodes = [node for node in nodes if node["id"] in edge_ids]
|
||||
|
||||
if "node" not in root["data"]:
|
||||
return build_json(local_nodes[0], nodes, edges)
|
||||
|
||||
final_dict = root["data"]["node"]["template"].copy()
|
||||
|
||||
for key, value in final_dict.items():
|
||||
if key == "_type":
|
||||
continue
|
||||
# elif key == "prompt":
|
||||
# pass
|
||||
|
||||
module_type = value["type"]
|
||||
# if module_type == "Tool":
|
||||
# pass
|
||||
# if module_type in ["str", "bool", "int", "float", "Any"] or value["value"]:
|
||||
# value = value["value"]
|
||||
if "value" in value and value["value"] is not None:
|
||||
value = value["value"]
|
||||
elif "dict" in module_type:
|
||||
value = {}
|
||||
else:
|
||||
# if value['list']:
|
||||
# print(key)
|
||||
children = []
|
||||
for c in local_nodes:
|
||||
module_types = [c["data"]["type"]]
|
||||
if "node" in c["data"]:
|
||||
module_types += c["data"]["node"]["base_classes"]
|
||||
if module_type in module_types:
|
||||
children.append(c)
|
||||
# children = [
|
||||
# c
|
||||
# for c in local_nodes
|
||||
# if module_type
|
||||
# in [c["data"]["type"]] + c["data"]["node"]["base_classes"]
|
||||
# ]
|
||||
# else:
|
||||
# children = next((c for c in local_nodes if type in [c['data']['type']] + c['data']['node']['base_classes']), None)
|
||||
if value["required"] and not children:
|
||||
raise ValueError(f"No child with type {module_type} found")
|
||||
values = [
|
||||
build_json(child, nodes, edges) for child in children
|
||||
] # if children else None
|
||||
value = list(values) if value["list"] else next(iter(values), None)
|
||||
final_dict[key] = value
|
||||
return final_dict
|
||||
2253
langflow/backend/poetry.lock
generated
Normal file
2253
langflow/backend/poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
27
langflow/backend/pyproject.toml
Normal file
27
langflow/backend/pyproject.toml
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
[tool.poetry]
|
||||
name = "langflow"
|
||||
version = "0.0.19"
|
||||
description = "Backend for Langflow"
|
||||
authors = ["Ibis Prevedello <ibiscp@gmail.com>"]
|
||||
# packages = [
|
||||
# { include = "*.py" }
|
||||
# ]
|
||||
# exclude = ['dev.py']
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10"
|
||||
openai = "^0.26.5"
|
||||
fastapi = "^0.91.0"
|
||||
uvicorn = "^0.20.0"
|
||||
beautifulsoup4 = "^4.11.2"
|
||||
google-search-results = "^2.4.1"
|
||||
google-api-python-client = "^2.79.0"
|
||||
langchain = {path = "../../../langchain", develop = true}
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
black = "^23.1.0"
|
||||
ipykernel = "^6.21.2"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
145
langflow/backend/signature.py
Normal file
145
langflow/backend/signature.py
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
from fastapi import APIRouter, HTTPException
|
||||
from langchain import agents, chains, llms, prompts
|
||||
from langchain.agents.load_tools import (
|
||||
_BASE_TOOLS,
|
||||
_EXTRA_LLM_TOOLS,
|
||||
_EXTRA_OPTIONAL_TOOLS,
|
||||
_LLM_TOOLS,
|
||||
get_all_tool_names,
|
||||
)
|
||||
from langchain.chains.conversation import memory as memories
|
||||
|
||||
from langflow.backend import util
|
||||
from langflow.backend import customs
|
||||
|
||||
# build router
|
||||
router = APIRouter(
|
||||
prefix="/signatures",
|
||||
tags=["signatures"],
|
||||
)
|
||||
|
||||
|
||||
@router.get("/chain")
|
||||
def get_chain(name: str):
|
||||
"""Get the signature of a chain."""
|
||||
try:
|
||||
return util.build_template_from_function(
|
||||
name, chains.loading.type_to_loader_dict
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=404, detail="Chain not found") from exc
|
||||
|
||||
|
||||
@router.get("/agent")
|
||||
def get_agent(name: str):
|
||||
"""Get the signature of an agent."""
|
||||
try:
|
||||
return util.build_template_from_class(name, agents.loading.AGENT_TO_CLASS)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=404, detail="Agent not found") from exc
|
||||
|
||||
|
||||
@router.get("/prompt")
|
||||
def get_prompt(name: str):
|
||||
"""Get the signature of a prompt."""
|
||||
try:
|
||||
if name in customs.get_custom_prompts().keys():
|
||||
return customs.get_custom_prompts()[name]
|
||||
return util.build_template_from_function(
|
||||
name, prompts.loading.type_to_loader_dict
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=404, detail="Prompt not found") from exc
|
||||
|
||||
|
||||
@router.get("/llm")
|
||||
def get_llm(name: str):
|
||||
"""Get the signature of an llm."""
|
||||
try:
|
||||
return util.build_template_from_class(name, llms.type_to_cls_dict)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=404, detail="LLM not found") from exc
|
||||
|
||||
|
||||
# @router.get("/utility")
|
||||
# def utility(name: str):
|
||||
# # Raise error if name is not in utilities
|
||||
# if name not in utilities.__all__:
|
||||
# raise Exception(f"Prompt {name} not found.")
|
||||
# _class = getattr(utilities, name)
|
||||
# return {
|
||||
# name: {name: value for (name, value) in value.__repr_args__() if name != "name"}
|
||||
# for name, value in _class.__fields__.items()
|
||||
# }
|
||||
|
||||
|
||||
@router.get("/memory")
|
||||
def get_memory(name: str):
|
||||
"""Get the signature of a memory."""
|
||||
try:
|
||||
return util.build_template_from_class(name, memories.type_to_cls_dict)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=404, detail="Memory not found") from exc
|
||||
|
||||
|
||||
# @router.get("/document_loader")
|
||||
# def document_loader(name: str):
|
||||
# # Raise error if name is not in document_loader
|
||||
# if name not in document_loaders.__all__:
|
||||
# raise Exception(f"Prompt {name} not found.")
|
||||
# _class = getattr(document_loaders, name)
|
||||
# return {
|
||||
# name: {name: value for (name, value) in value.__repr_args__() if name != "name"}
|
||||
# for name, value in _class.__fields__.items()
|
||||
# }
|
||||
|
||||
|
||||
@router.get("/tool")
|
||||
def get_tool(name: str):
|
||||
"""Get the signature of a tool."""
|
||||
|
||||
all_tools = {}
|
||||
for tool in get_all_tool_names():
|
||||
if tool_params := util.get_tool_params(util.get_tools_dict(tool)):
|
||||
all_tools[tool_params["name"]] = tool
|
||||
|
||||
# Raise error if name is not in tools
|
||||
if name not in all_tools.keys():
|
||||
raise HTTPException(status_code=404, detail=f"Tool {name} not found.")
|
||||
|
||||
type_dict = {
|
||||
"str": {
|
||||
"type": "str",
|
||||
"required": False,
|
||||
"list": False,
|
||||
"show": True,
|
||||
"placeholder": "",
|
||||
"value": "",
|
||||
},
|
||||
"llm": {"type": "BaseLLM", "required": True, "list": False, "show": True},
|
||||
}
|
||||
|
||||
tool_type = all_tools[name]
|
||||
|
||||
if tool_type in _BASE_TOOLS:
|
||||
params = []
|
||||
elif tool_type in _LLM_TOOLS:
|
||||
params = ["llm"]
|
||||
elif tool_type in _EXTRA_LLM_TOOLS:
|
||||
_, extra_keys = _EXTRA_LLM_TOOLS[tool_type]
|
||||
params = ["llm"] + extra_keys
|
||||
elif tool_type in _EXTRA_OPTIONAL_TOOLS:
|
||||
_, extra_keys = _EXTRA_OPTIONAL_TOOLS[tool_type]
|
||||
params = extra_keys
|
||||
|
||||
template = {
|
||||
param: (type_dict[param] if param == "llm" else type_dict["str"])
|
||||
for param in params
|
||||
}
|
||||
template["_type"] = tool_type
|
||||
|
||||
return {
|
||||
"template": template,
|
||||
**util.get_tool_params(util.get_tools_dict(tool_type)),
|
||||
"base_classes": ["Tool"],
|
||||
}
|
||||
289
langflow/backend/util.py
Normal file
289
langflow/backend/util.py
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
import ast
|
||||
import inspect
|
||||
import re
|
||||
import importlib
|
||||
|
||||
from langchain.agents.load_tools import *
|
||||
from langchain.agents.load_tools import (
|
||||
_BASE_TOOLS,
|
||||
_LLM_TOOLS,
|
||||
_EXTRA_LLM_TOOLS,
|
||||
_EXTRA_OPTIONAL_TOOLS,
|
||||
)
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def build_template_from_function(name: str, type_to_loader_dict: dict):
|
||||
classes = [
|
||||
item.__annotations__["return"].__name__ for item in type_to_loader_dict.values()
|
||||
]
|
||||
|
||||
# Raise error if name is not in chains
|
||||
if name not in classes:
|
||||
raise ValueError(f"{name} not found")
|
||||
|
||||
for _type, v in type_to_loader_dict.items():
|
||||
if v.__annotations__["return"].__name__ == name:
|
||||
_class = v.__annotations__["return"]
|
||||
|
||||
docs = get_class_doc(_class)
|
||||
|
||||
variables = {"_type": _type}
|
||||
for name, value in _class.__fields__.items():
|
||||
if name in ["callback_manager", "requests_wrapper"]:
|
||||
continue
|
||||
variables[name] = {}
|
||||
for name_, value_ in value.__repr_args__():
|
||||
if name_ == "default_factory":
|
||||
try:
|
||||
variables[name]["default"] = get_default_factory(
|
||||
module=_class.__base__.__module__, function=value_
|
||||
)
|
||||
except Exception:
|
||||
variables[name]["default"] = None
|
||||
elif name_ not in ["name"]:
|
||||
variables[name][name_] = value_
|
||||
|
||||
variables[name]["placeholder"] = (
|
||||
docs["Attributes"][name] if name in docs["Attributes"] else ""
|
||||
)
|
||||
|
||||
return {
|
||||
"template": format_dict(variables),
|
||||
"description": docs["Description"],
|
||||
"base_classes": get_base_classes(_class),
|
||||
}
|
||||
|
||||
|
||||
def build_template_from_class(name: str, type_to_cls_dict: dict):
|
||||
classes = [item.__name__ for item in type_to_cls_dict.values()]
|
||||
|
||||
# Raise error if name is not in chains
|
||||
if name not in classes:
|
||||
raise ValueError(f"{name} not found.")
|
||||
|
||||
for _type, v in type_to_cls_dict.items():
|
||||
if v.__name__ == name:
|
||||
_class = v
|
||||
|
||||
docs = get_class_doc(_class)
|
||||
|
||||
variables = {"_type": _type}
|
||||
for name, value in _class.__fields__.items():
|
||||
if name in ["callback_manager"]:
|
||||
continue
|
||||
variables[name] = {}
|
||||
for name_, value_ in value.__repr_args__():
|
||||
if name_ == "default_factory":
|
||||
try:
|
||||
variables[name]["default"] = get_default_factory(
|
||||
module=_class.__base__.__module__, function=value_
|
||||
)
|
||||
except Exception:
|
||||
variables[name]["default"] = None
|
||||
elif name_ not in ["name"]:
|
||||
variables[name][name_] = value_
|
||||
|
||||
variables[name]["placeholder"] = (
|
||||
docs["Attributes"][name] if name in docs["Attributes"] else ""
|
||||
)
|
||||
|
||||
return {
|
||||
"template": format_dict(variables),
|
||||
"description": docs["Description"],
|
||||
"base_classes": get_base_classes(_class),
|
||||
}
|
||||
|
||||
|
||||
def get_base_classes(cls):
|
||||
bases = cls.__bases__
|
||||
if not bases:
|
||||
return []
|
||||
else:
|
||||
result = []
|
||||
for base in bases:
|
||||
if any(type in base.__module__ for type in ["pydantic", "abc"]):
|
||||
continue
|
||||
result.append(base.__name__)
|
||||
result.extend(get_base_classes(base))
|
||||
return result
|
||||
|
||||
|
||||
def get_default_factory(module: str, function: str):
|
||||
pattern = r"<function (\w+)>"
|
||||
|
||||
if match := re.search(pattern, function):
|
||||
module = importlib.import_module(module)
|
||||
return getattr(module, match[1])()
|
||||
return None
|
||||
|
||||
|
||||
def get_tools_dict(name: Optional[str] = None):
|
||||
"""Get the tools dictionary."""
|
||||
tools = {
|
||||
**_BASE_TOOLS,
|
||||
**_LLM_TOOLS,
|
||||
**{k: v[0] for k, v in _EXTRA_LLM_TOOLS.items()},
|
||||
**{k: v[0] for k, v in _EXTRA_OPTIONAL_TOOLS.items()},
|
||||
}
|
||||
return tools[name] if name else tools
|
||||
|
||||
|
||||
def get_tool_params(func, **kwargs):
|
||||
# Parse the function code into an abstract syntax tree
|
||||
tree = ast.parse(inspect.getsource(func))
|
||||
|
||||
# Iterate over the statements in the abstract syntax tree
|
||||
for node in ast.walk(tree):
|
||||
# Find the first return statement
|
||||
if isinstance(node, ast.Return):
|
||||
tool = node.value
|
||||
if isinstance(tool, ast.Call):
|
||||
if tool.func.id == "Tool":
|
||||
if tool.keywords:
|
||||
tool_params = {}
|
||||
for keyword in tool.keywords:
|
||||
if keyword.arg == "name":
|
||||
tool_params["name"] = ast.literal_eval(keyword.value)
|
||||
elif keyword.arg == "description":
|
||||
tool_params["description"] = ast.literal_eval(
|
||||
keyword.value
|
||||
)
|
||||
return tool_params
|
||||
return {
|
||||
"name": ast.literal_eval(tool.args[0]),
|
||||
"description": ast.literal_eval(tool.args[2]),
|
||||
}
|
||||
else:
|
||||
# get the class object from the return statement
|
||||
try:
|
||||
class_obj = eval(
|
||||
compile(ast.Expression(tool), "<string>", "eval")
|
||||
)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
return {
|
||||
"name": getattr(class_obj, "name"),
|
||||
"description": getattr(class_obj, "description"),
|
||||
}
|
||||
|
||||
# Return None if no return statement was found
|
||||
return None
|
||||
|
||||
|
||||
def get_class_doc(class_name):
|
||||
"""
|
||||
Extracts information from the docstring of a given class.
|
||||
|
||||
Args:
|
||||
class_name: the class to extract information from
|
||||
|
||||
Returns:
|
||||
A dictionary containing the extracted information, with keys
|
||||
for 'Description', 'Parameters', 'Attributes', and 'Returns'.
|
||||
"""
|
||||
# Get the class docstring
|
||||
docstring = class_name.__doc__
|
||||
|
||||
# Parse the docstring to extract information
|
||||
lines = docstring.split("\n")
|
||||
data = {
|
||||
"Description": "",
|
||||
"Parameters": {},
|
||||
"Attributes": {},
|
||||
"Example": [],
|
||||
"Returns": {},
|
||||
}
|
||||
|
||||
current_section = "Description"
|
||||
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
|
||||
if not line:
|
||||
continue
|
||||
|
||||
if (
|
||||
line.startswith(tuple(data.keys()))
|
||||
and len(line.split()) == 1
|
||||
and line.endswith(":")
|
||||
):
|
||||
current_section = line[:-1]
|
||||
continue
|
||||
|
||||
if current_section in ["Description", "Example"]:
|
||||
data[current_section] += line
|
||||
else:
|
||||
param, desc = line.split(":")
|
||||
data[current_section][param.strip()] = desc.strip()
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def format_dict(d):
|
||||
"""
|
||||
Formats a dictionary by removing certain keys and modifying the
|
||||
values of other keys.
|
||||
|
||||
Args:
|
||||
d: the dictionary to format
|
||||
|
||||
Returns:
|
||||
A new dictionary with the desired modifications applied.
|
||||
"""
|
||||
|
||||
# Process remaining keys
|
||||
for key, value in d.items():
|
||||
if key == "examples":
|
||||
pass
|
||||
if key == "_type":
|
||||
continue
|
||||
_type = value["type"]
|
||||
|
||||
# Remove 'Optional' wrapper
|
||||
if "Optional" in _type:
|
||||
_type = _type.replace("Optional[", "")[:-1]
|
||||
|
||||
# Check for list type
|
||||
if "List" in _type:
|
||||
_type = _type.replace("List[", "")[:-1]
|
||||
value["list"] = True
|
||||
else:
|
||||
value["list"] = False
|
||||
|
||||
# Replace 'Mapping' with 'dict'
|
||||
if "Mapping" in _type:
|
||||
_type = _type.replace("Mapping", "dict")
|
||||
|
||||
value["type"] = "Tool" if key == "allowed_tools" else _type
|
||||
|
||||
# Show if required
|
||||
value["show"] = bool(
|
||||
(value["required"] and key not in ["input_variables"])
|
||||
or key
|
||||
in [
|
||||
"allowed_tools",
|
||||
"verbose",
|
||||
"Memory",
|
||||
"memory",
|
||||
"prefix",
|
||||
"examples",
|
||||
"temperature",
|
||||
]
|
||||
or "api_key" in key
|
||||
)
|
||||
|
||||
# Add multline
|
||||
value["multiline"] = key in ["suffix", "prefix", "template", "examples"]
|
||||
# Replace default value with actual value
|
||||
# if _type in ["str", "bool"]:
|
||||
# value["value"] = value.get("default", "")
|
||||
# if "default" in value:
|
||||
# value.pop("default")
|
||||
if "default" in value:
|
||||
value["value"] = value["default"]
|
||||
value.pop("default")
|
||||
|
||||
# Filter out keys that should not be shown
|
||||
return d
|
||||
3
langflow/build
Executable file
3
langflow/build
Executable file
|
|
@ -0,0 +1,3 @@
|
|||
#! /bin/bash
|
||||
|
||||
cd ../space_flow && npm run build
|
||||
28
langflow/cli.py
Normal file
28
langflow/cli.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
from langflow.backend.app import create_app
|
||||
|
||||
import typer
|
||||
import uvicorn
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from pathlib import Path
|
||||
|
||||
app = create_app()
|
||||
|
||||
|
||||
def serve(port: int = 5003):
|
||||
# get the directory of the current file
|
||||
path = Path(__file__).parent
|
||||
static_files_dir = path / "frontend/build"
|
||||
app.mount(
|
||||
"/",
|
||||
StaticFiles(directory=static_files_dir, html=True),
|
||||
name="static",
|
||||
)
|
||||
uvicorn.run(app, port=port)
|
||||
|
||||
|
||||
def main():
|
||||
typer.run(serve)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
5
langflow/frontend/Dockerfile
Normal file
5
langflow/frontend/Dockerfile
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
FROM logspace/frontend_build as frontend_build
|
||||
|
||||
FROM nginx
|
||||
COPY --from=frontend_build /app/build/ /usr/share/nginx/html
|
||||
COPY /nginx.conf /etc/nginx/conf.d/default.conf
|
||||
5
langflow/frontend/build.Dockerfile
Normal file
5
langflow/frontend/build.Dockerfile
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
FROM node:14-alpine
|
||||
WORKDIR /app
|
||||
COPY . /app
|
||||
RUN npm install
|
||||
RUN npm run build
|
||||
6
langflow/frontend/build_and_push
Executable file
6
langflow/frontend/build_and_push
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
#! /bin/bash
|
||||
|
||||
VERSION="0.1.0"
|
||||
docker build -t logspace/frontend_build -f build.Dockerfile .
|
||||
docker build --build-arg VERSION=$VERSION -t ibiscp/langflow_frontend:$VERSION .
|
||||
docker push ibiscp/langflow_frontend:$VERSION
|
||||
18
langflow/frontend/nginx.conf
Normal file
18
langflow/frontend/nginx.conf
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
server {
|
||||
gzip on;
|
||||
gzip_comp_level 2;
|
||||
gzip_min_length 1000;
|
||||
gzip_types text/xml text/css;
|
||||
gzip_http_version 1.1;
|
||||
gzip_vary on;
|
||||
gzip_disable "MSIE [4-6] \.";
|
||||
|
||||
listen 80;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html =404;
|
||||
}
|
||||
|
||||
}
|
||||
17998
langflow/frontend/package-lock.json
generated
Normal file
17998
langflow/frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -3,6 +3,12 @@
|
|||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@headlessui/react": "^1.7.10",
|
||||
"@heroicons/react": "^2.0.15",
|
||||
"@mui/material": "^5.11.9",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
|
|
@ -10,9 +16,17 @@
|
|||
"@types/node": "^16.18.12",
|
||||
"@types/react": "^18.0.27",
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"axios": "^1.3.2",
|
||||
"react": "^18.2.0",
|
||||
"react-cookie": "^4.1.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^4.7.1",
|
||||
"react-laag": "^2.0.5",
|
||||
"react-router-dom": "^6.8.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-tabs": "^6.0.0",
|
||||
"reactflow": "^11.5.5",
|
||||
"tailwindcss": "^3.2.6",
|
||||
"typescript": "^4.9.5",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
14
langflow/frontend/public/index.html
Normal file
14
langflow/frontend/public/index.html
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body id='body' style="width: 100%; height:100%">
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div style="width: 100vw; height:100vh" id='root'>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,3 +1,7 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
132
langflow/frontend/src/App.tsx
Normal file
132
langflow/frontend/src/App.tsx
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
import "reactflow/dist/style.css";
|
||||
import { useState, useEffect, useContext } from "react";
|
||||
import "./App.css";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import ErrorAlert from "./alerts/error";
|
||||
import NoticeAlert from "./alerts/notice";
|
||||
import SuccessAlert from "./alerts/success";
|
||||
import ExtraSidebar from "./components/ExtraSidebarComponent";
|
||||
import { alertContext } from "./contexts/alertContext";
|
||||
import { locationContext } from "./contexts/locationContext";
|
||||
import TabsManagerComponent from "./pages/FlowPage/components/tabsManagerComponent";
|
||||
|
||||
export default function App() {
|
||||
var _ = require("lodash");
|
||||
|
||||
let { setCurrent, setShowSideBar, setIsStackedOpen } =
|
||||
useContext(locationContext);
|
||||
let location = useLocation();
|
||||
useEffect(() => {
|
||||
setCurrent(location.pathname.replace(/\/$/g, "").split("/"));
|
||||
setShowSideBar(true);
|
||||
setIsStackedOpen(true);
|
||||
}, [location.pathname, setCurrent, setIsStackedOpen, setShowSideBar]);
|
||||
|
||||
const {
|
||||
errorData,
|
||||
errorOpen,
|
||||
setErrorOpen,
|
||||
noticeData,
|
||||
noticeOpen,
|
||||
setNoticeOpen,
|
||||
successData,
|
||||
successOpen,
|
||||
setSuccessOpen,
|
||||
} = useContext(alertContext);
|
||||
|
||||
// Initialize state variable for the list of alerts
|
||||
const [alertsList, setAlertsList] = useState<Array<{type:string,data:{title:string,list?:Array<string>,link?:string},id:string}>>([]);
|
||||
|
||||
// Use effect hook to update alertsList when a new alert is added
|
||||
useEffect(() => {
|
||||
// If there is an error alert open with data, add it to the alertsList
|
||||
if (errorOpen && errorData) {
|
||||
setErrorOpen(false);
|
||||
setAlertsList((old) => {
|
||||
let newAlertsList = [
|
||||
...old,
|
||||
{ type: "error", data: _.cloneDeep(errorData), id: _.uniqueId() },
|
||||
];
|
||||
return newAlertsList;
|
||||
});
|
||||
}
|
||||
// If there is a notice alert open with data, add it to the alertsList
|
||||
else if (noticeOpen && noticeData) {
|
||||
setNoticeOpen(false);
|
||||
setAlertsList((old) => {
|
||||
let newAlertsList = [
|
||||
...old,
|
||||
{ type: "notice", data: _.cloneDeep(noticeData), id: _.uniqueId() },
|
||||
];
|
||||
return newAlertsList;
|
||||
});
|
||||
}
|
||||
// If there is a success alert open with data, add it to the alertsList
|
||||
else if (successOpen && successData) {
|
||||
setSuccessOpen(false);
|
||||
setAlertsList((old) => {
|
||||
let newAlertsList = [
|
||||
...old,
|
||||
{ type: "success", data: _.cloneDeep(successData), id: _.uniqueId() },
|
||||
];
|
||||
return newAlertsList;
|
||||
});
|
||||
}
|
||||
}, [_, errorData, errorOpen, noticeData, noticeOpen, setErrorOpen, setNoticeOpen, setSuccessOpen, successData, successOpen]);
|
||||
|
||||
const removeAlert = (id: string) => {
|
||||
setAlertsList((prevAlertsList) =>
|
||||
prevAlertsList.filter((alert) => alert.id !== id)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
//need parent component with width and height
|
||||
<div className="h-full flex flex-col">
|
||||
<div className="flex grow-0 shrink basis-auto">
|
||||
</div>
|
||||
<div className="flex grow shrink basis-auto min-h-0 flex-1 overflow-hidden">
|
||||
<ExtraSidebar />
|
||||
{/* Main area */}
|
||||
<main className="min-w-0 flex-1 border-t border-gray-200 dark:border-gray-700 flex">
|
||||
{/* Primary column */}
|
||||
<div className="w-full h-full">
|
||||
<TabsManagerComponent></TabsManagerComponent>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
<div></div>
|
||||
<div className="flex z-40 flex-col-reverse fixed bottom-5 left-5">
|
||||
{alertsList.map((alert) => (
|
||||
<div key={alert.id}>
|
||||
{alert.type === "error" ? (
|
||||
<ErrorAlert
|
||||
key={alert.id}
|
||||
title={alert.data.title}
|
||||
list={alert.data.list}
|
||||
id={alert.id}
|
||||
removeAlert={removeAlert}
|
||||
/>
|
||||
) : alert.type === "notice" ? (
|
||||
<NoticeAlert
|
||||
key={alert.id}
|
||||
title={alert.data.title}
|
||||
link={alert.data.link}
|
||||
id={alert.id}
|
||||
removeAlert={removeAlert}
|
||||
/>
|
||||
) : (
|
||||
<SuccessAlert
|
||||
key={alert.id}
|
||||
title={alert.data.title}
|
||||
id={alert.id}
|
||||
removeAlert={removeAlert}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<a target={"_blank"} href="https://logspace.ai/" className="absolute bottom-1 left-1 text-gray-500 text-xs cursor-pointer font-sans tracking-wide">Created by Logspace</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
import { Handle, Position, useUpdateNodeInternals } from "reactflow";
|
||||
import Tooltip from "../../../../components/TooltipComponent";
|
||||
import { classNames, isValidConnection } from "../../../../utils";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import InputComponent from "../../../../components/inputComponent";
|
||||
import ToggleComponent from "../../../../components/toggleComponent";
|
||||
import InputListComponent from "../../../../components/inputListComponent";
|
||||
import TextAreaComponent from "../../../../components/textAreaComponent";
|
||||
import { typesContext } from "../../../../contexts/typesContext";
|
||||
import { ParameterComponentType } from "../../../../types/components";
|
||||
import FloatComponent from "../../../../components/floatComponent";
|
||||
|
||||
export default function ParameterComponent({
|
||||
left,
|
||||
id,
|
||||
data,
|
||||
tooltipTitle,
|
||||
title,
|
||||
color,
|
||||
type,
|
||||
name = "",
|
||||
required = false,
|
||||
}: ParameterComponentType) {
|
||||
const ref = useRef(null);
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
const [position, setPosition] = useState(0);
|
||||
useEffect(() => {
|
||||
if (ref.current && ref.current.offsetTop && ref.current.clientHeight) {
|
||||
setPosition(ref.current.offsetTop + ref.current.clientHeight / 2);
|
||||
updateNodeInternals(data.id);
|
||||
}
|
||||
}, [data.id, ref, updateNodeInternals]);
|
||||
|
||||
useEffect(() => {
|
||||
updateNodeInternals(data.id);
|
||||
}, [data.id, position, updateNodeInternals]);
|
||||
|
||||
const [enabled, setEnabled] = useState(
|
||||
data.node.template[name]?.value ?? false
|
||||
);
|
||||
const { reactFlowInstance } = useContext(typesContext);
|
||||
let disabled =
|
||||
reactFlowInstance?.getEdges().some((e) => e.targetHandle === id) ?? false;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="w-full flex flex-wrap justify-between items-center bg-gray-50 dark:bg-gray-800 dark:text-white mt-1 px-5 py-2"
|
||||
>
|
||||
<>
|
||||
<div className="text-sm truncate">
|
||||
{title}
|
||||
<span className="text-red-600">{required ? " *" : ""}</span>
|
||||
</div>
|
||||
<Tooltip title={tooltipTitle + (required ? " (required)" : "")}>
|
||||
<Handle
|
||||
type={left ? "target" : "source"}
|
||||
position={left ? Position.Left : Position.Right}
|
||||
id={id}
|
||||
isValidConnection={(connection) =>
|
||||
isValidConnection(connection, reactFlowInstance)
|
||||
}
|
||||
className={classNames(
|
||||
left ? "-ml-0.5 " : "-mr-0.5 ",
|
||||
"w-3 h-3 rounded-full border-2 bg-white dark:bg-gray-800",
|
||||
left && (type === "str" || type === "bool" || type === "float")
|
||||
? "hidden"
|
||||
: ""
|
||||
)}
|
||||
style={{
|
||||
borderColor: color,
|
||||
top: position,
|
||||
}}
|
||||
></Handle>
|
||||
</Tooltip>
|
||||
{left === true && type === "str" ? (
|
||||
<div className="mt-2 w-full">
|
||||
{data.node.template[name].list ? (
|
||||
<InputListComponent
|
||||
disabled={disabled}
|
||||
value={
|
||||
!data.node.template[name].value ||
|
||||
data.node.template[name].value === ""
|
||||
? [""]
|
||||
: data.node.template[name].value
|
||||
}
|
||||
onChange={(t: string[]) => {
|
||||
data.node.template[name].value = t;
|
||||
}}
|
||||
/>
|
||||
) : data.node.template[name].multiline ? (
|
||||
<TextAreaComponent
|
||||
disabled={disabled}
|
||||
value={data.node.template[name].value ?? ""}
|
||||
onChange={(t: string) => {
|
||||
data.node.template[name].value = t;
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<InputComponent
|
||||
disabled={disabled}
|
||||
value={data.node.template[name].value ?? ""}
|
||||
onChange={(t) => {
|
||||
data.node.template[name].value = t;
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : left === true && type === "bool" ? (
|
||||
<div className="mt-2">
|
||||
<ToggleComponent
|
||||
disabled={disabled}
|
||||
enabled={enabled}
|
||||
setEnabled={(t) => {
|
||||
data.node.template[name].value = t;
|
||||
setEnabled(t);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : left === true && type === "float" ? (
|
||||
<FloatComponent
|
||||
disabled={disabled}
|
||||
value={data.node.template[name].value ?? ""}
|
||||
onChange={(t) => {
|
||||
data.node.template[name].value = t;
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
92
langflow/frontend/src/CustomNodes/GenericNode/index.tsx
Normal file
92
langflow/frontend/src/CustomNodes/GenericNode/index.tsx
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import {
|
||||
TrashIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import {
|
||||
classNames,
|
||||
nodeColors,
|
||||
nodeIcons,
|
||||
snakeToNormalCase,
|
||||
} from "../../utils";
|
||||
import ParameterComponent from "./components/parameterComponent";
|
||||
import { typesContext } from "../../contexts/typesContext";
|
||||
import { useContext } from "react";
|
||||
import { NodeDataType} from "../../types/flow";
|
||||
|
||||
export default function GenericNode({ data, selected}:{data:NodeDataType,selected:boolean}) {
|
||||
const {types, deleteNode} = useContext(typesContext);
|
||||
const Icon = nodeIcons[types[data.type]];
|
||||
|
||||
|
||||
return (
|
||||
<div className={ classNames(selected?"border border-blue-500":"border dark:border-gray-700","prompt-node relative bg-white dark:bg-gray-900 w-96 rounded-lg flex flex-col justify-center")}>
|
||||
<div className="w-full dark:text-white flex items-center justify-between p-4 gap-8 bg-gray-50 rounded-t-lg dark:bg-gray-800 border-b dark:border-b-gray-700 ">
|
||||
<div className="w-full flex items-center truncate gap-4 text-lg">
|
||||
<Icon
|
||||
className="w-10 h-10 p-1 rounded"
|
||||
style={{ color: nodeColors[types[data.type]] }}
|
||||
/>
|
||||
<div className="truncate">{data.type}</div>
|
||||
</div>
|
||||
<button onClick={() => {deleteNode(data.id)}}>
|
||||
<TrashIcon className="w-6 h-6 hover:text-red-500 dark:text-gray-500 dark:hover:text-red-500"></TrashIcon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="w-full h-full py-5">
|
||||
<div className="w-full text-gray-500 px-5 text-sm">
|
||||
{data.node.description}
|
||||
</div>
|
||||
|
||||
<>
|
||||
{Object.keys(data.node.template)
|
||||
.filter((t) => t.charAt(0) !== "_")
|
||||
.map((t:string, idx) => (
|
||||
<div key={idx}>
|
||||
{idx === 0 ? (
|
||||
<div className="px-5 py-2 mt-2 dark:text-white text-center">Inputs:</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{data.node.template[t].show ? (
|
||||
<ParameterComponent
|
||||
|
||||
data={data}
|
||||
color={
|
||||
nodeColors[types[data.node.template[t].type]] ??
|
||||
nodeColors[types[data.node.template[t].type]] ??
|
||||
"black"
|
||||
}
|
||||
title={
|
||||
snakeToNormalCase(t)
|
||||
}
|
||||
name={t}
|
||||
tooltipTitle={
|
||||
"Type: " +
|
||||
data.node.template[t].type +
|
||||
(data.node.template[t].list ? " list" : "")
|
||||
}
|
||||
required={data.node.template[t].required}
|
||||
id={data.node.template[t].type + "|" + t + "|" + data.id}
|
||||
left={true}
|
||||
type={data.node.template[t].type}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<div className="px-5 py-2 mt-2 dark:text-white text-center">Output:</div>
|
||||
<ParameterComponent
|
||||
data={data}
|
||||
color={nodeColors[types[data.type]]}
|
||||
title={data.type}
|
||||
tooltipTitle={"Type: str"}
|
||||
id={data.type + "|" + data.id + data.node.base_classes.map((b) => ("|" + b))}
|
||||
type={'str'}
|
||||
left={false}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
import { XCircleIcon, XMarkIcon, InformationCircleIcon, CheckCircleIcon } from "@heroicons/react/24/outline";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Transition } from "@headlessui/react";
|
||||
import { useState } from "react";
|
||||
import { SingleAlertComponentType } from "../../../../types/alerts";
|
||||
|
||||
export default function SingleAlert({ dropItem, removeAlert}:SingleAlertComponentType) {
|
||||
const [show, setShow] = useState(true);
|
||||
const type = dropItem.type;
|
||||
console.log(dropItem.id)
|
||||
|
||||
return (
|
||||
<Transition
|
||||
className="relative"
|
||||
show={show}
|
||||
appear={true}
|
||||
enter="transition-transform duration-500 ease-out"
|
||||
enterFrom={"transform translate-x-[-100%]"}
|
||||
enterTo={"transform translate-x-0"}
|
||||
leave="transition-transform duration-500 ease-in"
|
||||
leaveFrom={"transform translate-x-0"}
|
||||
leaveTo={"transform translate-x-[-100%]"}
|
||||
>
|
||||
{type === "error"?
|
||||
<div className="flex bg-red-50 rounded-md p-3 mb-2 mx-2" key={dropItem.id}>
|
||||
<div className="flex-shrink-0">
|
||||
<XCircleIcon
|
||||
className="h-5 w-5 text-red-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h3 className="text-sm font-medium text-red-800">
|
||||
{dropItem.title}
|
||||
</h3>
|
||||
{dropItem.list ? (
|
||||
<div className="mt-2 text-sm text-red-700">
|
||||
<ul className="list-disc space-y-1 pl-5">
|
||||
{dropItem.list.map((item, idx) => (
|
||||
<li key={idx}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
<div className="ml-auto pl-3">
|
||||
<div className="-mx-1.5 -my-1.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShow(false); setTimeout(() => {removeAlert(dropItem.id);}, 500);
|
||||
}}
|
||||
className="inline-flex rounded-md bg-red-50 p-1.5 text-red-500"
|
||||
>
|
||||
<span className="sr-only">Dismiss</span>
|
||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
:(type === "notice" ?
|
||||
<div className="flex rounded-md bg-blue-50 p-3 mb-2 mx-2" key={dropItem.id}>
|
||||
<div className="flex-shrink-0">
|
||||
<InformationCircleIcon
|
||||
className="h-5 w-5 text-blue-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3 flex-1 md:flex md:justify-between">
|
||||
<p className="text-sm text-blue-700">{dropItem.title}</p>
|
||||
<p className="mt-3 text-sm md:mt-0 md:ml-6">
|
||||
{dropItem.link ? (
|
||||
<Link
|
||||
to={dropItem.link}
|
||||
className="whitespace-nowrap font-medium text-blue-700 hover:text-blue-600"
|
||||
>
|
||||
Details
|
||||
</Link>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="ml-auto pl-3">
|
||||
<div className="-mx-1.5 -my-1.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShow(false); setTimeout(() => {removeAlert(dropItem.id);}, 500);
|
||||
}}
|
||||
className="inline-flex rounded-md bg-blue-50 p-1.5 text-blue-500"
|
||||
>
|
||||
<span className="sr-only">Dismiss</span>
|
||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
:
|
||||
<div className="flex bg-green-50 p-3 mb-2 mx-2 rounded-md" key={dropItem.id}>
|
||||
<div className="flex-shrink-0">
|
||||
<CheckCircleIcon
|
||||
className="h-5 w-5 text-green-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<p className="text-sm font-medium text-green-800">
|
||||
{dropItem.title}
|
||||
</p>
|
||||
</div>
|
||||
<div className="ml-auto pl-3">
|
||||
<div className="-mx-1.5 -my-1.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShow(false); setTimeout(() => {removeAlert(dropItem.id);}, 500);
|
||||
}}
|
||||
className="inline-flex rounded-md bg-green-50 p-1.5 text-green-500"
|
||||
>
|
||||
<span className="sr-only">Dismiss</span>
|
||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
</Transition>
|
||||
)
|
||||
|
||||
}
|
||||
54
langflow/frontend/src/alerts/alertDropDown/index.tsx
Normal file
54
langflow/frontend/src/alerts/alertDropDown/index.tsx
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { useContext } from "react";
|
||||
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 { AlertDropdownType } from "../../types/alerts";
|
||||
import { PopUpContext } from "../../contexts/popUpContext";
|
||||
|
||||
|
||||
|
||||
export default function AlertDropdown({}: AlertDropdownType) {
|
||||
const {
|
||||
notificationList,
|
||||
clearNotificationList,
|
||||
removeFromNotificationList,
|
||||
} = useContext(alertContext);
|
||||
const {closePopUp} = useContext(PopUpContext)
|
||||
|
||||
|
||||
return (
|
||||
<div className="z-10 py-3 pb-4 rounded-md bg-white ring-1 ring-black ring-opacity-5 shadow-lg focus:outline-none overflow-hidden w-[16rem] h-[28rem] flex flex-col">
|
||||
<div className="flex pl-3 flex-row justify-between text-md font-medium text-gray-800">
|
||||
Notifications
|
||||
<div className="flex gap-2 pr-3 ">
|
||||
<button
|
||||
className="hover:text-red-500"
|
||||
onClick={() => {closePopUp(); setTimeout(clearNotificationList, 100)}}
|
||||
>
|
||||
<TrashIcon className="w-[1.1rem] h-[1.1rem]" />
|
||||
</button>
|
||||
<button
|
||||
className="hover:text-red-500"
|
||||
onClick={closePopUp}
|
||||
>
|
||||
<XMarkIcon className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 flex flex-col overflow-y-scroll w-full h-full scrollbar-hide">
|
||||
{notificationList.length !== 0 ?
|
||||
notificationList.map((alertItem, index) => (
|
||||
<SingleAlert key={alertItem.id} dropItem={alertItem} removeAlert={removeFromNotificationList} />
|
||||
))
|
||||
:
|
||||
<div className="h-full w-full pb-16 text-gray-500 flex justify-center items-center">
|
||||
No new notifications
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
64
langflow/frontend/src/alerts/error/index.tsx
Normal file
64
langflow/frontend/src/alerts/error/index.tsx
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import { Transition } from "@headlessui/react";
|
||||
import { XCircleIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ErrorAlertType} from "../../types/alerts";
|
||||
|
||||
export default function ErrorAlert({ title, list = [], id, removeAlert }:ErrorAlertType) {
|
||||
const [show, setShow] = useState(true);
|
||||
useEffect(() => {
|
||||
if(show){
|
||||
setTimeout(() => {
|
||||
setShow(false); setTimeout(() => {removeAlert(id);}, 500);
|
||||
}, 5000);
|
||||
}
|
||||
}, [id, removeAlert, show]);
|
||||
return (
|
||||
<Transition
|
||||
className="relative"
|
||||
show={show}
|
||||
appear={true}
|
||||
enter="transition-transform duration-500 ease-out"
|
||||
enterFrom={"transform translate-x-[-100%]"}
|
||||
enterTo={"transform translate-x-0"}
|
||||
leave="transition-transform duration-500 ease-in"
|
||||
leaveFrom={"transform translate-x-0"}
|
||||
leaveTo={"transform translate-x-[-100%]"}
|
||||
>
|
||||
<div className="rounded-md w-96 mt-6 shadow-xl bg-red-50 p-4">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<XCircleIcon className="h-5 w-5 text-red-400" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h3 className="text-sm font-medium text-red-800">{title}</h3>
|
||||
{list.length !== 0
|
||||
?
|
||||
<div className="mt-2 text-sm text-red-700">
|
||||
<ul className="list-disc space-y-1 pl-5">
|
||||
{list.map((item, index) => (
|
||||
<li key={index} >{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
:
|
||||
<></>
|
||||
}
|
||||
|
||||
</div>
|
||||
<div className="ml-auto pl-3">
|
||||
<div className="-mx-1.5 -my-1.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={()=>{setShow(false); setTimeout(() => {removeAlert(id);}, 500);}}
|
||||
className="inline-flex rounded-md bg-red-50 p-1.5 text-red-500"
|
||||
>
|
||||
<span className="sr-only">Dismiss</span>
|
||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
66
langflow/frontend/src/alerts/notice/index.tsx
Normal file
66
langflow/frontend/src/alerts/notice/index.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { Transition } from "@headlessui/react";
|
||||
import { InformationCircleIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { NoticeAlertType } from "../../types/alerts";
|
||||
|
||||
export default function NoticeAlert({ title, link = "", id, removeAlert }:NoticeAlertType) {
|
||||
const [show, setShow] = useState(true);
|
||||
useEffect(() => {
|
||||
if(show){
|
||||
setTimeout(() => {
|
||||
setShow(false); setTimeout(() => {removeAlert(id);}, 500);
|
||||
}, 5000);
|
||||
}
|
||||
}, [id, removeAlert, show]);
|
||||
return (
|
||||
<Transition
|
||||
show={show}
|
||||
enter="transition-transform duration-500 ease-out"
|
||||
enterFrom={"transform translate-x-[-100%]"}
|
||||
enterTo={"transform translate-x-0"}
|
||||
leave="transition-transform duration-500 ease-in"
|
||||
leaveFrom={"transform translate-x-0"}
|
||||
leaveTo={"transform translate-x-[-100%]"}
|
||||
>
|
||||
<div className="rounded-md w-96 mt-6 shadow-xl bg-blue-50 p-4">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<InformationCircleIcon
|
||||
className="h-5 w-5 text-blue-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3 flex-1 md:flex md:justify-between">
|
||||
<p className="text-sm text-blue-700">{title}</p>
|
||||
<p className="mt-3 text-sm md:mt-0 md:ml-6">
|
||||
{link !== ""
|
||||
?
|
||||
<Link
|
||||
to={link}
|
||||
className="whitespace-nowrap font-medium text-blue-700 hover:text-blue-600"
|
||||
>
|
||||
Details
|
||||
</Link>
|
||||
:
|
||||
<></>
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
<div className="ml-auto pl-3">
|
||||
<div className="-mx-1.5 -my-1.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={()=>{setShow(false); removeAlert(id);}}
|
||||
className="inline-flex rounded-md bg-blue-50 p-1.5 text-blue-500"
|
||||
>
|
||||
<span className="sr-only">Dismiss</span>
|
||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
52
langflow/frontend/src/alerts/success/index.tsx
Normal file
52
langflow/frontend/src/alerts/success/index.tsx
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { Transition } from "@headlessui/react";
|
||||
import { CheckCircleIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import { useEffect, useState } from "react";
|
||||
import { SuccessAlertType } from "../../types/alerts";
|
||||
|
||||
export default function SuccessAlert({ title, id, removeAlert }:SuccessAlertType) {
|
||||
const [show, setShow] = useState(true);
|
||||
useEffect(() => {
|
||||
if(show){
|
||||
setTimeout(() => {
|
||||
setShow(false); setTimeout(() => {removeAlert(id);}, 500);
|
||||
}, 5000);
|
||||
}
|
||||
}, [id, removeAlert, show]);
|
||||
return (
|
||||
<Transition
|
||||
show={show}
|
||||
enter="transition-transform duration-500 ease-out"
|
||||
enterFrom={"transform translate-x-[-100%]"}
|
||||
enterTo={"transform translate-x-0"}
|
||||
leave="transition-transform duration-500 ease-in"
|
||||
leaveFrom={"transform translate-x-0"}
|
||||
leaveTo={"transform translate-x-[-100%]"}
|
||||
>
|
||||
<div className="rounded-md w-96 mt-6 shadow-xl bg-green-50 p-4">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<CheckCircleIcon
|
||||
className="h-5 w-5 text-green-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<p className="text-sm font-medium text-green-800">{title}</p>
|
||||
</div>
|
||||
<div className="ml-auto pl-3">
|
||||
<div className="-mx-1.5 -my-1.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={()=>{setShow(false); removeAlert(id);}}
|
||||
className="inline-flex rounded-md bg-green-50 p-1.5 text-green-500"
|
||||
>
|
||||
<span className="sr-only">Dismiss</span>
|
||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
124
langflow/frontend/src/components/ExtraSidebarComponent/index.tsx
Normal file
124
langflow/frontend/src/components/ExtraSidebarComponent/index.tsx
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
import { Disclosure } from "@headlessui/react";
|
||||
import { ChevronLeftIcon } from "@heroicons/react/24/outline";
|
||||
import { useContext } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { classNames } from "../../utils";
|
||||
import { locationContext } from "../../contexts/locationContext";
|
||||
|
||||
export default function ExtraSidebar() {
|
||||
const {
|
||||
current,
|
||||
isStackedOpen,
|
||||
setIsStackedOpen,
|
||||
extraNavigation,
|
||||
extraComponent,
|
||||
} = useContext(locationContext);
|
||||
return (
|
||||
<>
|
||||
<aside
|
||||
className={` ${
|
||||
isStackedOpen ? "w-52" : "w-0 "
|
||||
} flex-shrink-0 flex overflow-hidden flex-col border-r dark:border-r-gray-700 transition-all duration-500`}
|
||||
>
|
||||
<div className="w-52 dark:bg-gray-800 border dark:border-gray-700 overflow-y-auto scrollbar-hide h-full flex flex-col items-start">
|
||||
<div className="flex pt-1 px-4 justify-between align-middle w-full">
|
||||
<span className="text-gray-900 dark:text-white py-[2px] font-medium ">
|
||||
{extraNavigation.title}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-grow flex-col w-full">
|
||||
{extraNavigation.options ? (
|
||||
<div className="p-4">
|
||||
<nav className="flex-1 space-y-1">
|
||||
{extraNavigation.options.map((item) =>
|
||||
!item.children ? (
|
||||
<div key={item.name}>
|
||||
<Link
|
||||
to={item.href}
|
||||
className={classNames(
|
||||
item.href.split("/")[2] === current[4]
|
||||
? "bg-gray-100 text-gray-900"
|
||||
: "bg-white text-gray-600 hover:bg-gray-50 hover:text-gray-900",
|
||||
"group w-full flex items-center pl-2 py-2 text-sm font-medium rounded-md"
|
||||
)}
|
||||
>
|
||||
<item.icon
|
||||
className={classNames(
|
||||
item.href.split("/")[2] === current[4]
|
||||
? "text-gray-500"
|
||||
: "text-gray-400 group-hover:text-gray-500",
|
||||
"mr-3 flex-shrink-0 h-6 w-6"
|
||||
)}
|
||||
/>
|
||||
{item.name}
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<Disclosure
|
||||
as="div"
|
||||
key={item.name}
|
||||
className="space-y-1"
|
||||
>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button
|
||||
className={classNames(
|
||||
item.href.split("/")[2] === current[4]
|
||||
? "bg-gray-100 text-gray-900"
|
||||
: "bg-white text-gray-600 hover:bg-gray-50 hover:text-gray-900",
|
||||
"group w-full flex items-center pl-2 pr-1 py-2 text-left text-sm font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
)}
|
||||
>
|
||||
<item.icon
|
||||
className="mr-3 h-6 w-6 flex-shrink-0 text-gray-400 group-hover:text-gray-500"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="flex-1">{item.name}</span>
|
||||
<svg
|
||||
className={classNames(
|
||||
open
|
||||
? "text-gray-400 rotate-90"
|
||||
: "text-gray-300",
|
||||
"ml-3 h-5 w-5 flex-shrink-0 transition-rotate duration-150 ease-in-out group-hover:text-gray-400"
|
||||
)}
|
||||
viewBox="0 0 20 20"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M6 6L14 10L6 14V6Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</Disclosure.Button>
|
||||
<Disclosure.Panel className="space-y-1">
|
||||
{item.children.map((subItem) => (
|
||||
<Link
|
||||
key={subItem.name}
|
||||
to={subItem.href}
|
||||
className={classNames(
|
||||
subItem.href.split("/")[3] === current[5]
|
||||
? "bg-gray-100 text-gray-900"
|
||||
: "bg-white text-gray-600 hover:bg-gray-50 hover:text-gray-900",
|
||||
"group flex w-full items-center rounded-md py-2 pl-11 pr-2 text-sm font-medium"
|
||||
)}
|
||||
>
|
||||
{subItem.name}
|
||||
</Link>
|
||||
))}
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
)
|
||||
)}
|
||||
</nav>
|
||||
</div>
|
||||
) : (
|
||||
extraComponent
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import { styled } from '@mui/material/styles';
|
||||
import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip';
|
||||
|
||||
export const LightTooltip = styled(({ className, ...props }: TooltipProps) => (
|
||||
<Tooltip {...props} classes={{ popper: className }} />
|
||||
))(({ theme }) => ({
|
||||
[`& .${tooltipClasses.tooltip}`]: {
|
||||
backgroundColor: theme.palette.common.white,
|
||||
color: 'rgba(0, 0, 0, 0.87)',
|
||||
boxShadow: theme.shadows[2],
|
||||
fontSize: 14,
|
||||
},
|
||||
[`& .${tooltipClasses.arrow}:before`]: {
|
||||
color: theme.palette.common.white,
|
||||
boxShadow: theme.shadows[1],
|
||||
},
|
||||
}));
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import { ReactElement } from "react";
|
||||
import { LightTooltip } from "../LightTooltipComponent";
|
||||
|
||||
export default function Tooltip({ children, title }:{children:ReactElement,title:string}) {
|
||||
return <LightTooltip title={title} arrow>{children}</LightTooltip>;
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import { ChatBubbleLeftEllipsisIcon, ChatBubbleOvalLeftEllipsisIcon, PlusSmallIcon } from "@heroicons/react/24/outline";
|
||||
import { useState } from "react";
|
||||
import { ChatMessageType } from "../../../types/chat";
|
||||
import { nodeColors } from "../../../utils";
|
||||
|
||||
export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
|
||||
const [hidden, setHidden] = useState(true);
|
||||
return (
|
||||
<div>
|
||||
{!chat.isSend ? (
|
||||
<div className="w-full text-start">
|
||||
<div
|
||||
style={{ backgroundColor: nodeColors["chat"] }}
|
||||
className=" relative text-start inline-block text-white rounded-xl overflow-hidden w-fit max-w-[280px] text-sm font-normal rounded-tl-none"
|
||||
>
|
||||
{hidden && chat.thought && chat.thought !== "" && (
|
||||
<div
|
||||
onClick={() => setHidden((prev) => !prev)}
|
||||
className="absolute top-2 right-2 cursor-pointer"
|
||||
>
|
||||
<ChatBubbleOvalLeftEllipsisIcon className="w-5 h-5 animate-bounce" />
|
||||
</div>
|
||||
)}
|
||||
{chat.thought && chat.thought !== "" && !hidden && (
|
||||
<div
|
||||
onClick={() => setHidden((prev) => !prev)}
|
||||
style={{ backgroundColor: nodeColors["thought"] }}
|
||||
className=" text-start inline-block w-full pb-3 pt-3 px-5 cursor-pointer"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: chat.thought.replace(/\n/g, "<br />"),
|
||||
}}
|
||||
></div>
|
||||
)}
|
||||
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
|
||||
<div className="w-full rounded-b-md px-4 pb-3 pt-3 pr-8" style={{ backgroundColor: nodeColors["chat"] }}>
|
||||
{chat.message}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-full text-end">
|
||||
<div className="text-start inline-block rounded-xl p-3 overflow-hidden w-fit max-w-[280px] px-5 text-sm text-black dark:text-white dark:bg-gray-700 bg-gray-200 font-normal rounded-tr-none">
|
||||
{chat.message}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
217
langflow/frontend/src/components/chatComponent/index.tsx
Normal file
217
langflow/frontend/src/components/chatComponent/index.tsx
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
import { Transition } from "@headlessui/react";
|
||||
import {
|
||||
Bars3CenterLeftIcon,
|
||||
LockClosedIcon,
|
||||
PaperAirplaneIcon,
|
||||
XMarkIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
import { sendAll } from "../../controllers/NodesServices";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { classNames, nodeColors } from "../../utils";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import { ChatType } from "../../types/chat";
|
||||
import ChatMessage from "./chatMessage";
|
||||
|
||||
const _ = require("lodash");
|
||||
|
||||
export default function Chat({ flow, reactFlowInstance }: ChatType) {
|
||||
const { updateFlow } = useContext(TabsContext);
|
||||
const [saveChat, setSaveChat] = useState(false);
|
||||
const [lockChat, setLockChat] = useState(false);
|
||||
const [open, setOpen] = useState(true);
|
||||
const [chatValue, setChatValue] = useState("");
|
||||
const [chatHistory, setChatHistory] = useState(flow.chat);
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const addChatHistory = (
|
||||
message: string,
|
||||
isSend: boolean,
|
||||
thought?: string
|
||||
) => {
|
||||
setChatHistory((old) => {
|
||||
let newChat = _.cloneDeep(old);
|
||||
if (thought) {
|
||||
newChat.push({ message, isSend, thought });
|
||||
} else {
|
||||
newChat.push({ message, isSend });
|
||||
}
|
||||
return newChat;
|
||||
});
|
||||
setSaveChat((chat) => !chat);
|
||||
};
|
||||
useEffect(() => {
|
||||
updateFlow({ ..._.cloneDeep(flow), chat: chatHistory });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [saveChat]);
|
||||
useEffect(() => {
|
||||
setChatHistory(flow.chat);
|
||||
}, [flow]);
|
||||
useEffect(() => {
|
||||
if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" });
|
||||
}, [chatHistory]);
|
||||
function validateNodes() {
|
||||
if (
|
||||
reactFlowInstance
|
||||
.getNodes()
|
||||
.some(
|
||||
(n) =>
|
||||
n.data.node &&
|
||||
Object.keys(n.data.node.template).some(
|
||||
(t: any) =>
|
||||
n.data.node.template[t].required &&
|
||||
n.data.node.template[t].value === "" &&
|
||||
n.data.node.template[t].required &&
|
||||
!reactFlowInstance
|
||||
.getEdges()
|
||||
.some(
|
||||
(e) =>
|
||||
e.sourceHandle.split("|")[1] === t &&
|
||||
e.sourceHandle.split("|")[2] === n.id
|
||||
)
|
||||
)
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const ref = useRef(null);
|
||||
|
||||
function sendMessage() {
|
||||
if (chatValue !== "") {
|
||||
if (validateNodes()) {
|
||||
setLockChat(true);
|
||||
let message = chatValue;
|
||||
setChatValue("");
|
||||
addChatHistory(message, true);
|
||||
console.log({ ...reactFlowInstance.toObject(), message, chatHistory });
|
||||
|
||||
sendAll({ ...reactFlowInstance.toObject(), message, chatHistory })
|
||||
.then((r) => {
|
||||
console.log(r.data);
|
||||
addChatHistory(r.data.result, false,r.data.thought);
|
||||
setLockChat(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorData({ title: error.message ?? "unknow error" });
|
||||
setLockChat(false);
|
||||
});
|
||||
} else {
|
||||
setErrorData({
|
||||
title: "Error sending message",
|
||||
list: ["There are required fields not filled yet."],
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setErrorData({
|
||||
title: "Error sending message",
|
||||
list: ["The message cannot be empty."],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Transition
|
||||
show={open}
|
||||
appear={true}
|
||||
enter="transition ease-out duration-300"
|
||||
enterFrom="translate-y-96"
|
||||
enterTo="translate-y-0"
|
||||
leave="transition ease-in duration-300"
|
||||
leaveFrom="translate-y-0"
|
||||
leaveTo="translate-y-96"
|
||||
>
|
||||
<div className="w-[340px] absolute bottom-0 right-1">
|
||||
<div className="border dark:border-gray-700 h-full rounded-xl rounded-b-none bg-white dark:bg-gray-800 shadow">
|
||||
<div
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
className="flex justify-between cursor-pointer items-center px-5 py-2 border-b dark:border-b-gray-700"
|
||||
>
|
||||
<div className="flex gap-3 text-lg dark:text-white font-medium items-center">
|
||||
<Bars3CenterLeftIcon
|
||||
className="h-5 w-5 mt-1"
|
||||
style={{ color: nodeColors["chat"] }}
|
||||
/>
|
||||
Chat
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full h-[400px] flex gap-3 mb-auto overflow-y-auto scrollbar-hide flex-col bg-gray-50 dark:bg-gray-900 p-3 py-5">
|
||||
{chatHistory.map((c, i) => (
|
||||
<ChatMessage chat={c} key={i}/>
|
||||
))}
|
||||
<div ref={ref}></div>
|
||||
</div>
|
||||
<div className="w-full bg-white dark:bg-gray-800 border-t dark:border-t-gray-600 flex items-center justify-between p-3">
|
||||
<div className="relative w-full mt-1 rounded-md shadow-sm">
|
||||
<input
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter" && !lockChat) {
|
||||
sendMessage();
|
||||
}
|
||||
}}
|
||||
type="text"
|
||||
disabled={lockChat}
|
||||
value={lockChat?"please wait for the response": chatValue}
|
||||
onChange={(e) => {
|
||||
setChatValue(e.target.value);
|
||||
}}
|
||||
className={classNames(
|
||||
lockChat ? "bg-gray-500 text-white" : "dark:bg-gray-700",
|
||||
"form-input block w-full rounded-md border-gray-300 dark:border-gray-600 dark:text-white pr-10 sm:text-sm"
|
||||
)}
|
||||
placeholder={"Send a message..."}
|
||||
/>
|
||||
<div className="absolute inset-y-0 right-0 flex items-center pr-3">
|
||||
<button disabled={lockChat} onClick={() => sendMessage()}>
|
||||
{lockChat ? (
|
||||
<LockClosedIcon
|
||||
className="h-5 w-5 text-gray-400 dark:hover:text-gray-300 animate-pulse"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : (
|
||||
<PaperAirplaneIcon
|
||||
className="h-5 w-5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
<Transition
|
||||
show={!open}
|
||||
appear={true}
|
||||
enter="transition ease-out duration-300"
|
||||
enterFrom="translate-y-96"
|
||||
enterTo="translate-y-0"
|
||||
leave="transition ease-in duration-300"
|
||||
leaveFrom="translate-y-0"
|
||||
leaveTo="translate-y-96"
|
||||
>
|
||||
<div className="absolute bottom-0 right-1">
|
||||
<div className="border flex justify-center align-center py-1 px-3 rounded-xl rounded-b-none bg-white dark:bg-gray-800 dark:border-gray-600 dark:text-white shadow">
|
||||
<button
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<Bars3CenterLeftIcon
|
||||
className="h-6 w-6 mt-1"
|
||||
style={{ color: nodeColors["chat"] }}
|
||||
/>
|
||||
Chat
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||
82
langflow/frontend/src/components/dropdownComponent/index.tsx
Normal file
82
langflow/frontend/src/components/dropdownComponent/index.tsx
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
import { Listbox, Transition } from "@headlessui/react";
|
||||
import { ChevronUpDownIcon, CheckIcon } from "@heroicons/react/24/outline";
|
||||
import { Fragment } from "react";
|
||||
import { DropDownComponentType } from "../../types/components";
|
||||
import { classNames } from "../../utils";
|
||||
|
||||
export default function Dropdown({title, value, options, onSelect}:DropDownComponentType) {
|
||||
return (
|
||||
<>
|
||||
<Listbox value={value} onChange={onSelect}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Listbox.Label className="block text-sm font-medium text-gray-700">
|
||||
{title}
|
||||
</Listbox.Label>
|
||||
<div className="relative mt-1">
|
||||
<Listbox.Button className="relative w-full cursor-default rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 text-left shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm">
|
||||
<span className="block truncate">{value}</span>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<ChevronUpDownIcon
|
||||
className="h-5 w-5 text-gray-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
</Listbox.Button>
|
||||
|
||||
<Transition
|
||||
show={open}
|
||||
as={Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
||||
{options.map((option, id) => (
|
||||
<Listbox.Option
|
||||
key={id}
|
||||
className={({ active }) =>
|
||||
classNames(
|
||||
active ? "text-white bg-indigo-600" : "text-gray-900",
|
||||
"relative cursor-default select-none py-2 pl-3 pr-9"
|
||||
)
|
||||
}
|
||||
value={option}
|
||||
>
|
||||
{({ selected, active }) => (
|
||||
<>
|
||||
<span
|
||||
className={classNames(
|
||||
selected ? "font-semibold" : "font-normal",
|
||||
"block truncate"
|
||||
)}
|
||||
>
|
||||
{option}
|
||||
</span>
|
||||
|
||||
{selected ? (
|
||||
<span
|
||||
className={classNames(
|
||||
active ? "text-white" : "text-indigo-600",
|
||||
"absolute inset-y-0 right-0 flex items-center pr-4"
|
||||
)}
|
||||
>
|
||||
<CheckIcon
|
||||
className="h-5 w-5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Listbox>
|
||||
</>
|
||||
);
|
||||
}
|
||||
26
langflow/frontend/src/components/floatComponent/index.tsx
Normal file
26
langflow/frontend/src/components/floatComponent/index.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { FloatComponentType } from "../../types/components";
|
||||
|
||||
export default function FloatComponent({value, onChange, disabled}: FloatComponentType){
|
||||
const [myValue, setMyValue] = useState(value ?? "");
|
||||
useEffect(()=> {
|
||||
if(disabled){
|
||||
setMyValue("");
|
||||
onChange("");
|
||||
}
|
||||
}, [disabled, onChange])
|
||||
return (
|
||||
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
|
||||
<input
|
||||
type="number"
|
||||
value={myValue}
|
||||
className={"block w-full form-input dark:bg-gray-900 arrow-hide dark:border-gray-600 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" + (disabled ? " bg-gray-200 dark:bg-gray-700" : "")}
|
||||
placeholder="Type a number from zero to one"
|
||||
onChange={(e) => {
|
||||
setMyValue(e.target.value);
|
||||
onChange(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
26
langflow/frontend/src/components/inputComponent/index.tsx
Normal file
26
langflow/frontend/src/components/inputComponent/index.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { InputComponentType } from "../../types/components";
|
||||
|
||||
export default function InputComponent({value, onChange, disabled}: InputComponentType){
|
||||
const [myValue, setMyValue] = useState(value ?? "");
|
||||
useEffect(()=> {
|
||||
if(disabled){
|
||||
setMyValue("");
|
||||
onChange("");
|
||||
}
|
||||
}, [disabled, onChange])
|
||||
return (
|
||||
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
|
||||
<input
|
||||
type="text"
|
||||
value={myValue}
|
||||
className={"block w-full form-input dark:bg-gray-900 dark:border-gray-600 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" + (disabled ? " bg-gray-200 dark:bg-gray-700" : "")}
|
||||
placeholder="Type a text"
|
||||
onChange={(e) => {
|
||||
setMyValue(e.target.value);
|
||||
onChange(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
||||
import { useEffect, useState } from "react";
|
||||
import { InputListComponentType } from "../../types/components";
|
||||
|
||||
var _ = require("lodash");
|
||||
|
||||
export default function InputListComponent({ value, onChange, disabled}:InputListComponentType) {
|
||||
const [inputList, setInputList] = useState(value ?? [""]);
|
||||
useEffect(()=> {
|
||||
if(disabled){
|
||||
setInputList([""]);
|
||||
onChange([""]);
|
||||
}
|
||||
}, [disabled, onChange])
|
||||
return (
|
||||
<div className={(disabled ? "pointer-events-none cursor-not-allowed" : "") + "flex flex-col gap-3"}>
|
||||
{inputList.map((i, idx) => (
|
||||
<div key={idx} className="w-full flex gap-3">
|
||||
<input
|
||||
type="text"
|
||||
value={i}
|
||||
className={"block w-full form-input rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" + (disabled ? " bg-gray-200" : "")}
|
||||
placeholder="Type a text"
|
||||
onChange={(e) => {
|
||||
setInputList((old) => {
|
||||
let newInputList = _.cloneDeep(old);
|
||||
newInputList[idx] = e.target.value;
|
||||
return newInputList;
|
||||
});
|
||||
onChange(inputList);
|
||||
}}
|
||||
/>
|
||||
{idx === inputList.length - 1 ?
|
||||
<button onClick={() => {setInputList((old) => {
|
||||
let newInputList = _.cloneDeep(old);
|
||||
newInputList.push('');
|
||||
return newInputList;
|
||||
});
|
||||
onChange(inputList);}}>
|
||||
<PlusIcon className="w-4 h-4 hover:text-blue-600" />
|
||||
</button>
|
||||
: <button onClick={() => {setInputList((old) => {
|
||||
let newInputList = _.cloneDeep(old);
|
||||
newInputList.splice(idx, 1);
|
||||
return newInputList;
|
||||
});
|
||||
onChange(inputList);}}>
|
||||
<XMarkIcon className="w-4 h-4 hover:text-red-600" />
|
||||
</button>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
16
langflow/frontend/src/components/loadingComponent/index.tsx
Normal file
16
langflow/frontend/src/components/loadingComponent/index.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
type LoadingComponentProps={
|
||||
remSize:number
|
||||
}
|
||||
|
||||
|
||||
export default function LoadingComponent({remSize}:LoadingComponentProps){
|
||||
return(
|
||||
<div role="status" className="w-min m-auto">
|
||||
<svg aria-hidden="true" className={`w-${remSize} h-${remSize} mr-2 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600`} viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
|
||||
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
|
||||
</svg>
|
||||
<span className="animate-pulse text-blue-600 text-lg">Loading...</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
33
langflow/frontend/src/components/textAreaComponent/index.tsx
Normal file
33
langflow/frontend/src/components/textAreaComponent/index.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/outline";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { PopUpContext } from "../../contexts/popUpContext";
|
||||
import TextAreaModal from "../../modals/textAreaModal";
|
||||
import { TextAreaComponentType } from "../../types/components";
|
||||
|
||||
export default function TextAreaComponent({ value, onChange, disabled }:TextAreaComponentType) {
|
||||
const [myValue, setMyValue] = useState(value);
|
||||
const { openPopUp } = useContext(PopUpContext);
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
setMyValue([""]);
|
||||
onChange([""]);
|
||||
}
|
||||
}, [disabled, onChange]);
|
||||
return (
|
||||
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
|
||||
<div className="w-full flex items-center gap-3">
|
||||
<span
|
||||
className={
|
||||
"truncate block w-full text-gray-500 px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" +
|
||||
(disabled ? " bg-gray-200" : "")
|
||||
}
|
||||
>
|
||||
{myValue !== "" ? myValue : 'Text empty'}
|
||||
</span>
|
||||
<button onClick={()=>{openPopUp(<TextAreaModal value={myValue} setValue={(t:string) => {setMyValue(t); onChange(t);}}/>)}}>
|
||||
<ArrowTopRightOnSquareIcon className="w-6 h-6 hover:text-blue-600" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
75
langflow/frontend/src/components/toggleComponent/index.tsx
Normal file
75
langflow/frontend/src/components/toggleComponent/index.tsx
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import { Switch } from "@headlessui/react";
|
||||
import { classNames } from "../../utils";
|
||||
import { useEffect } from "react";
|
||||
import { ToggleComponentType } from "../../types/components";
|
||||
|
||||
export default function ToggleComponent({ enabled, setEnabled, disabled }:ToggleComponentType) {
|
||||
useEffect(()=> {
|
||||
if(disabled){
|
||||
setEnabled(false);
|
||||
}
|
||||
}, [disabled, setEnabled])
|
||||
return (
|
||||
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
|
||||
<Switch
|
||||
checked={enabled}
|
||||
onChange={(x:boolean) => {
|
||||
setEnabled(x);
|
||||
}}
|
||||
className={classNames(
|
||||
enabled ? "bg-indigo-600" : "bg-gray-200 dark:bg-gray-600",
|
||||
"relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out "
|
||||
)}
|
||||
>
|
||||
<span className="sr-only">Use setting</span>
|
||||
<span
|
||||
className={classNames(
|
||||
enabled ? "translate-x-5" : "translate-x-0",
|
||||
"pointer-events-none relative inline-block h-5 w-5 transform rounded-full shadow ring-0 transition duration-200 ease-in-out", disabled ? "bg-gray-200 dark:bg-gray-600" : "bg-white dark:bg-gray-800"
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={classNames(
|
||||
enabled
|
||||
? "opacity-0 ease-out duration-100"
|
||||
: "opacity-100 ease-in duration-200",
|
||||
"absolute inset-0 flex h-full w-full items-center justify-center transition-opacity"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
className="h-3 w-3 text-gray-400"
|
||||
fill="none"
|
||||
viewBox="0 0 12 12"
|
||||
>
|
||||
<path
|
||||
d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
className={classNames(
|
||||
enabled
|
||||
? "opacity-100 ease-in duration-200"
|
||||
: "opacity-0 ease-out duration-100",
|
||||
"absolute inset-0 flex h-full w-full items-center justify-center transition-opacity"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
className="h-3 w-3 text-indigo-600"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 12 12"
|
||||
>
|
||||
<path d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z" />
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
164
langflow/frontend/src/contexts/alertContext.tsx
Normal file
164
langflow/frontend/src/contexts/alertContext.tsx
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
import { createContext, ReactNode, useState } from "react";
|
||||
import { AlertItemType } from "../types/alerts";
|
||||
|
||||
var _ = require("lodash");
|
||||
|
||||
//types for alertContextType
|
||||
type alertContextType = {
|
||||
errorData: { title: string; list?: Array<string> };
|
||||
setErrorData: (newState: { title: string; list?: Array<string> }) => void;
|
||||
errorOpen: boolean;
|
||||
setErrorOpen: (newState: boolean) => void;
|
||||
noticeData: { title: string; link?: string };
|
||||
setNoticeData: (newState: { title: string; link?: string }) => void;
|
||||
noticeOpen: boolean;
|
||||
setNoticeOpen: (newState: boolean) => void;
|
||||
successData: { title: string };
|
||||
setSuccessData: (newState: { title: string }) => void;
|
||||
successOpen: boolean;
|
||||
setSuccessOpen: (newState: boolean) => void;
|
||||
notificationCenter: boolean;
|
||||
setNotificationCenter: (newState: boolean) => void;
|
||||
notificationList: Array<AlertItemType>;
|
||||
pushNotificationList: (Object:AlertItemType) => void;
|
||||
clearNotificationList: () => void;
|
||||
removeFromNotificationList: (index: string) => void;
|
||||
};
|
||||
|
||||
//initial values to alertContextType
|
||||
const initialValue:alertContextType = {
|
||||
errorData: { title: "", list: [] },
|
||||
setErrorData: () => {},
|
||||
errorOpen: false,
|
||||
setErrorOpen: () => {},
|
||||
noticeData: { title: "", link: "" },
|
||||
setNoticeData: () => {},
|
||||
noticeOpen: false,
|
||||
setNoticeOpen: () => {},
|
||||
successData: { title: "" },
|
||||
setSuccessData: () => {},
|
||||
successOpen: false,
|
||||
setSuccessOpen: () => {},
|
||||
notificationCenter: false,
|
||||
setNotificationCenter: () => {},
|
||||
notificationList: [],
|
||||
pushNotificationList: () => {},
|
||||
clearNotificationList: () => {},
|
||||
removeFromNotificationList: () => {},
|
||||
};
|
||||
|
||||
export const alertContext = createContext<alertContextType>(initialValue);
|
||||
|
||||
export function AlertProvider({ children }:{children:ReactNode}) {
|
||||
const [errorData, setErrorDataState] = useState<{
|
||||
title: string;
|
||||
list?: Array<string>;
|
||||
}>({ title: "", list: [] });
|
||||
const [errorOpen, setErrorOpen] = useState(false);
|
||||
const [noticeData, setNoticeDataState] = useState<{
|
||||
title: string;
|
||||
link?: string;
|
||||
}>({ title: "", link: "" });
|
||||
const [noticeOpen, setNoticeOpen] = useState(false);
|
||||
const [successData, setSuccessDataState] = useState<{ title: string }>({
|
||||
title: "",
|
||||
});
|
||||
const [successOpen, setSuccessOpen] = useState(false);
|
||||
const [notificationCenter, setNotificationCenter] = useState(false);
|
||||
const [notificationList, setNotificationList] = useState([]);
|
||||
const pushNotificationList = (notification: AlertItemType) => {
|
||||
setNotificationList((old) => {
|
||||
let newNotificationList = _.cloneDeep(old);
|
||||
newNotificationList.unshift(notification);
|
||||
return newNotificationList;
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Sets the error data state, opens the error dialog and pushes the new error notification to the notification list
|
||||
* @param newState An object containing the new error data, including title and optional list of error messages
|
||||
*/
|
||||
function setErrorData(newState: { title: string; list?: Array<string> }) {
|
||||
setErrorDataState(newState);
|
||||
setErrorOpen(true);
|
||||
if (newState.title) {
|
||||
setNotificationCenter(true);
|
||||
pushNotificationList({
|
||||
type: "error",
|
||||
title: newState.title,
|
||||
list: newState.list,
|
||||
id: _.uniqueId(),
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Sets the state of the notice data and opens the notice modal, also adds a new notice to the notification center if the title is defined.
|
||||
* @param newState An object containing the title of the notice and optionally a link.
|
||||
*/
|
||||
function setNoticeData(newState: { title: string; link?: string }) {
|
||||
setNoticeDataState(newState);
|
||||
setNoticeOpen(true);
|
||||
if (newState.title) {
|
||||
// Add new notice to notification center
|
||||
setNotificationCenter(true);
|
||||
pushNotificationList({
|
||||
type: "notice",
|
||||
title: newState.title,
|
||||
link: newState.link,
|
||||
id: _.uniqueId(),
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Update the success data state and show a success alert notification.
|
||||
* @param newState - A state object with a "title" property to set in the success data state.
|
||||
*/
|
||||
function setSuccessData(newState: { title: string }) {
|
||||
setSuccessDataState(newState); // update the success data state with the provided new state
|
||||
setSuccessOpen(true); // open the success alert
|
||||
|
||||
// If the new state has a "title" property, add a new success notification to the list
|
||||
if (newState.title) {
|
||||
setNotificationCenter(true); // show the notification center
|
||||
pushNotificationList({ // add the new notification to the list
|
||||
type: "success",
|
||||
title: newState.title,
|
||||
id: _.uniqueId(),
|
||||
});
|
||||
}
|
||||
}
|
||||
function clearNotificationList() {
|
||||
setNotificationList([]);
|
||||
}
|
||||
function removeFromNotificationList(index: string) {
|
||||
// set the notification list to a new array that filters out the alert with the matching id
|
||||
setNotificationList((prevAlertsList) =>
|
||||
prevAlertsList.filter((alert) => alert.id !== index)
|
||||
);
|
||||
}
|
||||
return (
|
||||
<alertContext.Provider
|
||||
value={{
|
||||
removeFromNotificationList,
|
||||
clearNotificationList,
|
||||
notificationList,
|
||||
pushNotificationList,
|
||||
setNotificationCenter,
|
||||
notificationCenter,
|
||||
errorData,
|
||||
setErrorData,
|
||||
errorOpen,
|
||||
setErrorOpen,
|
||||
noticeData,
|
||||
setNoticeData,
|
||||
noticeOpen,
|
||||
setNoticeOpen,
|
||||
successData,
|
||||
setSuccessData,
|
||||
successOpen,
|
||||
setSuccessOpen,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</alertContext.Provider>
|
||||
);
|
||||
}
|
||||
34
langflow/frontend/src/contexts/darkContext.tsx
Normal file
34
langflow/frontend/src/contexts/darkContext.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { createContext, useEffect, useState } from "react";
|
||||
|
||||
type darkContextType = {
|
||||
dark: {};
|
||||
setDark: (newState: {}) => void;
|
||||
};
|
||||
|
||||
const initialValue = {
|
||||
dark: {},
|
||||
setDark: () => {},
|
||||
};
|
||||
|
||||
export const darkContext = createContext<darkContextType>(initialValue);
|
||||
|
||||
export function DarkProvider({ children }) {
|
||||
const [dark, setDark] = useState(false);
|
||||
useEffect(()=>{
|
||||
if(dark){
|
||||
document.getElementById("body").classList.add("dark");
|
||||
} else {
|
||||
document.getElementById("body").classList.remove("dark");
|
||||
}
|
||||
}, [dark])
|
||||
return (
|
||||
<darkContext.Provider
|
||||
value={{
|
||||
dark,
|
||||
setDark,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</darkContext.Provider>
|
||||
);
|
||||
}
|
||||
26
langflow/frontend/src/contexts/index.tsx
Normal file
26
langflow/frontend/src/contexts/index.tsx
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { ReactNode } from "react";
|
||||
import { AlertProvider } from "./alertContext";
|
||||
import { DarkProvider } from "./darkContext";
|
||||
import { LocationProvider } from "./locationContext";
|
||||
import PopUpProvider from "./popUpContext";
|
||||
import { TabsProvider } from "./tabsContext";
|
||||
import { TypesProvider } from "./typesContext";
|
||||
|
||||
export default function ContextWrapper({ children }: { children: ReactNode }) {
|
||||
//element to wrap all context
|
||||
return (
|
||||
<>
|
||||
<DarkProvider>
|
||||
<LocationProvider>
|
||||
<AlertProvider>
|
||||
<PopUpProvider>
|
||||
<TypesProvider>
|
||||
<TabsProvider>{children}</TabsProvider>
|
||||
</TypesProvider>
|
||||
</PopUpProvider>
|
||||
</AlertProvider>
|
||||
</LocationProvider>
|
||||
</DarkProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
79
langflow/frontend/src/contexts/locationContext.tsx
Normal file
79
langflow/frontend/src/contexts/locationContext.tsx
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import { createContext, ReactNode, useState } from "react";
|
||||
|
||||
//types for location context
|
||||
type locationContextType = {
|
||||
current: Array<string>;
|
||||
setCurrent: (newState: Array<string>) => void;
|
||||
isStackedOpen: boolean;
|
||||
setIsStackedOpen: (newState: boolean) => void;
|
||||
showSideBar: boolean;
|
||||
setShowSideBar: (newState: boolean) => void;
|
||||
extraNavigation: {
|
||||
title: string;
|
||||
options?: Array<{
|
||||
name: string;
|
||||
href: string;
|
||||
icon: any;
|
||||
children?: Array<any>;
|
||||
}>;
|
||||
};
|
||||
setExtraNavigation: (newState: {
|
||||
title: string;
|
||||
options?: Array<{
|
||||
name: string;
|
||||
href: string;
|
||||
icon: any;
|
||||
children?: Array<any>;
|
||||
}>;
|
||||
}) => void;
|
||||
extraComponent: any;
|
||||
setExtraComponent: (newState: any) => void;
|
||||
};
|
||||
|
||||
//initial value for location context
|
||||
const initialValue = {
|
||||
//actual
|
||||
current: window.location.pathname.replace(/\/$/g, "").split("/"),
|
||||
isStackedOpen:
|
||||
window.innerWidth > 1024 && window.location.pathname.split("/")[1]
|
||||
? true
|
||||
: false,
|
||||
setCurrent: () => {},
|
||||
setIsStackedOpen: () => {},
|
||||
showSideBar: window.location.pathname.split("/")[1] ? true : false,
|
||||
setShowSideBar: () => {},
|
||||
extraNavigation: { title: "" },
|
||||
setExtraNavigation: () => {},
|
||||
extraComponent: <></>,
|
||||
setExtraComponent: () => {},
|
||||
};
|
||||
|
||||
export const locationContext = createContext<locationContextType>(initialValue);
|
||||
|
||||
export function LocationProvider({ children }:{children:ReactNode}) {
|
||||
const [current, setCurrent] = useState(initialValue.current);
|
||||
const [isStackedOpen, setIsStackedOpen] = useState(
|
||||
initialValue.isStackedOpen
|
||||
);
|
||||
const [showSideBar, setShowSideBar] = useState(initialValue.showSideBar);
|
||||
const [extraNavigation, setExtraNavigation] = useState({ title: "" });
|
||||
const [extraComponent, setExtraComponent] = useState(<></>);
|
||||
return (
|
||||
<locationContext.Provider
|
||||
value={{
|
||||
isStackedOpen,
|
||||
setIsStackedOpen,
|
||||
current,
|
||||
setCurrent,
|
||||
showSideBar,
|
||||
setShowSideBar,
|
||||
extraNavigation,
|
||||
setExtraNavigation,
|
||||
extraComponent,
|
||||
setExtraComponent,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</locationContext.Provider>
|
||||
);
|
||||
}
|
||||
33
langflow/frontend/src/contexts/popUpContext.tsx
Normal file
33
langflow/frontend/src/contexts/popUpContext.tsx
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { createContext } from "react";
|
||||
import React, { useState } from "react";
|
||||
|
||||
//context to set JSX element on the DOM
|
||||
export const PopUpContext = createContext({
|
||||
openPopUp: (popUpElement: JSX.Element) => {},
|
||||
closePopUp: () => {},
|
||||
});
|
||||
|
||||
interface PopUpProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const PopUpProvider = ({ children }: PopUpProviderProps) => {
|
||||
const [popUpElement, setPopUpElement] = useState<JSX.Element | null>(null);
|
||||
|
||||
const openPopUp = (element: JSX.Element) => {
|
||||
setPopUpElement(element);
|
||||
};
|
||||
|
||||
const closePopUp = () => {
|
||||
setPopUpElement(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<PopUpContext.Provider value={{ openPopUp, closePopUp }}>
|
||||
{children}
|
||||
{popUpElement}
|
||||
</PopUpContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default PopUpProvider;
|
||||
179
langflow/frontend/src/contexts/tabsContext.tsx
Normal file
179
langflow/frontend/src/contexts/tabsContext.tsx
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
import { createContext, useEffect, useState, useRef, ReactNode } from "react";
|
||||
import { FlowType } from "../types/flow";
|
||||
import { TabsContextType } from "../types/tabs";
|
||||
|
||||
const TabsContextInitialValue: TabsContextType = {
|
||||
tabIndex: 0,
|
||||
setTabIndex: (index: number) => {},
|
||||
flows: [],
|
||||
removeFlow: (id: string) => {},
|
||||
addFlow: (flowData?: any) => {},
|
||||
updateFlow: (newFlow: FlowType) => {},
|
||||
incrementNodeId: () => 0,
|
||||
downloadFlow: () => {},
|
||||
uploadFlow: () => {},
|
||||
};
|
||||
|
||||
export const TabsContext = createContext<TabsContextType>(
|
||||
TabsContextInitialValue
|
||||
);
|
||||
|
||||
export function TabsProvider({ children }: { children: ReactNode }) {
|
||||
const [tabIndex, setTabIndex] = useState(0);
|
||||
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
||||
const [id, setId] = useState(0);
|
||||
|
||||
const newNodeId = useRef(0);
|
||||
function incrementNodeId() {
|
||||
newNodeId.current = newNodeId.current + 1;
|
||||
return newNodeId.current;
|
||||
}
|
||||
useEffect(() => {
|
||||
//save tabs locally
|
||||
if (flows.length !== 0)
|
||||
window.localStorage.setItem(
|
||||
"tabsData",
|
||||
JSON.stringify({ tabIndex, flows, id, nodeId: newNodeId.current })
|
||||
);
|
||||
}, [flows, id, tabIndex, newNodeId]);
|
||||
|
||||
useEffect(() => {
|
||||
//get tabs locally saved
|
||||
let cookie = window.localStorage.getItem("tabsData");
|
||||
if (cookie) {
|
||||
let cookieObject = JSON.parse(cookie);
|
||||
setTabIndex(cookieObject.tabIndex);
|
||||
setFlows(cookieObject.flows);
|
||||
setId(cookieObject.id);
|
||||
newNodeId.current = cookieObject.nodeId;
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Downloads the current flow as a JSON file
|
||||
*/
|
||||
function downloadFlow() {
|
||||
// create a data URI with the current flow data
|
||||
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
|
||||
JSON.stringify(flows[tabIndex])
|
||||
)}`;
|
||||
|
||||
// create a link element and set its properties
|
||||
const link = document.createElement("a");
|
||||
link.href = jsonString;
|
||||
link.download = `${flows[tabIndex].name}.json`;
|
||||
|
||||
// simulate a click on the link element to trigger the download
|
||||
link.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a file input and listens to a change event to upload a JSON flow file.
|
||||
* If the file type is application/json, the file is read and parsed into a JSON object.
|
||||
* The resulting JSON object is passed to the addFlow function.
|
||||
*/
|
||||
function uploadFlow() {
|
||||
// create a file input
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
// add a change event listener to the file input
|
||||
input.onchange = (e: Event) => {
|
||||
// check if the file type is application/json
|
||||
if ((e.target as HTMLInputElement).files[0].type === "application/json") {
|
||||
// get the file from the file input
|
||||
const file = (e.target as HTMLInputElement).files[0];
|
||||
// read the file as text
|
||||
file.text().then((text) => {
|
||||
// parse the text into a JSON object
|
||||
addFlow(JSON.parse(text));
|
||||
});
|
||||
}
|
||||
};
|
||||
// trigger the file input click event to open the file dialog
|
||||
input.click();
|
||||
}
|
||||
/**
|
||||
* Removes a flow from an array of flows based on its id.
|
||||
* Updates the state of flows and tabIndex using setFlows and setTabIndex hooks.
|
||||
* @param {string} id - The id of the flow to remove.
|
||||
*/
|
||||
function removeFlow(id: string) {
|
||||
setFlows((prevState) => {
|
||||
const newFlows = [...prevState];
|
||||
const index = newFlows.findIndex((flow) => flow.id === id);
|
||||
if (index >= 0) {
|
||||
if (index === tabIndex) {
|
||||
setTabIndex(flows.length - 2);
|
||||
newFlows.splice(index, 1);
|
||||
} else {
|
||||
let flowId = flows[tabIndex].id;
|
||||
newFlows.splice(index, 1);
|
||||
setTabIndex(newFlows.findIndex((flow) => flow.id === flowId));
|
||||
}
|
||||
}
|
||||
return newFlows;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Add a new flow to the list of flows.
|
||||
* @param flow Optional flow to add.
|
||||
*/
|
||||
function addFlow(flow?: FlowType) {
|
||||
// Get data from the flow or set it to null if there's no flow provided.
|
||||
const data = flow?.data ? flow.data : null;
|
||||
|
||||
// Create a new flow with a default name if no flow is provided.
|
||||
let newFlow: FlowType = {
|
||||
name: flow ? flow.name : "flow" + id,
|
||||
id: id.toString(),
|
||||
data,
|
||||
chat: flow ? flow.chat : [],
|
||||
};
|
||||
|
||||
// Increment the ID counter.
|
||||
setId((old) => old + 1);
|
||||
|
||||
// Add the new flow to the list of flows.
|
||||
setFlows((prevState) => {
|
||||
const newFlows = [...prevState, newFlow];
|
||||
return newFlows;
|
||||
});
|
||||
|
||||
// Set the tab index to the new flow.
|
||||
setTabIndex(flows.length);
|
||||
}
|
||||
/**
|
||||
* Updates an existing flow with new data
|
||||
* @param newFlow - The new flow object containing the updated data
|
||||
*/
|
||||
function updateFlow(newFlow: FlowType) {
|
||||
setFlows((prevState) => {
|
||||
const newFlows = [...prevState];
|
||||
const index = newFlows.findIndex((flow) => flow.id === newFlow.id);
|
||||
if (index !== -1) {
|
||||
newFlows[index].data = newFlow.data;
|
||||
newFlows[index].name = newFlow.name;
|
||||
newFlows[index].chat = newFlow.chat;
|
||||
}
|
||||
return newFlows;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<TabsContext.Provider
|
||||
value={{
|
||||
tabIndex,
|
||||
setTabIndex,
|
||||
flows,
|
||||
incrementNodeId,
|
||||
removeFlow,
|
||||
addFlow,
|
||||
updateFlow,
|
||||
downloadFlow,
|
||||
uploadFlow,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</TabsContext.Provider>
|
||||
);
|
||||
}
|
||||
38
langflow/frontend/src/contexts/typesContext.tsx
Normal file
38
langflow/frontend/src/contexts/typesContext.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { createContext, ReactNode, useState } from "react";
|
||||
import { Node} from "reactflow";
|
||||
import { typesContextType } from "../types/typesContext";
|
||||
|
||||
//context to share types adn functions from nodes to flow
|
||||
|
||||
const initialValue:typesContextType = {
|
||||
reactFlowInstance: null,
|
||||
setReactFlowInstance: () => {},
|
||||
deleteNode: () => {},
|
||||
types: {},
|
||||
setTypes: () => {},
|
||||
};
|
||||
|
||||
export const typesContext = createContext<typesContextType>(initialValue);
|
||||
|
||||
export function TypesProvider({ children }:{children:ReactNode}) {
|
||||
const [types, setTypes] = useState({});
|
||||
const [reactFlowInstance, setReactFlowInstance] = useState(null);
|
||||
function deleteNode(idx:string) {
|
||||
reactFlowInstance.setNodes(
|
||||
reactFlowInstance.getNodes().filter((n:Node) => n.id !== idx)
|
||||
);
|
||||
}
|
||||
return (
|
||||
<typesContext.Provider
|
||||
value={{
|
||||
types,
|
||||
setTypes,
|
||||
reactFlowInstance,
|
||||
setReactFlowInstance,
|
||||
deleteNode,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</typesContext.Provider>
|
||||
);
|
||||
}
|
||||
13
langflow/frontend/src/controllers/NodesServices/index.ts
Normal file
13
langflow/frontend/src/controllers/NodesServices/index.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { APIObjectType, sendAllProps } from '../../types/api/index';
|
||||
import axios, { AxiosResponse } from "axios";
|
||||
|
||||
const backendUrl = process.env.BACKEND || "http://localhost:5003";
|
||||
|
||||
export async function getAll():Promise<AxiosResponse<APIObjectType>> {
|
||||
return await axios.get(`${backendUrl}/all`);
|
||||
}
|
||||
|
||||
export async function sendAll(data:sendAllProps) {
|
||||
console.log(data);
|
||||
return await axios.post(`${backendUrl}/predict`, data);
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
18
langflow/frontend/src/index.tsx
Normal file
18
langflow/frontend/src/index.tsx
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import ContextWrapper from './contexts';
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
);
|
||||
root.render(
|
||||
<ContextWrapper>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</ContextWrapper>
|
||||
);
|
||||
reportWebVitals();
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
104
langflow/frontend/src/modals/textAreaModal/index.tsx
Normal file
104
langflow/frontend/src/modals/textAreaModal/index.tsx
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { XMarkIcon, ClipboardDocumentListIcon } from "@heroicons/react/24/outline";
|
||||
import { Fragment, useContext, useRef, useState } from "react";
|
||||
import { PopUpContext } from "../../contexts/popUpContext";
|
||||
|
||||
export default function TextAreaModal({value, setValue}:{setValue:(value:string)=>void,value:string|string[]}){
|
||||
const [open, setOpen] = useState(true);
|
||||
const [myValue, setMyValue] = useState(value);
|
||||
const { closePopUp } = useContext(PopUpContext);
|
||||
const ref = useRef();
|
||||
function setModalOpen(x:boolean){
|
||||
setOpen(x);
|
||||
if(x === false){
|
||||
setTimeout(() => {closePopUp()}, 300);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Transition.Root show={open} appear={true} as={Fragment}>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-10"
|
||||
onClose={setModalOpen}
|
||||
initialFocus={ref}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative flex flex-col justify-between transform h-[600px] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[700px]">
|
||||
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Close</span>
|
||||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="h-full w-full flex flex-col justify-center items-center">
|
||||
<div className="flex w-full pb-4 z-10 justify-center shadow-sm">
|
||||
<div className="mx-auto mt-4 flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 dark:bg-gray-900 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<ClipboardDocumentListIcon
|
||||
className="h-6 w-6 text-blue-600"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 text-center sm:ml-4 sm:text-left">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium dark:text-white leading-10 text-gray-900"
|
||||
>
|
||||
Edit text
|
||||
</Dialog.Title>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-full w-full bg-gray-200 dark:bg-gray-900 p-4 gap-4 flex flex-row justify-center items-center">
|
||||
<div className="flex h-full w-full">
|
||||
<div className="overflow-hidden px-4 py-5 sm:p-6 w-full rounded-lg bg-white dark:bg-gray-800 shadow">
|
||||
<textarea ref={ref} className="form-input h-full w-full rounded-lg border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-white" value={myValue} onChange={(e) => {setMyValue(e.target.value); setValue(e.target.value)}}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-200 dark:bg-gray-900 w-full pb-3 flex flex-row-reverse px-4">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm"
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
>
|
||||
Finish editing
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { ConnectionLineComponentProps } from 'reactflow';
|
||||
|
||||
|
||||
|
||||
const ConnectionLineComponent = ({
|
||||
fromX,
|
||||
fromY,
|
||||
toX,
|
||||
toY,
|
||||
connectionLineStyle = {} // provide a default value for connectionLineStyle
|
||||
}:ConnectionLineComponentProps) => {
|
||||
return (
|
||||
<g>
|
||||
<path
|
||||
fill="none"
|
||||
stroke="#222"
|
||||
strokeWidth={1.5}
|
||||
className="animated dark:stroke-gray-400"
|
||||
d={`M${fromX},${fromY} C ${fromX} ${toY} ${fromX} ${toY} ${toX},${toY}`}
|
||||
style={connectionLineStyle}
|
||||
/>
|
||||
<circle
|
||||
cx={toX}
|
||||
cy={toY}
|
||||
fill="#fff"
|
||||
r={3}
|
||||
stroke="#222"
|
||||
className="dark:stroke-gray-400 dark:fill-gray-800"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConnectionLineComponent;
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import {
|
||||
ChevronRightIcon,
|
||||
} from "@heroicons/react/24/solid";
|
||||
import { Disclosure } from "@headlessui/react";
|
||||
import { DisclosureComponentType } from "../../../../types/components";
|
||||
|
||||
export default function DisclosureComponent({
|
||||
button: { title, Icon, buttons = [] },
|
||||
children,
|
||||
}: DisclosureComponentType
|
||||
) {
|
||||
return (
|
||||
<Disclosure as="div" key={title}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<div>
|
||||
<div className="select-none bg-gray-50 dark:bg-gray-700 dark:border-y-gray-600 w-full flex justify-between items-center -mt-px px-3 py-2 border-y border-y-gray-200">
|
||||
<div className="flex gap-4">
|
||||
<Icon className="w-6 text-gray-800 dark:text-white" />
|
||||
<span className="flex items-center text-sm text-gray-800 dark:text-white font-medium">
|
||||
{title}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
{buttons.map((x, index) => (
|
||||
<button key={index} onClick={x.onClick}>
|
||||
{x.Icon}
|
||||
</button>
|
||||
))}
|
||||
<Disclosure.Button as="button">
|
||||
<ChevronRightIcon
|
||||
className={`${
|
||||
open ? "rotate-90 transform" : ""
|
||||
} h-4 w-4 text-gray-800 dark:text-white`}
|
||||
/>
|
||||
</Disclosure.Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Disclosure.Panel as="div" className="-mt-px">
|
||||
{children}
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
import { Bars2Icon } from "@heroicons/react/24/outline";
|
||||
import DisclosureComponent from "../DisclosureComponent";
|
||||
import {
|
||||
nodeColors,
|
||||
nodeIcons,
|
||||
nodeNames,
|
||||
} from "../../../../utils";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { getAll } from "../../../../controllers/NodesServices";
|
||||
import { typesContext } from "../../../../contexts/typesContext";
|
||||
import { APIClassType, APIKindType, APIObjectType } from "../../../../types/api";
|
||||
|
||||
export default function ExtraSidebar() {
|
||||
const [data, setData] = useState({});
|
||||
const { setTypes} = useContext(typesContext);
|
||||
|
||||
useEffect(() => {
|
||||
async function getTypes():Promise<void>{
|
||||
|
||||
// Make an asynchronous API call to retrieve all data.
|
||||
let result = await getAll();
|
||||
|
||||
// Update the state of the component with the retrieved data.
|
||||
setData(result.data);
|
||||
|
||||
// Set the types by reducing over the keys of the result data and updating the accumulator.
|
||||
setTypes(
|
||||
Object.keys(result.data).reduce(
|
||||
(acc, curr) => {
|
||||
Object.keys(result.data[curr]).forEach((c:keyof APIKindType) => {
|
||||
acc[c] = curr;
|
||||
// Add the base classes to the accumulator as well.
|
||||
result.data[curr][c].base_classes?.forEach((b) => {
|
||||
acc[b] = curr;
|
||||
});
|
||||
});
|
||||
return acc;
|
||||
},{}
|
||||
)
|
||||
);
|
||||
}
|
||||
// Call the getTypes function.
|
||||
getTypes();
|
||||
}, [setTypes]);
|
||||
|
||||
|
||||
function onDragStart(event: React.DragEvent<any>, data:{type:string,node?:APIClassType}) {
|
||||
//start drag event
|
||||
event.dataTransfer.effectAllowed = "move";
|
||||
event.dataTransfer.setData("json", JSON.stringify(data));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-1 w-full">
|
||||
{Object.keys(data).map((d:keyof APIObjectType, i) => (
|
||||
<DisclosureComponent
|
||||
key={i}
|
||||
button={{ title: nodeNames[d], Icon: nodeIcons[d] }}
|
||||
>
|
||||
<div className="p-2 flex flex-col gap-2">
|
||||
{Object.keys(data[d]).map((t: string, k) => (
|
||||
<div key={k}>
|
||||
<div
|
||||
draggable
|
||||
className={" cursor-grab border-l-8 rounded-l-md"}
|
||||
style={{ borderLeftColor: nodeColors[d] }}
|
||||
onDragStart={(event) =>
|
||||
onDragStart(event, {
|
||||
type: t,
|
||||
node: data[d][t],
|
||||
})
|
||||
}
|
||||
>
|
||||
<div className="flex w-full justify-between text-sm px-3 py-1 items-center border-dashed border-gray-400 dark:border-gray-600 border-l-0 rounded-md rounded-l-none border">
|
||||
<span className="text-black dark:text-white w-36 truncate text-xs">{t}</span>
|
||||
<Bars2Icon className="w-4 h-6 text-gray-400 dark:text-gray-600" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</DisclosureComponent>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
import { PlusIcon, XMarkIcon } from "@heroicons/react/24/solid";
|
||||
import { useContext, useState } from "react";
|
||||
import { TabsContext } from "../../../../contexts/tabsContext";
|
||||
import { FlowType } from "../../../../types/flow";
|
||||
|
||||
var _ = require("lodash");
|
||||
|
||||
export default function TabComponent({ selected, flow, onClick }:{flow:FlowType,selected:boolean,onClick:()=>void}) {
|
||||
const { removeFlow, updateFlow, flows } =
|
||||
useContext(TabsContext);
|
||||
const [isRename, setIsRename] = useState(false);
|
||||
const [value, setValue] = useState("");
|
||||
return (
|
||||
<>
|
||||
{flow ? (
|
||||
!selected ? (
|
||||
<div
|
||||
className="dark:text-white flex justify-between select-none truncate w-44 items-center px-4 my-1.5 border-x border-x-gray-300 dark:border-x-gray-600 -ml-px"
|
||||
onClick={onClick}
|
||||
>
|
||||
{flow.name}
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
removeFlow(flow.id);
|
||||
}}
|
||||
>
|
||||
<XMarkIcon className="h-4 hover:bg-white dark:hover:bg-gray-600 rounded-full" />
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-white dark:text-white dark:bg-gray-700 flex select-none justify-between w-44 items-center border border-b-0 border-gray-300 dark:border-gray-600 px-4 py-1 rounded-t-xl -ml-px">
|
||||
{isRename ? (
|
||||
<input
|
||||
autoFocus
|
||||
className="bg-transparent focus:border-none active:outline hover:outline focus:outline outline-gray-300 rounded-md w-28"
|
||||
onBlur={() => {
|
||||
setIsRename(false);
|
||||
if (value !== "") {
|
||||
let newFlow = _.cloneDeep(flow);
|
||||
newFlow.name = value;
|
||||
updateFlow(newFlow);
|
||||
}
|
||||
}}
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
setValue(e.target.value);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className="text-left truncate"
|
||||
onDoubleClick={() => {
|
||||
setIsRename(true);
|
||||
setValue(flow.name);
|
||||
}}
|
||||
>
|
||||
{flow.name}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={() => {
|
||||
removeFlow(flow.id);
|
||||
}}
|
||||
>
|
||||
{flows.length > 1 && (
|
||||
<XMarkIcon className="h-4 hover:bg-gray-100 dark:hover:bg-gray-600 rounded-full" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<div className="h-full py-1.5 flex justify-center items-center">
|
||||
<button
|
||||
className="px-3 flex items-center h-full pb-0.5 pt-0.5 border-x-gray-300 dark:border-x-gray-600 dark:text-white -ml-px"
|
||||
onClick={onClick}
|
||||
>
|
||||
<PlusIcon className="h-5 rounded-full hover:bg-white dark:hover:bg-gray-600" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
import { useContext, useEffect, useState } from "react";
|
||||
import { ReactFlowProvider } from "reactflow";
|
||||
import TabComponent from "../tabComponent";
|
||||
import { TabsContext } from "../../../../contexts/tabsContext";
|
||||
import FlowPage from "../..";
|
||||
import { darkContext } from "../../../../contexts/darkContext";
|
||||
import { BellIcon, MoonIcon, SunIcon } from "@heroicons/react/24/outline";
|
||||
import { PopUpContext } from "../../../../contexts/popUpContext";
|
||||
import AlertDropdown from "../../../../alerts/alertDropDown";
|
||||
import { alertContext } from "../../../../contexts/alertContext";
|
||||
|
||||
export default function TabsManagerComponent() {
|
||||
const { flows, addFlow, tabIndex, setTabIndex } = useContext(TabsContext);
|
||||
const { openPopUp } = useContext(PopUpContext);
|
||||
const AlertWidth = 256
|
||||
const { dark, setDark } = useContext(darkContext);
|
||||
const {notificationCenter, setNotificationCenter} = useContext(alertContext)
|
||||
useEffect(() => {
|
||||
//create the first flow
|
||||
if (flows.length === 0) {
|
||||
addFlow();
|
||||
}
|
||||
}, [addFlow, flows.length]);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full flex flex-col">
|
||||
<div className="w-full flex pr-2 flex-row text-center items-center bg-gray-100 dark:bg-gray-800 px-2">
|
||||
{flows.map((flow, index) => {
|
||||
return (
|
||||
<TabComponent
|
||||
onClick={() => setTabIndex(index)}
|
||||
selected={index === tabIndex}
|
||||
key={index}
|
||||
flow={flow}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<TabComponent
|
||||
onClick={() => {
|
||||
addFlow();
|
||||
}}
|
||||
selected={false}
|
||||
flow={null}
|
||||
/>
|
||||
<div className="ml-auto mr-2 flex gap-3">
|
||||
<button
|
||||
className="text-gray-400 hover:text-gray-500 "
|
||||
onClick={() => {
|
||||
setDark(!dark);
|
||||
}}
|
||||
>
|
||||
{dark ? (
|
||||
<SunIcon className="h-5 w-5" />
|
||||
) : (
|
||||
<MoonIcon className="h-5 w-5" />
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
className="text-gray-400 hover:text-gray-500 relative"
|
||||
onClick={(event: React.MouseEvent<HTMLElement>) => {
|
||||
setNotificationCenter(false)
|
||||
const top = (event.target as Element).getBoundingClientRect().top
|
||||
const left = (event.target as Element).getBoundingClientRect().left
|
||||
openPopUp(<div className="z-10 absolute" style={{top:top+20, left:left-AlertWidth}}><AlertDropdown/></div>)
|
||||
}}
|
||||
>
|
||||
{notificationCenter&&<div className='absolute w-1.5 h-1.5 rounded-full bg-red-600 right-[3px]'></div>}
|
||||
<BellIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full h-full">
|
||||
<ReactFlowProvider>
|
||||
{flows[tabIndex] ? (
|
||||
<FlowPage flow={flows[tabIndex]}></FlowPage>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</ReactFlowProvider>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
200
langflow/frontend/src/pages/FlowPage/index.tsx
Normal file
200
langflow/frontend/src/pages/FlowPage/index.tsx
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
import { useCallback, useContext, useEffect, useRef } from "react";
|
||||
import ReactFlow, {
|
||||
Background,
|
||||
Controls,
|
||||
addEdge,
|
||||
useEdgesState,
|
||||
useNodesState,
|
||||
useReactFlow,
|
||||
ControlButton,
|
||||
EdgeChange,
|
||||
Connection,
|
||||
} from "reactflow";
|
||||
import { locationContext } from "../../contexts/locationContext";
|
||||
import ExtraSidebar from "./components/extraSidebarComponent";
|
||||
import Chat from "../../components/chatComponent";
|
||||
import GenericNode from "../../CustomNodes/GenericNode";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import { typesContext } from "../../contexts/typesContext";
|
||||
import {
|
||||
ArrowDownTrayIcon,
|
||||
ArrowUpTrayIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import ConnectionLineComponent from "./components/ConnectionLineComponent";
|
||||
import { FlowType, NodeType } from "../../types/flow";
|
||||
import { APIClassType } from "../../types/api";
|
||||
|
||||
const nodeTypes = {
|
||||
genericNode: GenericNode,
|
||||
};
|
||||
|
||||
var _ = require("lodash");
|
||||
|
||||
export default function FlowPage({ flow }:{flow:FlowType}) {
|
||||
let { updateFlow, incrementNodeId, downloadFlow, uploadFlow } =
|
||||
useContext(TabsContext);
|
||||
const { types, reactFlowInstance, setReactFlowInstance } =
|
||||
useContext(typesContext);
|
||||
const reactFlowWrapper = useRef(null);
|
||||
|
||||
const { setExtraComponent, setExtraNavigation } = useContext(locationContext);
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(
|
||||
flow.data?.nodes ?? []
|
||||
);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(
|
||||
flow.data?.edges ?? []
|
||||
);
|
||||
const { setViewport } = useReactFlow();
|
||||
|
||||
useEffect(() => {
|
||||
if (reactFlowInstance && flow) {
|
||||
flow.data = reactFlowInstance.toObject();
|
||||
updateFlow(flow);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [nodes, edges]);
|
||||
//update flow when tabs change
|
||||
useEffect(() => {
|
||||
setNodes(flow?.data?.nodes ?? []);
|
||||
setEdges(flow?.data?.edges ?? []);
|
||||
if (reactFlowInstance) {
|
||||
setViewport(flow?.data?.viewport ?? { x: 1, y: 0, zoom: 0.5 });
|
||||
}
|
||||
}, [flow, reactFlowInstance, setEdges, setNodes, setViewport]);
|
||||
//set extra sidebar
|
||||
useEffect(() => {
|
||||
setExtraComponent(<ExtraSidebar />);
|
||||
setExtraNavigation({ title: "Components" });
|
||||
}, [setExtraComponent, setExtraNavigation]);
|
||||
|
||||
const onEdgesChangeMod = useCallback(
|
||||
(s:EdgeChange[]) => {
|
||||
onEdgesChange(s);
|
||||
setNodes((x) => {
|
||||
let newX = _.cloneDeep(x);
|
||||
return newX;
|
||||
});
|
||||
},
|
||||
[onEdgesChange, setNodes]
|
||||
);
|
||||
|
||||
const onConnect = useCallback(
|
||||
(params:Connection) => {
|
||||
setEdges((eds) =>
|
||||
addEdge({ ...params, className: "animate-pulse" }, eds)
|
||||
);
|
||||
setNodes((x) => {
|
||||
let newX = _.cloneDeep(x);
|
||||
return newX;
|
||||
});
|
||||
},
|
||||
[setEdges, setNodes]
|
||||
);
|
||||
|
||||
const onDragOver = useCallback((event:React.DragEvent) => {
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = "move";
|
||||
}, []);
|
||||
|
||||
const onDrop = useCallback(
|
||||
(event:React.DragEvent) => {
|
||||
event.preventDefault();
|
||||
|
||||
// Helper function to generate a unique node ID
|
||||
function getId() {
|
||||
return `dndnode_` + incrementNodeId();
|
||||
}
|
||||
|
||||
// Get the current bounds of the ReactFlow wrapper element
|
||||
const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||
|
||||
// Extract the data from the drag event and parse it as a JSON object
|
||||
let data:{type:string,node?:APIClassType} = JSON.parse(event.dataTransfer.getData("json"));
|
||||
|
||||
// If data type is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node
|
||||
if (
|
||||
data.type !== "chatInput" ||
|
||||
(data.type === "chatInput" &&
|
||||
!reactFlowInstance.getNodes().some((n) => n.type === "chatInputNode"))
|
||||
) {
|
||||
// Calculate the position where the node should be created
|
||||
const position = reactFlowInstance.project({
|
||||
x: event.clientX - reactflowBounds.left,
|
||||
y: event.clientY - reactflowBounds.top,
|
||||
});
|
||||
|
||||
// Generate a unique node ID
|
||||
let newId = getId();
|
||||
|
||||
// Create a new node object
|
||||
const newNode:NodeType = {
|
||||
id: newId,
|
||||
type: "genericNode",
|
||||
position,
|
||||
data: {
|
||||
...data,
|
||||
id: newId,
|
||||
value: null,
|
||||
},
|
||||
};
|
||||
|
||||
// Add the new node to the list of nodes in state
|
||||
setNodes((nds) => nds.concat(newNode));
|
||||
} else {
|
||||
// If a chat input node already exists, set an error message
|
||||
setErrorData({
|
||||
title: "Error creating node",
|
||||
list: ["There can't be more than one chat input."],
|
||||
});
|
||||
}
|
||||
},
|
||||
// Specify dependencies for useCallback
|
||||
[incrementNodeId, reactFlowInstance, setErrorData, setNodes]
|
||||
);
|
||||
|
||||
|
||||
return (
|
||||
<div className="w-full h-full" ref={reactFlowWrapper}>
|
||||
{Object.keys(types).length > 0 ? (
|
||||
<>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
onMove={() =>
|
||||
updateFlow({ ...flow, data: reactFlowInstance.toObject() })
|
||||
}
|
||||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChangeMod}
|
||||
onConnect={onConnect}
|
||||
onLoad={setReactFlowInstance}
|
||||
onInit={setReactFlowInstance}
|
||||
nodeTypes={nodeTypes}
|
||||
connectionLineComponent={ConnectionLineComponent}
|
||||
onDragOver={onDragOver}
|
||||
onDrop={onDrop}
|
||||
>
|
||||
<Background className="dark:bg-gray-900"/>
|
||||
<Controls className="[&>button]:text-black [&>button]:dark:bg-gray-800 hover:[&>button]:dark:bg-gray-700 [&>button]:dark:text-gray-400 [&>button]:dark:fill-gray-400 [&>button]:dark:border-gray-600">
|
||||
<ControlButton
|
||||
onClick={() => uploadFlow()}
|
||||
>
|
||||
<ArrowUpTrayIcon />
|
||||
</ControlButton>
|
||||
|
||||
<ControlButton
|
||||
onClick={() => downloadFlow()}
|
||||
>
|
||||
<ArrowDownTrayIcon />
|
||||
</ControlButton>
|
||||
</Controls>
|
||||
</ReactFlow>
|
||||
<Chat flow={flow} reactFlowInstance={reactFlowInstance} />
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
12
langflow/frontend/src/types/alerts/index.ts
Normal file
12
langflow/frontend/src/types/alerts/index.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
export type ErrorAlertType = {title:string,list:Array<string>,id:string,removeAlert:(id:string)=>void}
|
||||
export type NoticeAlertType = {title:string,link:string,id:string,removeAlert:(id:string)=>void}
|
||||
export type SuccessAlertType = {title:string,id:string, removeAlert:(id:string)=>void}
|
||||
export type SingleAlertComponentType = {dropItem:AlertItemType,removeAlert:(index:string)=>void}
|
||||
export type AlertDropdownType = {};
|
||||
export type AlertItemType = {
|
||||
type: "notice" | "error" | "success";
|
||||
title: string;
|
||||
link?: string;
|
||||
list?: Array<string>;
|
||||
id: string;
|
||||
};
|
||||
15
langflow/frontend/src/types/api/index.ts
Normal file
15
langflow/frontend/src/types/api/index.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { Node,Edge,Viewport } from "reactflow"
|
||||
//kind and class are just representative names to represent the actual structure of the object received by the API
|
||||
|
||||
export type APIObjectType = {kind:APIKindType,[key:string]:APIKindType}
|
||||
export type APIKindType= {class:APIClassType,[key:string]:APIClassType}
|
||||
export type APITemplateType = {variable:TemplateVariableType,[key:string]:TemplateVariableType}
|
||||
export type APIClassType ={base_classes:Array<string>,description:string,template:APITemplateType,[key:string]:Array<string>|string|APITemplateType}
|
||||
export type TemplateVariableType = {type:string,required:boolean,placeholder?:string,list:boolean,show:boolean,multiline?:boolean,value?:any,[key:string]:any}
|
||||
export type sendAllProps={
|
||||
nodes: Node[];
|
||||
edges: Edge[];
|
||||
viewport: Viewport;
|
||||
message:string;
|
||||
chatHistory:{message:string,isSend:boolean}[]
|
||||
};
|
||||
5
langflow/frontend/src/types/chat/index.ts
Normal file
5
langflow/frontend/src/types/chat/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { ReactFlowInstance } from 'reactflow';
|
||||
import { FlowType } from "../flow";
|
||||
|
||||
export type ChatType = {flow:FlowType,reactFlowInstance:ReactFlowInstance}
|
||||
export type ChatMessageType = { message: string; isSend: boolean, thought?:string }
|
||||
58
langflow/frontend/src/types/components/index.ts
Normal file
58
langflow/frontend/src/types/components/index.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { ForwardRefExoticComponent, ReactElement, ReactNode } from "react";
|
||||
import { NodeDataType } from "../flow/index";
|
||||
export type InputComponentType = {
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
export type ToggleComponentType = {
|
||||
enabled: boolean;
|
||||
setEnabled: (state: boolean) => void;
|
||||
disabled: boolean;
|
||||
};
|
||||
export type DropDownComponentType = {
|
||||
title: string;
|
||||
value: string;
|
||||
options: string[];
|
||||
onSelect: (value: string) => void;
|
||||
};
|
||||
export type ParameterComponentType = {
|
||||
data: NodeDataType;
|
||||
title: string;
|
||||
id: string;
|
||||
color: string;
|
||||
left: boolean;
|
||||
type: string;
|
||||
required?: boolean;
|
||||
name?: string;
|
||||
tooltipTitle: string;
|
||||
};
|
||||
export type InputListComponentType = {
|
||||
value: string[];
|
||||
onChange: (value: string[]) => void;
|
||||
disabled: boolean;
|
||||
};
|
||||
|
||||
export type TextAreaComponentType = {
|
||||
disabled: boolean;
|
||||
onChange: (value: string[] | string) => void;
|
||||
value: string[] | string;
|
||||
};
|
||||
|
||||
export type DisclosureComponentType = {
|
||||
children: ReactNode;
|
||||
button: {
|
||||
title: string;
|
||||
Icon: ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>;
|
||||
buttons?: {
|
||||
Icon: ReactElement;
|
||||
title: string;
|
||||
onClick: (event?: React.MouseEvent) => void;
|
||||
}[];
|
||||
};
|
||||
};
|
||||
export type FloatComponentType = {
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
3
langflow/frontend/src/types/entities/index.ts
Normal file
3
langflow/frontend/src/types/entities/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { HomeIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
export type sidebarNavigationItemType = { name: string, href: string, icon: React.ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>, current: boolean }
|
||||
12
langflow/frontend/src/types/flow/index.ts
Normal file
12
langflow/frontend/src/types/flow/index.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { ChatMessageType } from './../chat/index';
|
||||
import { APIClassType } from '../api/index';
|
||||
import { ReactFlowJsonObject, XYPosition } from "reactflow";
|
||||
|
||||
export type FlowType = {
|
||||
name: string;
|
||||
id: string;
|
||||
data: ReactFlowJsonObject;
|
||||
chat: Array<ChatMessageType>;
|
||||
};
|
||||
export type NodeType = {id:string,type:string,position:XYPosition,data:NodeDataType}
|
||||
export type NodeDataType = {type:string,node?:APIClassType,id:string,value:any}
|
||||
13
langflow/frontend/src/types/tabs/index.ts
Normal file
13
langflow/frontend/src/types/tabs/index.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { FlowType } from "../flow";
|
||||
|
||||
export type TabsContextType = {
|
||||
tabIndex: number;
|
||||
setTabIndex: (index: number) => void;
|
||||
flows: Array<FlowType>;
|
||||
removeFlow: (id: string) => void;
|
||||
addFlow: (flowData?: any) => void;
|
||||
updateFlow: (newFlow: FlowType) => void;
|
||||
incrementNodeId: () => number;
|
||||
downloadFlow: () => void;
|
||||
uploadFlow: () => void;
|
||||
};
|
||||
11
langflow/frontend/src/types/typesContext/index.ts
Normal file
11
langflow/frontend/src/types/typesContext/index.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { ReactFlowInstance } from "reactflow";
|
||||
|
||||
const types:{[char: string]: string}={}
|
||||
|
||||
export type typesContextType = {
|
||||
reactFlowInstance: ReactFlowInstance|null;
|
||||
setReactFlowInstance: any;
|
||||
deleteNode: (idx: string) => void;
|
||||
types: typeof types;
|
||||
setTypes: (newState: {}) => void;
|
||||
};
|
||||
332
langflow/frontend/src/utils.ts
Normal file
332
langflow/frontend/src/utils.ts
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
import {
|
||||
RocketLaunchIcon,
|
||||
LinkIcon,
|
||||
CpuChipIcon,
|
||||
LightBulbIcon,
|
||||
CommandLineIcon,
|
||||
WrenchScrewdriverIcon,
|
||||
ComputerDesktopIcon,
|
||||
Bars3CenterLeftIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { Connection, Edge, Node, ReactFlowInstance } from "reactflow";
|
||||
|
||||
export function classNames(...classes:Array<string>) {
|
||||
return classes.filter(Boolean).join(" ");
|
||||
}
|
||||
|
||||
export const textColors = {
|
||||
white: "text-white",
|
||||
red: "text-red-700",
|
||||
orange: "text-orange-700",
|
||||
amber: "text-amber-700",
|
||||
yellow: "text-yellow-700",
|
||||
lime: "text-lime-700",
|
||||
green: "text-green-700",
|
||||
emerald: "text-emerald-700",
|
||||
teal: "text-teal-700",
|
||||
cyan: "text-cyan-700",
|
||||
sky: "text-sky-700",
|
||||
blue: "text-blue-700",
|
||||
indigo: "text-indigo-700",
|
||||
violet: "text-violet-700",
|
||||
purple: "text-purple-700",
|
||||
fuchsia: "text-fuchsia-700",
|
||||
pink: "text-pink-700",
|
||||
rose: "text-rose-700",
|
||||
black: "text-black-700",
|
||||
gray: "text-gray-700",
|
||||
};
|
||||
|
||||
export const borderLColors = {
|
||||
white: "border-l-white",
|
||||
red: "border-l-red-500",
|
||||
orange: "border-l-orange-500",
|
||||
amber: "border-l-amber-500",
|
||||
yellow: "border-l-yellow-500",
|
||||
lime: "border-l-lime-500",
|
||||
green: "border-l-green-500",
|
||||
emerald: "border-l-emerald-500",
|
||||
teal: "border-l-teal-500",
|
||||
cyan: "border-l-cyan-500",
|
||||
sky: "border-l-sky-500",
|
||||
blue: "border-l-blue-500",
|
||||
indigo: "border-l-indigo-500",
|
||||
violet: "border-l-violet-500",
|
||||
purple: "border-l-purple-500",
|
||||
fuchsia: "border-l-fuchsia-500",
|
||||
pink: "border-l-pink-500",
|
||||
rose: "border-l-rose-500",
|
||||
black: "border-l-black-500",
|
||||
gray: "border-l-gray-500",
|
||||
};
|
||||
|
||||
export const nodeColors: {[char: string]: string} = {
|
||||
prompts: "#4367BF",
|
||||
llms: "#6344BE",
|
||||
chains: "#FE7500",
|
||||
agents: "#903BBE",
|
||||
tools: "#FF3434",
|
||||
memories: "#FF9135",
|
||||
advanced: "#000000",
|
||||
chat: "#454173",
|
||||
thought:"#272541"
|
||||
};
|
||||
|
||||
export const nodeNames:{[char: string]: string} = {
|
||||
prompts: "Prompts",
|
||||
llms: "LLMs",
|
||||
chains: "Chains",
|
||||
agents: "Agents",
|
||||
tools: "Tools",
|
||||
memories: "Memories",
|
||||
advanced: "Advanced",
|
||||
chat: "Chat",
|
||||
|
||||
};
|
||||
|
||||
export const nodeIcons:{[char: string]: React.ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>} = {
|
||||
agents: RocketLaunchIcon,
|
||||
chains: LinkIcon,
|
||||
memories: CpuChipIcon,
|
||||
llms: LightBulbIcon,
|
||||
prompts: CommandLineIcon,
|
||||
tools: WrenchScrewdriverIcon,
|
||||
advanced: ComputerDesktopIcon,
|
||||
chat: Bars3CenterLeftIcon,
|
||||
};
|
||||
|
||||
export const bgColors = {
|
||||
white: "bg-white",
|
||||
red: "bg-red-100",
|
||||
orange: "bg-orange-100",
|
||||
amber: "bg-amber-100",
|
||||
yellow: "bg-yellow-100",
|
||||
lime: "bg-lime-100",
|
||||
green: "bg-green-100",
|
||||
emerald: "bg-emerald-100",
|
||||
teal: "bg-teal-100",
|
||||
cyan: "bg-cyan-100",
|
||||
sky: "bg-sky-100",
|
||||
blue: "bg-blue-100",
|
||||
indigo: "bg-indigo-100",
|
||||
violet: "bg-violet-100",
|
||||
purple: "bg-purple-100",
|
||||
fuchsia: "bg-fuchsia-100",
|
||||
pink: "bg-pink-100",
|
||||
rose: "bg-rose-100",
|
||||
black: "bg-black-100",
|
||||
gray: "bg-gray-100",
|
||||
};
|
||||
|
||||
export const bgColorsHover = {
|
||||
white: "hover:bg-white",
|
||||
black: "hover:bg-black-50",
|
||||
gray: "hover:bg-gray-50",
|
||||
red: "hover:bg-red-50",
|
||||
orange: "hover:bg-orange-50",
|
||||
amber: "hover:bg-amber-50",
|
||||
yellow: "hover:bg-yellow-50",
|
||||
lime: "hover:bg-lime-50",
|
||||
green: "hover:bg-green-50",
|
||||
emerald: "hover:bg-emerald-50",
|
||||
teal: "hover:bg-teal-50",
|
||||
cyan: "hover:bg-cyan-50",
|
||||
sky: "hover:bg-sky-50",
|
||||
blue: "hover:bg-blue-50",
|
||||
indigo: "hover:bg-indigo-50",
|
||||
violet: "hover:bg-violet-50",
|
||||
purple: "hover:bg-purple-50",
|
||||
fuchsia: "hover:bg-fuchsia-50",
|
||||
pink: "hover:bg-pink-50",
|
||||
rose: "hover:bg-rose-50",
|
||||
};
|
||||
|
||||
export const textColorsHex = {
|
||||
red: "rgb(185 28 28)",
|
||||
orange: "rgb(194 65 12)",
|
||||
amber: "rgb(180 83 9)",
|
||||
yellow: "rgb(161 98 7)",
|
||||
lime: "rgb(77 124 15)",
|
||||
green: "rgb(21 128 61)",
|
||||
emerald: "rgb(4 120 87)",
|
||||
teal: "rgb(15 118 110)",
|
||||
cyan: "rgb(14 116 144)",
|
||||
sky: "rgb(3 105 161)",
|
||||
blue: "rgb(29 78 216)",
|
||||
indigo: "rgb(67 56 202)",
|
||||
violet: "rgb(109 40 217)",
|
||||
purple: "rgb(126 34 206)",
|
||||
fuchsia: "rgb(162 28 175)",
|
||||
pink: "rgb(190 24 93)",
|
||||
rose: "rgb(190 18 60)",
|
||||
};
|
||||
|
||||
export const bgColorsHex = {
|
||||
red: "rgb(254 226 226)",
|
||||
orange: "rgb(255 237 213)",
|
||||
amber: "rgb(254 243 199)",
|
||||
yellow: "rgb(254 249 195)",
|
||||
lime: "rgb(236 252 203)",
|
||||
green: "rgb(220 252 231)",
|
||||
emerald: "rgb(209 250 229)",
|
||||
teal: "rgb(204 251 241)",
|
||||
cyan: "rgb(207 250 254)",
|
||||
sky: "rgb(224 242 254)",
|
||||
blue: "rgb(219 234 254)",
|
||||
indigo: "rgb(224 231 255)",
|
||||
violet: "rgb(237 233 254)",
|
||||
purple: "rgb(243 232 255)",
|
||||
fuchsia: "rgb(250 232 255)",
|
||||
pink: "rgb(252 231 243)",
|
||||
rose: "rgb(255 228 230)",
|
||||
};
|
||||
|
||||
export const taskTypeMap: { [key: string]: string } = {
|
||||
MULTICLASS_CLASSIFICATION: "Multiclass Classification",
|
||||
};
|
||||
|
||||
const charWidths:{[char: string]: number} = {
|
||||
" ": 0.2,
|
||||
"!": 0.2,
|
||||
'"': 0.3,
|
||||
"#": 0.5,
|
||||
$: 0.5,
|
||||
"%": 0.5,
|
||||
"&": 0.5,
|
||||
"(": 0.2,
|
||||
")": 0.2,
|
||||
"*": 0.5,
|
||||
"+": 0.5,
|
||||
",": 0.2,
|
||||
"-": 0.2,
|
||||
".": 0.1,
|
||||
"/": 0.5,
|
||||
":": 0.2,
|
||||
";": 0.2,
|
||||
"<": 0.5,
|
||||
"=": 0.5,
|
||||
">": 0.5,
|
||||
"?": 0.2,
|
||||
"@": 0.5,
|
||||
"[": 0.2,
|
||||
"\\": 0.5,
|
||||
"]": 0.2,
|
||||
"^": 0.5,
|
||||
_: 0.2,
|
||||
"`": 0.5,
|
||||
"{": 0.2,
|
||||
"|": 0.2,
|
||||
"}": 0.2,
|
||||
"~": 0.5,
|
||||
};
|
||||
|
||||
for (let i = 65; i <= 90; i++) {
|
||||
charWidths[String.fromCharCode(i)] = 0.6;
|
||||
}
|
||||
for (let i = 97; i <= 122; i++) {
|
||||
charWidths[String.fromCharCode(i)] = 0.5;
|
||||
}
|
||||
|
||||
export function measureTextWidth(text: string, fontSize:number) {
|
||||
let wordWidth = 0;
|
||||
for (let j = 0; j < text.length; j++) {
|
||||
let char = text[j];
|
||||
let charWidth = charWidths[char] || 0.5;
|
||||
wordWidth += charWidth * fontSize;
|
||||
}
|
||||
return wordWidth;
|
||||
}
|
||||
|
||||
export function measureTextHeight(text: string, width:number, fontSize:number) {
|
||||
const charHeight = fontSize;
|
||||
const lineHeight = charHeight * 1.5;
|
||||
const words = text.split(" ");
|
||||
let lineWidth = 0;
|
||||
let totalHeight = 0;
|
||||
for (let i = 0; i < words.length; i++) {
|
||||
let word = words[i];
|
||||
let wordWidth = measureTextWidth(word, fontSize);
|
||||
if (lineWidth + wordWidth + charWidths[" "] * fontSize <= width) {
|
||||
lineWidth += wordWidth + charWidths[" "] * fontSize;
|
||||
} else {
|
||||
totalHeight += lineHeight;
|
||||
lineWidth = wordWidth;
|
||||
}
|
||||
}
|
||||
totalHeight += lineHeight;
|
||||
return totalHeight;
|
||||
}
|
||||
|
||||
export function toCamelCase(str: string) {
|
||||
return str
|
||||
.split(" ")
|
||||
.map((word, index) =>
|
||||
index === 0
|
||||
? word.toLowerCase()
|
||||
: word[0].toUpperCase() + word.slice(1).toLowerCase()
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
export function toFirstUpperCase(str: string) {
|
||||
return str
|
||||
.split(" ")
|
||||
.map((word, index) => word[0].toUpperCase() + word.slice(1).toLowerCase())
|
||||
.join("");
|
||||
}
|
||||
|
||||
export function snakeToNormalCase(str: string) {
|
||||
return str
|
||||
.split("_")
|
||||
.map((word, index) => {
|
||||
if (index === 0) {
|
||||
return word[0].toUpperCase() + word.slice(1).toLowerCase();
|
||||
}
|
||||
return word.toLowerCase();
|
||||
})
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
export function roundNumber(x:number, decimals:number) {
|
||||
return Math.round(x * Math.pow(10, decimals)) / Math.pow(10, decimals);
|
||||
}
|
||||
|
||||
export function getConnectedNodes(edge: Edge, nodes: Array<Node>): Array<Node> {
|
||||
const sourceId = edge.source;
|
||||
const targetId = edge.target;
|
||||
const connectedNodes = nodes.filter(
|
||||
(node) => node.id === targetId || node.id === sourceId
|
||||
);
|
||||
return connectedNodes;
|
||||
}
|
||||
|
||||
export function isValidConnection(
|
||||
{ source, target, sourceHandle, targetHandle }:Connection,
|
||||
reactFlowInstance:ReactFlowInstance
|
||||
) {
|
||||
console.log(target)
|
||||
if (
|
||||
sourceHandle.split('|')[0] === targetHandle.split("|")[0] ||
|
||||
sourceHandle.split('|').slice(2).some((t) => t === targetHandle.split("|")[0]) ||
|
||||
targetHandle.split("|")[0] === "str"
|
||||
) {
|
||||
let targetNode = reactFlowInstance.getNode(target).data.node;
|
||||
if (!targetNode) {
|
||||
if (
|
||||
!reactFlowInstance
|
||||
.getEdges()
|
||||
.find((e) => e.targetHandle === targetHandle)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
} else if (
|
||||
(!targetNode.template[targetHandle.split("|")[1]].list &&
|
||||
!reactFlowInstance
|
||||
.getEdges()
|
||||
.find((e) => e.targetHandle === targetHandle)) ||
|
||||
targetNode.template[targetHandle.split("|")[1]].list
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
40
langflow/frontend/tailwind.config.js
Normal file
40
langflow/frontend/tailwind.config.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
const plugin = require('tailwindcss/plugin')
|
||||
module.exports = {
|
||||
content: ["./src/**/*.{js,ts,tsx,jsx}"],
|
||||
darkMode: 'class',
|
||||
important:true,
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [
|
||||
require("@tailwindcss/forms")({
|
||||
strategy: 'class', // only generate classes
|
||||
}),
|
||||
plugin(function ({ addUtilities }) {
|
||||
addUtilities({
|
||||
'.scrollbar-hide': {
|
||||
/* IE and Edge */
|
||||
'-ms-overflow-style': 'none',
|
||||
/* Firefox */
|
||||
'scrollbar-width': 'none',
|
||||
/* Safari and Chrome */
|
||||
'&::-webkit-scrollbar': {
|
||||
display: 'none'
|
||||
}
|
||||
},
|
||||
'.arrow-hide':{
|
||||
'&::-webkit-inner-spin-button':{
|
||||
'-webkit-appearance': 'none',
|
||||
'margin': 0
|
||||
},
|
||||
'&::-webkit-outer-spin-button':{
|
||||
'-webkit-appearance': 'none',
|
||||
'margin': 0
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
],
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
|
|
@ -18,7 +18,8 @@
|
|||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
"jsx": "react-jsx",
|
||||
"noImplicitAny": false
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
1494
poetry.lock
generated
Normal file
1494
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
28
pyproject.toml
Normal file
28
pyproject.toml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
[tool.poetry]
|
||||
name = "langflow"
|
||||
version = "0.0.1"
|
||||
description = "A Python package with a built-in web application"
|
||||
authors = ["Ibis Prevedello <ibiscp@gmail.com>"]
|
||||
packages = [
|
||||
{ include = "langflow"}
|
||||
]
|
||||
include = ["langflow/frontend/build/*", "langflow/frontend/build/**/*"]
|
||||
exclude = ["langflow/frontend/node_modules/*", "langflow/frontend/src/*"]
|
||||
|
||||
[tool.poetry.scripts]
|
||||
langflow = "langflow.cli:main"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10"
|
||||
openai = "^0.26.5"
|
||||
fastapi = "^0.91.0"
|
||||
uvicorn = "^0.20.0"
|
||||
beautifulsoup4 = "^4.11.2"
|
||||
google-search-results = "^2.4.1"
|
||||
google-api-python-client = "^2.79.0"
|
||||
langchain = {git = "https://github.com/ibiscp/langchain.git", rev = "ibis"}
|
||||
typer = "^0.7.0"
|
||||
16859
space_flow/package-lock.json
generated
16859
space_flow/package-lock.json
generated
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB |
|
|
@ -1,43 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.4 KiB |
|
|
@ -1,25 +0,0 @@
|
|||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
import React from 'react';
|
||||
import logo from './logo.svg';
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<p>
|
||||
Edit <code>src/App.tsx</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn React
|
||||
</a>
|
||||
</header>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
||||
1
space_flow/src/react-app-env.d.ts
vendored
1
space_flow/src/react-app-env.d.ts
vendored
|
|
@ -1 +0,0 @@
|
|||
/// <reference types="react-scripts" />
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
||||
Loading…
Add table
Add a link
Reference in a new issue