Merge branch 'logspace-ai:dev' into dev

This commit is contained in:
Deepankar Mahapatro 2023-05-15 17:48:52 +05:30 • committed by GitHub
commit 9bbd013dcb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
93 changed files with 6717 additions and 16968 deletions

View file

@ -1,59 +1,14 @@
# FROM logspace/backend_build as backend_build FROM python:3.10-slim
FROM logspace/frontend_build as frontend_build
# `python-base` sets up all our shared environment variables RUN apt-get update && apt-get install gcc g++ git make -y
FROM python:3.10-slim as langflow_build RUN useradd -m -u 1000 user
USER user
ENV HOME=/home/user \
PATH=/home/user/.local/bin:$PATH
# python WORKDIR $HOME/app
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 COPY --chown=user . $HOME/app
ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH"
RUN apt-get update \ RUN pip install langflow>==0.0.71 -U --user
&& apt-get install --no-install-recommends -y \ CMD ["langflow", "--host", "0.0.0.0", "--port", "7860"]
# 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/src/frontend/build/
RUN pip install .
EXPOSE 5003
CMD [ "langflow" ]

906
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "langflow" name = "langflow"
version = "0.0.68" version = "0.0.71"
description = "A Python package with a built-in web application" description = "A Python package with a built-in web application"
authors = ["Logspace <contact@logspace.ai>"] authors = ["Logspace <contact@logspace.ai>"]
maintainers = [ maintainers = [
@ -29,7 +29,7 @@ google-search-results = "^2.4.1"
google-api-python-client = "^2.79.0" google-api-python-client = "^2.79.0"
typer = "^0.7.0" typer = "^0.7.0"
gunicorn = "^20.1.0" gunicorn = "^20.1.0"
langchain = "~0.0.150" langchain = "^0.0.166"
openai = "^0.27.2" openai = "^0.27.2"
types-pyyaml = "^6.0.12.8" types-pyyaml = "^6.0.12.8"
dill = "^0.3.6" dill = "^0.3.6"
@ -50,6 +50,7 @@ pyarrow = "^11.0.0"
websockets = "^11.0.2" websockets = "^11.0.2"
tiktoken = "^0.3.3" tiktoken = "^0.3.3"
wikipedia = "^1.4.0" wikipedia = "^1.4.0"
gptcache = "^0.1.23"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
black = "^23.1.0" black = "^23.1.0"

View file

@ -1,12 +1,13 @@
import asyncio
from typing import Any from typing import Any
from langchain.callbacks.base import AsyncCallbackHandler from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler
from langflow.api.schemas import ChatResponse from langflow.api.schemas import ChatResponse
# https://github.com/hwchase17/chat-langchain/blob/master/callback.py # https://github.com/hwchase17/chat-langchain/blob/master/callback.py
class StreamingLLMCallbackHandler(AsyncCallbackHandler): class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
"""Callback handler for streaming LLM responses.""" """Callback handler for streaming LLM responses."""
def __init__(self, websocket): def __init__(self, websocket):
@ -15,3 +16,17 @@ class StreamingLLMCallbackHandler(AsyncCallbackHandler):
async def on_llm_new_token(self, token: str, **kwargs: Any) -> None: async def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
resp = ChatResponse(message=token, type="stream", intermediate_steps="") resp = ChatResponse(message=token, type="stream", intermediate_steps="")
await self.websocket.send_json(resp.dict()) await self.websocket.send_json(resp.dict())
class StreamingLLMCallbackHandler(BaseCallbackHandler):
"""Callback handler for streaming LLM responses."""
def __init__(self, websocket):
self.websocket = websocket
def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
resp = ChatResponse(message=token, type="stream", intermediate_steps="")
loop = asyncio.get_event_loop()
coroutine = self.websocket.send_json(resp.dict())
asyncio.run_coroutine_threadsafe(coroutine, loop)

View file

@ -1,4 +1,10 @@
from fastapi import APIRouter, WebSocket from fastapi import (
APIRouter,
WebSocket,
WebSocketDisconnect,
WebSocketException,
status,
)
from langflow.api.chat_manager import ChatManager from langflow.api.chat_manager import ChatManager
from langflow.utils.logger import logger from langflow.utils.logger import logger
@ -12,7 +18,9 @@ async def websocket_endpoint(client_id: str, websocket: WebSocket):
"""Websocket endpoint for chat.""" """Websocket endpoint for chat."""
try: try:
await chat_manager.handle_websocket(client_id, websocket) await chat_manager.handle_websocket(client_id, websocket)
except Exception as e: except WebSocketException as exc:
# Log stack trace logger.error(exc)
logger.exception(e) await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=str(exc))
raise e except WebSocketDisconnect as exc:
logger.error(exc)
await websocket.close(code=status.WS_1000_NORMAL_CLOSURE, reason=str(exc))

View file

@ -3,7 +3,7 @@ import json
from collections import defaultdict from collections import defaultdict
from typing import Dict, List from typing import Dict, List
from fastapi import WebSocket from fastapi import WebSocket, status
from langflow.api.schemas import ChatMessage, ChatResponse, FileResponse from langflow.api.schemas import ChatMessage, ChatResponse, FileResponse
from langflow.cache import cache_manager from langflow.cache import cache_manager
@ -47,7 +47,6 @@ class ChatManager:
def __init__(self): def __init__(self):
self.active_connections: Dict[str, WebSocket] = {} self.active_connections: Dict[str, WebSocket] = {}
self.chat_history = ChatHistory() self.chat_history = ChatHistory()
self.chat_history.attach(self.on_chat_history_update)
self.cache_manager = cache_manager self.cache_manager = cache_manager
self.cache_manager.attach(self.update) self.cache_manager.attach(self.update)
@ -91,7 +90,7 @@ class ChatManager:
self.active_connections[client_id] = websocket self.active_connections[client_id] = websocket
def disconnect(self, client_id: str): def disconnect(self, client_id: str):
del self.active_connections[client_id] self.active_connections.pop(client_id, None)
async def send_message(self, client_id: str, message: str): async def send_message(self, client_id: str, message: str):
websocket = self.active_connections[client_id] websocket = self.active_connections[client_id]
@ -109,7 +108,7 @@ class ChatManager:
graph_data = payload graph_data = payload
start_resp = ChatResponse(message=None, type="start", intermediate_steps="") start_resp = ChatResponse(message=None, type="start", intermediate_steps="")
self.chat_history.add_message(client_id, start_resp) await self.send_json(client_id, start_resp)
is_first_message = len(self.chat_history.get_history(client_id=client_id)) == 0 is_first_message = len(self.chat_history.get_history(client_id=client_id)) == 0
# Generate result and thought # Generate result and thought
@ -143,11 +142,12 @@ class ChatManager:
break break
response = ChatResponse( response = ChatResponse(
message=result or "", message=result,
intermediate_steps=intermediate_steps.strip(), intermediate_steps=intermediate_steps.strip(),
type="end", type="end",
files=file_responses, files=file_responses,
) )
await self.send_json(client_id, response)
self.chat_history.add_message(client_id, response) self.chat_history.add_message(client_id, response)
async def handle_websocket(self, client_id: str, websocket: WebSocket): async def handle_websocket(self, client_id: str, websocket: WebSocket):
@ -171,17 +171,24 @@ class ChatManager:
with self.cache_manager.set_client_id(client_id): with self.cache_manager.set_client_id(client_id):
await self.process_message(client_id, payload) await self.process_message(client_id, payload)
except Exception as e: except Exception as e:
# Handle any exceptions that might occur # Handle any exceptions that might occur
logger.exception(e) logger.exception(e)
# send a message to the client # send a message to the client
await self.send_message(client_id, str(e))
raise e
finally:
await self.active_connections[client_id].close( await self.active_connections[client_id].close(
code=1000, reason="Client disconnected" code=status.WS_1011_INTERNAL_ERROR, reason=str(e)[:120]
) )
self.disconnect(client_id) self.disconnect(client_id)
finally:
try:
connection = self.active_connections.get(client_id)
if connection:
await connection.close(code=1000, reason="Client disconnected")
self.disconnect(client_id)
except Exception as e:
logger.exception(e)
self.disconnect(client_id)
async def process_graph( async def process_graph(
@ -203,8 +210,8 @@ async def process_graph(
# Generate result and thought # Generate result and thought
try: try:
logger.debug("Generating result and thought") logger.debug("Generating result and thought")
result, intermediate_steps = get_result_and_steps( result, intermediate_steps = await get_result_and_steps(
langchain_object, chat_message.message or "" langchain_object, chat_message.message or "", websocket=websocket
) )
logger.debug("Generated result and intermediate_steps") logger.debug("Generated result and intermediate_steps")
return result, intermediate_steps return result, intermediate_steps

View file

@ -1,16 +1,15 @@
import logging import logging
from typing import Any, Dict
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException
from langflow.interface.run import process_graph_cached
from langflow.interface.types import build_langchain_types_dict
from langflow.api.schemas import ( from langflow.api.schemas import (
ExportedFlow, ExportedFlow,
GraphData, GraphData,
PredictRequest, PredictRequest,
PredictResponse, PredictResponse,
) )
from langflow.interface.run import process_graph_cached
from langflow.interface.types import build_langchain_types_dict
# build router # build router
router = APIRouter() router = APIRouter()

View file

@ -1,4 +1,4 @@
from typing import Any, Union, Dict, List from typing import Any, Dict, List, Union
from pydantic import BaseModel, validator from pydantic import BaseModel, validator

View file

@ -1,3 +1,5 @@
import json
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException
from langflow.api.base import ( from langflow.api.base import (
@ -7,6 +9,7 @@ from langflow.api.base import (
PromptValidationResponse, PromptValidationResponse,
validate_prompt, validate_prompt,
) )
from langflow.graph.nodes import VectorStoreNode
from langflow.interface.run import build_graph from langflow.interface.run import build_graph
from langflow.utils.logger import logger from langflow.utils.logger import logger
from langflow.utils.validate import validate_code from langflow.utils.validate import validate_code
@ -44,10 +47,11 @@ def post_validate_node(node_id: str, data: dict):
graph = build_graph(data) graph = build_graph(data)
# validate node # validate node
node = graph.get_node(node_id) node = graph.get_node(node_id)
if node is not None: if node is None:
_ = node.build() raise ValueError(f"Node {node_id} not found")
return str(node.params) if not isinstance(node, VectorStoreNode):
raise Exception(f"Node {node_id} not found") node.build()
return json.dumps({"valid": True, "params": str(node._built_object_repr())})
except Exception as e: except Exception as e:
logger.error(e) logger.exception(e)
raise HTTPException(status_code=500, detail=str(e)) from e return json.dumps({"valid": False})

View file

@ -232,6 +232,9 @@ class Node:
def __hash__(self) -> int: def __hash__(self) -> int:
return id(self) return id(self)
def _built_object_repr(self):
return repr(self._built_object)
class Edge: class Edge:
def __init__(self, source: "Node", target: "Node"): def __init__(self, source: "Node", target: "Node"):

View file

@ -139,6 +139,13 @@ class DocumentLoaderNode(Node):
def __init__(self, data: Dict): def __init__(self, data: Dict):
super().__init__(data, base_type="documentloaders") super().__init__(data, base_type="documentloaders")
def _built_object_repr(self):
# This built_object is a list of documents. Maybe we should
# show how many documents are in the list?
if self._built_object:
return f"""{self.node_type}({len(self._built_object)} documents)\nDocuments: {self._built_object[:3]}..."""
return f"{self.node_type}()"
class EmbeddingNode(Node): class EmbeddingNode(Node):
def __init__(self, data: Dict): def __init__(self, data: Dict):
@ -149,6 +156,9 @@ class VectorStoreNode(Node):
def __init__(self, data: Dict): def __init__(self, data: Dict):
super().__init__(data, base_type="vectorstores") super().__init__(data, base_type="vectorstores")
def _built_object_repr(self):
return "Vector stores can take time to build. It will build on the first query."
class MemoryNode(Node): class MemoryNode(Node):
def __init__(self, data: Dict): def __init__(self, data: Dict):
@ -158,3 +168,10 @@ class MemoryNode(Node):
class TextSplitterNode(Node): class TextSplitterNode(Node):
def __init__(self, data: Dict): def __init__(self, data: Dict):
super().__init__(data, base_type="textsplitters") super().__init__(data, base_type="textsplitters")
def _built_object_repr(self):
# This built_object is a list of documents. Maybe we should
# show how many documents are in the list?
if self._built_object:
return f"""{self.node_type}({len(self._built_object)} documents)\nDocuments: {self._built_object[:3]}..."""
return f"{self.node_type}()"

View file

@ -1,3 +1,4 @@
from abc import ABC
from typing import Any, List, Optional from typing import Any, List, Optional
from langchain import LLMChain from langchain import LLMChain
@ -27,14 +28,31 @@ from langchain.agents.agent_toolkits.vectorstore.prompt import (
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS as SQL_FORMAT_INSTRUCTIONS from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS as SQL_FORMAT_INSTRUCTIONS
from langchain.base_language import BaseLanguageModel from langchain.base_language import BaseLanguageModel
from langchain.memory.chat_memory import BaseChatMemory from langchain.memory.chat_memory import BaseChatMemory
from langchain.sql_database import SQLDatabase from langchain.sql_database import SQLDatabase
from langchain.tools.python.tool import PythonAstREPLTool from langchain.tools.python.tool import PythonAstREPLTool
from langchain.tools.sql_database.prompt import QUERY_CHECKER from langchain.tools.sql_database.prompt import QUERY_CHECKER
class JsonAgent(AgentExecutor): class CustomAgentExecutor(AgentExecutor, ABC):
"""Custom agent executor"""
@staticmethod
def function_name():
return "CustomAgentExecutor"
@classmethod
def initialize(cls, *args, **kwargs):
pass
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def run(self, *args, **kwargs):
return super().run(*args, **kwargs)
class JsonAgent(CustomAgentExecutor):
"""Json agent""" """Json agent"""
@staticmethod @staticmethod
@ -70,7 +88,7 @@ class JsonAgent(AgentExecutor):
return super().run(*args, **kwargs) return super().run(*args, **kwargs)
class CSVAgent(AgentExecutor): class CSVAgent(CustomAgentExecutor):
"""CSV agent""" """CSV agent"""
@staticmethod @staticmethod
@ -118,7 +136,7 @@ class CSVAgent(AgentExecutor):
return super().run(*args, **kwargs) return super().run(*args, **kwargs)
class VectorStoreAgent(AgentExecutor): class VectorStoreAgent(CustomAgentExecutor):
"""Vector Store agent""" """Vector Store agent"""
@staticmethod @staticmethod
@ -156,7 +174,7 @@ class VectorStoreAgent(AgentExecutor):
return super().run(*args, **kwargs) return super().run(*args, **kwargs)
class SQLAgent(AgentExecutor): class SQLAgent(CustomAgentExecutor):
"""SQL agent""" """SQL agent"""
@staticmethod @staticmethod
@ -228,7 +246,7 @@ class SQLAgent(AgentExecutor):
return super().run(*args, **kwargs) return super().run(*args, **kwargs)
class VectorStoreRouterAgent(AgentExecutor): class VectorStoreRouterAgent(CustomAgentExecutor):
"""Vector Store Router Agent""" """Vector Store Router Agent"""
@staticmethod @staticmethod
@ -267,7 +285,7 @@ class VectorStoreRouterAgent(AgentExecutor):
return super().run(*args, **kwargs) return super().run(*args, **kwargs)
class InitializeAgent(AgentExecutor): class InitializeAgent(CustomAgentExecutor):
"""Implementation of initialize_agent function""" """Implementation of initialize_agent function"""
@staticmethod @staticmethod

View file

@ -5,9 +5,9 @@ from typing import Any, Type
from langchain import PromptTemplate from langchain import PromptTemplate
from langchain.agents import Agent from langchain.agents import Agent
from langchain.base_language import BaseLanguageModel
from langchain.chains.base import Chain from langchain.chains.base import Chain
from langchain.chat_models.base import BaseChatModel from langchain.chat_models.base import BaseChatModel
from langchain.base_language import BaseLanguageModel
from langchain.tools import BaseTool from langchain.tools import BaseTool

View file

@ -13,9 +13,9 @@ from langchain.agents.load_tools import (
) )
from langchain.agents.loading import load_agent_from_config from langchain.agents.loading import load_agent_from_config
from langchain.agents.tools import Tool from langchain.agents.tools import Tool
from langchain.base_language import BaseLanguageModel
from langchain.callbacks.base import BaseCallbackManager from langchain.callbacks.base import BaseCallbackManager
from langchain.chains.loading import load_chain_from_config from langchain.chains.loading import load_chain_from_config
from langchain.base_language import BaseLanguageModel
from langchain.llms.loading import load_llm_from_config from langchain.llms.loading import load_llm_from_config
from pydantic import ValidationError from pydantic import ValidationError
@ -30,88 +30,19 @@ from langflow.utils import util, validate
def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any: def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
"""Instantiate class from module type and key, and params""" """Instantiate class from module type and key, and params"""
params = convert_params_to_sets(params)
if node_type in CUSTOM_AGENTS: if node_type in CUSTOM_AGENTS:
if custom_agent := CUSTOM_AGENTS.get(node_type): custom_agent = CUSTOM_AGENTS.get(node_type)
return custom_agent.initialize(**params) # type: ignore if custom_agent:
params = process_params(params) return custom_agent.initialize(**params)
class_object = import_by_type(_type=base_type, name=node_type) class_object = import_by_type(_type=base_type, name=node_type)
# check if it is a class before using issubclass return instantiate_based_on_type(class_object, base_type, node_type, params)
# if isinstance(class_object, type) and issubclass(class_object, BaseModel):
# # validate params
# fields = class_object.__fields__
# params = {key: value for key, value in params.items() if key in fields}
if base_type == "agents":
# We need to initialize it differently
return load_agent_executor(class_object, params)
elif base_type == "prompts":
if node_type == "ZeroShotPrompt":
if "tools" not in params:
params["tools"] = []
return ZeroShotAgent.create_prompt(**params)
elif base_type == "tools":
if node_type == "JsonSpec":
params["dict_"] = load_file_into_dict(params.pop("path"))
return class_object(**params)
elif node_type == "PythonFunction":
# If the node_type is "PythonFunction"
# we need to get the function from the params
# which will be a str containing a python function
# and then we need to compile it and return the function
# as the instance
function_string = params["code"]
if isinstance(function_string, str):
return validate.eval_function(function_string)
raise ValueError("Function should be a string")
elif node_type.lower() == "tool":
return class_object(**params)
elif base_type == "toolkits":
loaded_toolkit = class_object(**params)
# Check if node_type has a loader
if toolkits_creator.has_create_function(node_type):
return load_toolkits_executor(node_type, loaded_toolkit, params)
return loaded_toolkit
elif base_type == "embeddings":
# ? Why remove model from params?
try:
params.pop("model")
except KeyError:
pass
# remove all params that are not in class_object.__fields__
try:
return class_object(**params)
except ValidationError:
params = {
key: value
for key, value in params.items()
if key in class_object.__fields__
}
return class_object(**params)
elif base_type == "vectorstores":
if len(params.get("documents", [])) == 0:
# Error when the pdf or other source was not correctly
# loaded.
raise ValueError(
"The source you provided did not load correctly or was empty."
"This may cause an error in the vectorstore."
)
return class_object.from_documents(**params)
elif base_type == "documentloaders":
return class_object(**params).load()
elif base_type == "textsplitters":
documents = params.pop("documents")
text_splitter = class_object(**params)
return text_splitter.split_documents(documents)
elif base_type == "utilities":
if node_type == "SQLDatabase":
return class_object.from_uri(params.pop("uri"))
return class_object(**params)
def process_params(params): def convert_params_to_sets(params):
"""Process params""" """Convert certain params to sets"""
if "allowed_special" in params: if "allowed_special" in params:
params["allowed_special"] = set(params["allowed_special"]) params["allowed_special"] = set(params["allowed_special"])
if "disallowed_special" in params: if "disallowed_special" in params:
@ -119,6 +50,100 @@ def process_params(params):
return params return params
def instantiate_based_on_type(class_object, base_type, node_type, params):
if base_type == "agents":
return instantiate_agent(class_object, params)
elif base_type == "prompts":
return instantiate_prompt(class_object, node_type, params)
elif base_type == "tools":
return instantiate_tool(node_type, class_object, params)
elif base_type == "toolkits":
return instantiate_toolkit(node_type, class_object, params)
elif base_type == "embeddings":
return instantiate_embedding(class_object, params)
elif base_type == "vectorstores":
return instantiate_vectorstore(class_object, params)
elif base_type == "documentloaders":
return instantiate_documentloader(class_object, params)
elif base_type == "textsplitters":
return instantiate_textsplitter(class_object, params)
elif base_type == "utilities":
return instantiate_utility(node_type, class_object, params)
else:
return class_object(**params)
def instantiate_agent(class_object, params):
return load_agent_executor(class_object, params)
def instantiate_prompt(class_object, node_type, params):
if node_type == "ZeroShotPrompt":
if "tools" not in params:
params["tools"] = []
return ZeroShotAgent.create_prompt(**params)
return class_object(**params)
def instantiate_tool(node_type, class_object, params):
if node_type == "JsonSpec":
params["dict_"] = load_file_into_dict(params.pop("path"))
return class_object(**params)
elif node_type == "PythonFunction":
function_string = params["code"]
if isinstance(function_string, str):
return validate.eval_function(function_string)
raise ValueError("Function should be a string")
elif node_type.lower() == "tool":
return class_object(**params)
return None # Or some other default action
def instantiate_toolkit(node_type, class_object, params):
loaded_toolkit = class_object(**params)
if toolkits_creator.has_create_function(node_type):
return load_toolkits_executor(node_type, loaded_toolkit, params)
return loaded_toolkit
def instantiate_embedding(class_object, params):
params.pop("model", None)
try:
return class_object(**params)
except ValidationError:
params = {
key: value
for key, value in params.items()
if key in class_object.__fields__
}
return class_object(**params)
def instantiate_vectorstore(class_object, params):
if len(params.get("documents", [])) == 0:
raise ValueError(
"The source you provided did not load correctly or was empty."
"This may cause an error in the vectorstore."
)
return class_object.from_documents(**params)
def instantiate_documentloader(class_object, params):
return class_object(**params).load()
def instantiate_textsplitter(class_object, params):
documents = params.pop("documents")
text_splitter = class_object(**params)
return text_splitter.split_documents(documents)
def instantiate_utility(node_type, class_object, params):
if node_type == "SQLDatabase":
return class_object.from_uri(params.pop("uri"))
return class_object(**params)
def load_flow_from_json(path: str, build=True): def load_flow_from_json(path: str, build=True):
# This is done to avoid circular imports # This is done to avoid circular imports
from langflow.graph import Graph from langflow.graph import Graph

View file

@ -1,13 +1,14 @@
import contextlib import contextlib
import io import io
from typing import Any, Dict from typing import Any, Dict, List, Tuple
from chromadb.errors import NotEnoughElementsException # type: ignore from chromadb.errors import NotEnoughElementsException # type: ignore
from langflow.api.callback import AsyncStreamingLLMCallbackHandler, StreamingLLMCallbackHandler # type: ignore
from langflow.cache.base import compute_dict_hash, load_cache, memoize_dict from langflow.cache.base import compute_dict_hash, load_cache, memoize_dict
from langflow.graph.graph import Graph from langflow.graph.graph import Graph
from langflow.interface import loading
from langflow.utils.logger import logger from langflow.utils.logger import logger
from langchain.schema import AgentAction
def load_langchain_object(data_graph, is_first_message=False): def load_langchain_object(data_graph, is_first_message=False):
@ -66,40 +67,6 @@ def build_langchain_object(data_graph):
return graph.build() return graph.build()
def process_graph(data_graph: Dict[str, Any]):
"""
Process graph by extracting input variables and replacing ZeroShotPrompt
with PromptTemplate,then run the graph and return the result and thought.
"""
# Load langchain object
logger.debug("Loading langchain object")
message = data_graph.pop("message", "")
is_first_message = len(data_graph.get("chatHistory", [])) == 0
computed_hash, langchain_object = load_langchain_object(
data_graph, is_first_message
)
logger.debug("Loaded langchain object")
if langchain_object is None:
# Raise user facing error
raise ValueError(
"There was an error loading the langchain_object. Please, check all the nodes and try again."
)
# Generate result and thought
logger.debug("Generating result and thought")
result, thought = get_result_and_steps(langchain_object, message)
logger.debug("Generated result and thought")
# Save langchain_object to cache
# We have to save it here because if the
# memory is updated we need to keep the new values
logger.debug("Saving langchain object to cache")
# save_cache(computed_hash, langchain_object, is_first_message)
logger.debug("Saved langchain object to cache")
return {"result": str(result), "thought": thought.strip()}
def process_graph_cached(data_graph: Dict[str, Any], message: str): def process_graph_cached(data_graph: Dict[str, Any], message: str):
""" """
Process graph by extracting input variables and replacing ZeroShotPrompt Process graph by extracting input variables and replacing ZeroShotPrompt
@ -184,8 +151,9 @@ def fix_memory_inputs(langchain_object):
update_memory_keys(langchain_object, possible_new_mem_key) update_memory_keys(langchain_object, possible_new_mem_key)
def get_result_and_steps(langchain_object, message: str): async def get_result_and_steps(langchain_object, message: str, **kwargs):
"""Get result and thought from extracted json""" """Get result and thought from extracted json"""
try: try:
if hasattr(langchain_object, "verbose"): if hasattr(langchain_object, "verbose"):
langchain_object.verbose = True langchain_object.verbose = True
@ -205,32 +173,28 @@ def get_result_and_steps(langchain_object, message: str):
# https://github.com/hwchase17/langchain/issues/2068 # https://github.com/hwchase17/langchain/issues/2068
# Deactivating until we have a frontend solution # Deactivating until we have a frontend solution
# to display intermediate steps # to display intermediate steps
langchain_object.return_intermediate_steps = False langchain_object.return_intermediate_steps = True
fix_memory_inputs(langchain_object) fix_memory_inputs(langchain_object)
try:
async_callbacks = [AsyncStreamingLLMCallbackHandler(**kwargs)]
output = await langchain_object.acall(chat_input, callbacks=async_callbacks)
except Exception as exc:
# make the error message more informative
logger.debug(f"Error: {str(exc)}")
sync_callbacks = [StreamingLLMCallbackHandler(**kwargs)]
output = langchain_object(chat_input, callbacks=sync_callbacks)
with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer): intermediate_steps = (
try: output.get("intermediate_steps", []) if isinstance(output, dict) else []
output = langchain_object(chat_input) )
except ValueError as exc:
# make the error message more informative
logger.debug(f"Error: {str(exc)}")
output = langchain_object.run(chat_input)
intermediate_steps = (
output.get("intermediate_steps", []) if isinstance(output, dict) else []
)
result = (
output.get(langchain_object.output_keys[0])
if isinstance(output, dict)
else output
)
if intermediate_steps:
thought = format_intermediate_steps(intermediate_steps)
else:
thought = output_buffer.getvalue()
result = (
output.get(langchain_object.output_keys[0])
if isinstance(output, dict)
else output
)
thought = format_actions(intermediate_steps) if intermediate_steps else ""
except NotEnoughElementsException as exc: except NotEnoughElementsException as exc:
raise ValueError( raise ValueError(
"Error: Not enough documents for ChromaDB to index. Try reducing chunk size in TextSplitter." "Error: Not enough documents for ChromaDB to index. Try reducing chunk size in TextSplitter."
@ -286,7 +250,7 @@ def get_result_and_thought(langchain_object, message: str):
else output else output
) )
if intermediate_steps: if intermediate_steps:
thought = format_intermediate_steps(intermediate_steps) thought = format_actions(intermediate_steps)
else: else:
thought = output_buffer.getvalue() thought = output_buffer.getvalue()
@ -295,19 +259,17 @@ def get_result_and_thought(langchain_object, message: str):
return result, thought return result, thought
def format_intermediate_steps(intermediate_steps): def format_actions(actions: List[Tuple[AgentAction, str]]) -> str:
formatted_chain = "> Entering new AgentExecutor chain...\n" """Format a list of (AgentAction, answer) tuples into a string."""
for step in intermediate_steps: output = []
action = step[0] for action, answer in actions:
observation = step[1] log = action.log
tool = action.tool
formatted_chain += ( tool_input = action.tool_input
f" {action.log}\nAction: {action.tool}\nAction Input: {action.tool_input}\n" output.append(f"Log: {log}")
) if "Action" not in log and "Action Input" not in log:
formatted_chain += f"Observation: {observation}\n" output.append(f"Tool: {tool}")
output.append(f"Tool Input: {tool_input}")
final_answer = f"Final Answer: {observation}\n" output.append(f"Answer: {answer}")
formatted_chain += f"Thought: I now know the final answer\n{final_answer}\n" output.append("") # Add a blank line
formatted_chain += "> Finished chain.\n" return "\n".join(output)
return formatted_chain

View file

@ -4,13 +4,9 @@ import os
from io import BytesIO from io import BytesIO
import yaml import yaml
from langchain.callbacks.manager import AsyncCallbackManager from langchain.base_language import BaseLanguageModel
from langchain.chat_models import AzureChatOpenAI, ChatOpenAI
from langchain.llms import AzureOpenAI, OpenAI
from PIL.Image import Image from PIL.Image import Image
from langflow.api.callback import StreamingLLMCallbackHandler
def load_file_into_dict(file_path: str) -> dict: def load_file_into_dict(file_path: str) -> dict:
if not os.path.exists(file_path): if not os.path.exists(file_path):
@ -48,10 +44,7 @@ def try_setting_streaming_options(langchain_object, websocket):
langchain_object.llm_chain, "llm" langchain_object.llm_chain, "llm"
): ):
llm = langchain_object.llm_chain.llm llm = langchain_object.llm_chain.llm
if isinstance(llm, (OpenAI, ChatOpenAI, AzureOpenAI, AzureChatOpenAI)): if isinstance(llm, BaseLanguageModel):
llm.streaming = bool(hasattr(llm, "streaming")) llm.streaming = bool(hasattr(llm, "streaming"))
stream_handler = StreamingLLMCallbackHandler(websocket)
stream_manager = AsyncCallbackManager([stream_handler])
llm.callback_manager = stream_manager
return langchain_object return langchain_object

View file

@ -123,6 +123,13 @@ class MidJourneyPromptChainNode(FrontendNode):
multiline=False, multiline=False,
name="llm", name="llm",
), ),
TemplateField(
field_type="BaseChatMemory",
required=False,
show=True,
name="memory",
advanced=False,
),
], ],
) )
description: str = "MidJourneyPromptChain is a chain you can use to generate new MidJourney prompts." description: str = "MidJourneyPromptChain is a chain you can use to generate new MidJourney prompts."

View file

@ -4,12 +4,12 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<title>LangFlow</title> <title>LangFlow</title>
</head> </head>
<body id='body' style="width: 100%; height:100%"> <body id='body' style="width: 100%; height:100%">
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<div style="width: 100vw; height:100vh" id='root'> <div style="width: 100vw; height:100vh" id='root'></div>
</div> <script type="module" src="/src/index.tsx"></script>
</body> </body>
</html> </html>

File diff suppressed because it is too large Load diff

View file

@ -8,15 +8,9 @@
"@headlessui/react": "^1.7.10", "@headlessui/react": "^1.7.10",
"@heroicons/react": "^2.0.15", "@heroicons/react": "^2.0.15",
"@mui/material": "^5.11.9", "@mui/material": "^5.11.9",
"@tabler/icons-react": "^2.18.0",
"@tailwindcss/forms": "^0.5.3", "@tailwindcss/forms": "^0.5.3",
"@tailwindcss/line-clamp": "^0.4.4", "@tailwindcss/line-clamp": "^0.4.4",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.12",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"ace-builds": "^1.16.0", "ace-builds": "^1.16.0",
"ansi-to-html": "^0.7.2", "ansi-to-html": "^0.7.2",
"axios": "^1.3.2", "axios": "^1.3.2",
@ -29,19 +23,21 @@
"react-error-boundary": "^4.0.2", "react-error-boundary": "^4.0.2",
"react-icons": "^4.8.0", "react-icons": "^4.8.0",
"react-laag": "^2.0.5", "react-laag": "^2.0.5",
"react-markdown": "^8.0.7",
"react-router-dom": "^6.8.1", "react-router-dom": "^6.8.1",
"react-scripts": "5.0.1", "react-syntax-highlighter": "^15.5.0",
"react-tabs": "^6.0.0", "react-tabs": "^6.0.0",
"reactflow": "^11.5.5", "reactflow": "^11.5.5",
"tailwindcss": "^3.2.6", "rehype-mathjax": "^4.0.2",
"typescript": "^4.9.5", "remark-gfm": "^3.0.1",
"remark-math": "^5.1.1",
"uuid": "^9.0.0",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "vite",
"build": "react-scripts build", "build": "vite build",
"test": "react-scripts test", "serve": "vite preview"
"eject": "react-scripts eject"
}, },
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [
@ -61,5 +57,23 @@
"last 1 safari version" "last 1 safari version"
] ]
}, },
"proxy": "http://127.0.0.1:7860" "proxy": "http://127.0.0.1:7860",
} "devDependencies": {
"@tailwindcss/typography": "^0.5.9",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/lodash": "^4.14.194",
"@types/node": "^16.18.12",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/uuid": "^9.0.1",
"@vitejs/plugin-react-swc": "^3.0.0",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.23",
"tailwindcss": "^3.3.2",
"typescript": "^5.0.2",
"vite": "^4.3.5"
}
}

View file

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View file

@ -3,45 +3,45 @@
@tailwind utilities; @tailwind utilities;
.App { .App {
text-align: center; text-align: center;
} }
.App-logo { .App-logo {
height: 40vmin; height: 40vmin;
pointer-events: none; pointer-events: none;
} }
@media (prefers-reduced-motion: no-preference) { @media (prefers-reduced-motion: no-preference) {
.App-logo { .App-logo {
animation: App-logo-spin infinite 20s linear; animation: App-logo-spin infinite 20s linear;
} }
} }
.App-header { .App-header {
background-color: #282c34; background-color: #282c34;
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: calc(10px + 2vmin); font-size: calc(10px + 2vmin);
color: white; color: white;
} }
.App-link { .App-link {
color: #61dafb; color: #61dafb;
} }
@keyframes App-logo-spin { @keyframes App-logo-spin {
from { from {
transform: rotate(0deg); transform: rotate(0deg);
} }
to { to {
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
@font-face{ @font-face {
font-family: text-security-disc; font-family: text-security-disc;
src: url("assets/text-security-disc.woff") format("woff"); src: url("assets/text-security-disc.woff") format("woff");
} }

View file

@ -2,6 +2,8 @@ import "reactflow/dist/style.css";
import { useState, useEffect, useContext } from "react"; import { useState, useEffect, useContext } from "react";
import "./App.css"; import "./App.css";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import _ from "lodash";
import ErrorAlert from "./alerts/error"; import ErrorAlert from "./alerts/error";
import NoticeAlert from "./alerts/notice"; import NoticeAlert from "./alerts/notice";
import SuccessAlert from "./alerts/success"; import SuccessAlert from "./alerts/success";
@ -14,7 +16,6 @@ import CrashErrorComponent from "./components/CrashErrorComponent";
import { TabsContext } from "./contexts/tabsContext"; import { TabsContext } from "./contexts/tabsContext";
export default function App() { export default function App() {
var _ = require("lodash");
let { setCurrent, setShowSideBar, setIsStackedOpen } = let { setCurrent, setShowSideBar, setIsStackedOpen } =
useContext(locationContext); useContext(locationContext);

View file

@ -198,7 +198,9 @@ export default function ParameterComponent({
save(); save();
}} }}
/> />
):(<></>)} ) : (
<></>
)}
</> </>
</div> </div>
); );

View file

@ -1,13 +1,8 @@
import { Cog6ToothIcon, TrashIcon } from "@heroicons/react/24/outline"; import { BugAntIcon, Cog6ToothIcon, ExclamationCircleIcon, InformationCircleIcon, TrashIcon } from "@heroicons/react/24/outline";
import { import { classNames, nodeColors, nodeIcons, toNormalCase } from "../../utils";
classNames,
nodeColors,
nodeIcons,
toNormalCase,
} from "../../utils";
import ParameterComponent from "./components/parameterComponent"; import ParameterComponent from "./components/parameterComponent";
import { typesContext } from "../../contexts/typesContext"; import { typesContext } from "../../contexts/typesContext";
import { useContext, useState, useEffect, useRef } from "react"; import { useContext, useState, useEffect, useRef, Fragment } from "react";
import { NodeDataType } from "../../types/flow"; import { NodeDataType } from "../../types/flow";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
import { PopUpContext } from "../../contexts/popUpContext"; import { PopUpContext } from "../../contexts/popUpContext";
@ -15,47 +10,52 @@ import NodeModal from "../../modals/NodeModal";
import { useCallback } from "react"; import { useCallback } from "react";
import { TabsContext } from "../../contexts/tabsContext"; import { TabsContext } from "../../contexts/tabsContext";
import { debounce } from "../../utils"; import { debounce } from "../../utils";
import Tooltip from "../../components/TooltipComponent";
export default function GenericNode({ export default function GenericNode({
data, data,
selected, selected,
}: { }: {
data: NodeDataType; data: NodeDataType;
selected: boolean; selected: boolean;
}) { }) {
const { setErrorData } = useContext(alertContext); const { setErrorData } = useContext(alertContext);
const showError = useRef(true); const showError = useRef(true);
const { types, deleteNode } = useContext(typesContext); const { types, deleteNode } = useContext(typesContext);
const { openPopUp } = useContext(PopUpContext); const { openPopUp } = useContext(PopUpContext);
const Icon = nodeIcons[types[data.type]]; const Icon = nodeIcons[types[data.type]];
const [validationStatus, setValidationStatus] = useState("idle"); const [validationStatus, setValidationStatus] = useState(null);
// State for outline color // State for outline color
const [isValid, setIsValid] = useState(false); const [isValid, setIsValid] = useState(false);
const { save } = useContext(TabsContext); const { save } = useContext(TabsContext);
const { reactFlowInstance } = useContext(typesContext); const { reactFlowInstance } = useContext(typesContext);
const [params, setParams] = useState([]); const [params, setParams] = useState([]);
useEffect(() => {
if (reactFlowInstance) {
setParams(Object.values(reactFlowInstance.toObject()));
}
}, [save]);
useEffect(() => { const validateNode = useCallback(
if (reactFlowInstance) { debounce(async () => {
setParams(Object.values(reactFlowInstance.toObject())); try {
} const response = await fetch(`/validate/node/${data.id}`, {
}, [save]); method: "POST",
headers: {
const validateNode = useCallback( "Content-Type": "application/json",
debounce(async () => { },
try { body: JSON.stringify(reactFlowInstance.toObject()),
const response = await fetch(`/validate/node/${data.id}`, { });
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(reactFlowInstance.toObject()),
});
if (response.status === 200) { if (response.status === 200) {
setValidationStatus("success"); let jsonResponse = await response.json();
} else if (response.status === 500) { let jsonResponseParsed = await JSON.parse(jsonResponse);
setValidationStatus("error"); console.log(jsonResponseParsed);
if(jsonResponseParsed.valid){
setValidationStatus(jsonResponseParsed.params);
} else {
setValidationStatus("error");
}
} }
} catch (error) { } catch (error) {
// console.error("Error validating node:", error); // console.error("Error validating node:", error);
@ -71,25 +71,25 @@ export default function GenericNode({
}, [params, validateNode]); }, [params, validateNode]);
useEffect(() => { useEffect(() => {
if (validationStatus === "success") { if (validationStatus !== "error") {
setIsValid(true); setIsValid(true);
} else { } else {
setIsValid(false); setIsValid(false);
} }
}, [validationStatus]); }, [validationStatus]);
if (!Icon) { if (!Icon) {
if (showError.current) { if (showError.current) {
setErrorData({ setErrorData({
title: data.type title: data.type
? `The ${data.type} node could not be rendered, please review your json file` ? `The ${data.type} node could not be rendered, please review your json file`
: "There was a node that can't be rendered, please review your json file", : "There was a node that can't be rendered, please review your json file",
}); });
showError.current = false; showError.current = false;
} }
deleteNode(data.id); deleteNode(data.id);
return; return;
} }
return ( return (
<div <div
@ -100,14 +100,23 @@ export default function GenericNode({
)} )}
> >
<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 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"> <div className="w-full flex items-center truncate gap-2 text-lg">
<Icon <Icon
className="w-10 h-10 p-1 rounded" className="w-10 h-10 p-1 rounded"
style={{ style={{
color: nodeColors[types[data.type]] ?? nodeColors.unknown, color: nodeColors[types[data.type]] ?? nodeColors.unknown,
}} }}
/> />
<div className="truncate">{data.type}</div> <div className="ml-2 truncate">{data.type}</div>
{validationStatus && validationStatus !== "error" ?
<Tooltip title={
<div className="max-h-96 overflow-auto">
{validationStatus}
</div>}>
<ExclamationCircleIcon className="w-5 hover:text-gray-500 hover:dark:text-gray-300" />
</Tooltip>
: <></>
}
</div> </div>
<div className="flex gap-3"> <div className="flex gap-3">
<button <button
@ -134,7 +143,7 @@ export default function GenericNode({
) )
? "" ? ""
: "hidden", : "hidden",
"w-6 h-6 dark:text-gray-500 hover:animate-spin" "w-6 h-6 dark:text-gray-300 hover:animate-spin"
)} )}
></Cog6ToothIcon> ></Cog6ToothIcon>
</button> </button>
@ -143,22 +152,22 @@ export default function GenericNode({
deleteNode(data.id); deleteNode(data.id);
}} }}
> >
<TrashIcon className="w-6 h-6 hover:text-red-500 dark:text-gray-500 dark:hover:text-red-500"></TrashIcon> <TrashIcon className="w-6 h-6 hover:text-red-500 dark:text-gray-300 dark:hover:text-red-500"></TrashIcon>
</button> </button>
</div> </div>
</div> </div>
<div className="w-full h-full py-5"> <div className="w-full h-full py-5">
<div className="w-full text-gray-500 px-5 pb-3 text-sm"> <div className="w-full text-gray-500 dark:text-gray-300 px-5 pb-3 text-sm">
{data.node.description} {data.node.description}
</div> </div>
<> <>
{Object.keys(data.node.template) {Object.keys(data.node.template)
.filter((t) => t.charAt(0) !== "_") .filter((t) => t.charAt(0) !== "_")
.map((t: string, idx) => ( .map((t: string, idx) => (
<div key={idx}> <div key={idx}>
{/* {idx === 0 ? ( {/* {idx === 0 ? (
<div <div
className={classNames( className={classNames(
"px-5 py-2 mt-2 dark:text-white text-center", "px-5 py-2 mt-2 dark:text-white text-center",
@ -177,59 +186,59 @@ export default function GenericNode({
) : ( ) : (
<></> <></>
)} */} )} */}
{data.node.template[t].show && {data.node.template[t].show &&
!data.node.template[t].advanced ? ( !data.node.template[t].advanced ? (
<ParameterComponent <ParameterComponent
data={data} data={data}
color={ color={
nodeColors[types[data.node.template[t].type]] ?? nodeColors[types[data.node.template[t].type]] ??
nodeColors.unknown nodeColors.unknown
} }
title={ title={
data.node.template[t].display_name data.node.template[t].display_name
? data.node.template[t].display_name ? data.node.template[t].display_name
: data.node.template[t].name : data.node.template[t].name
? toNormalCase(data.node.template[t].name) ? toNormalCase(data.node.template[t].name)
: toNormalCase(t) : toNormalCase(t)
} }
name={t} name={t}
tooltipTitle={ tooltipTitle={
"Type: " + "Type: " +
data.node.template[t].type + data.node.template[t].type +
(data.node.template[t].list ? " list" : "") (data.node.template[t].list ? " list" : "")
} }
required={data.node.template[t].required} required={data.node.template[t].required}
id={data.node.template[t].type + "|" + t + "|" + data.id} id={data.node.template[t].type + "|" + t + "|" + data.id}
left={true} left={true}
type={data.node.template[t].type} type={data.node.template[t].type}
/> />
) : ( ) : (
<></> <></>
)} )}
</div> </div>
))} ))}
<div <div
className={classNames( className={classNames(
Object.keys(data.node.template).length < 1 ? "hidden" : "", Object.keys(data.node.template).length < 1 ? "hidden" : "",
"w-full flex justify-center" "w-full flex justify-center"
)} )}
> >
{" "} {" "}
</div> </div>
{/* <div className="px-5 py-2 mt-2 dark:text-white text-center"> {/* <div className="px-5 py-2 mt-2 dark:text-white text-center">
Output Output
</div> */} </div> */}
<ParameterComponent <ParameterComponent
data={data} data={data}
color={nodeColors[types[data.type]] ?? nodeColors.unknown} color={nodeColors[types[data.type]] ?? nodeColors.unknown}
title={data.type} title={data.type}
tooltipTitle={`Type: ${data.node.base_classes.join(" | ")}`} tooltipTitle={`Type: ${data.node.base_classes.join(" | ")}`}
id={[data.type, data.id, ...data.node.base_classes].join("|")} id={[data.type, data.id, ...data.node.base_classes].join("|")}
type={data.node.base_classes.join("|")} type={data.node.base_classes.join("|")}
left={false} left={false}
/> />
</> </>
</div> </div>
</div> </div>
); );
} }

View file

@ -1,8 +1,8 @@
import { import {
XCircleIcon, XCircleIcon,
XMarkIcon, XMarkIcon,
InformationCircleIcon, InformationCircleIcon,
CheckCircleIcon, CheckCircleIcon,
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { Transition } from "@headlessui/react"; import { Transition } from "@headlessui/react";
@ -10,145 +10,145 @@ import { useState } from "react";
import { SingleAlertComponentType } from "../../../../types/alerts"; import { SingleAlertComponentType } from "../../../../types/alerts";
export default function SingleAlert({ export default function SingleAlert({
dropItem, dropItem,
removeAlert, removeAlert,
}: SingleAlertComponentType) { }: SingleAlertComponentType) {
const [show, setShow] = useState(true); const [show, setShow] = useState(true);
const type = dropItem.type; const type = dropItem.type;
return ( return (
<Transition <Transition
className="relative" className="relative"
show={show} show={show}
appear={true} appear={true}
enter="transition-transform duration-500 ease-out" enter="transition-transform duration-500 ease-out"
enterFrom={"transform translate-x-[-100%]"} enterFrom={"transform translate-x-[-100%]"}
enterTo={"transform translate-x-0"} enterTo={"transform translate-x-0"}
leave="transition-transform duration-500 ease-in" leave="transition-transform duration-500 ease-in"
leaveFrom={"transform translate-x-0"} leaveFrom={"transform translate-x-0"}
leaveTo={"transform translate-x-[-100%]"} leaveTo={"transform translate-x-[-100%]"}
> >
{type === "error" ? ( {type === "error" ? (
<div <div
className="flex bg-red-50 rounded-md p-3 mb-2 mx-2" className="flex bg-red-50 dark:bg-red-900 rounded-md p-3 mb-2 mx-2"
key={dropItem.id} key={dropItem.id}
> >
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<XCircleIcon className="h-5 w-5 text-red-400" aria-hidden="true" /> <XCircleIcon className="h-5 w-5 text-red-400 dark:text-red-50" aria-hidden="true" />
</div> </div>
<div className="ml-3"> <div className="ml-3">
<h3 className="text-sm font-medium text-red-800"> <h3 className="text-sm break-words font-medium text-red-800 dark:text-white/80">
{dropItem.title} {dropItem.title}
</h3> </h3>
{dropItem.list ? ( {dropItem.list ? (
<div className="mt-2 text-sm text-red-700"> <div className="mt-2 text-sm text-red-700 dark:text-red-50">
<ul className="list-disc space-y-1 pl-5"> <ul className="list-disc space-y-1 pl-5">
{dropItem.list.map((item, idx) => ( {dropItem.list.map((item, idx) => (
<li key={idx}>{item}</li> <li className="break-words" key={idx}>{item}</li>
))} ))}
</ul> </ul>
</div> </div>
) : ( ) : (
<></> <></>
)} )}
</div> </div>
<div className="ml-auto pl-3"> <div className="ml-auto pl-3">
<div className="-mx-1.5 -my-1.5"> <div className="-mx-1.5 -my-1.5">
<button <button
type="button" type="button"
onClick={() => { onClick={() => {
setShow(false); setShow(false);
setTimeout(() => { setTimeout(() => {
removeAlert(dropItem.id); removeAlert(dropItem.id);
}, 500); }, 500);
}} }}
className="inline-flex rounded-md bg-red-50 p-1.5 text-red-500" className="inline-flex rounded-md bg-red-50 dark:bg-transparent p-1.5 text-red-500 dark:text-red-50"
> >
<span className="sr-only">Dismiss</span> <span className="sr-only">Dismiss</span>
<XMarkIcon className="h-5 w-5" aria-hidden="true" /> <XMarkIcon className="h-5 w-5" aria-hidden="true" />
</button> </button>
</div> </div>
</div> </div>
</div> </div>
) : type === "notice" ? ( ) : type === "notice" ? (
<div <div
className="flex rounded-md bg-blue-50 p-3 mb-2 mx-2" className="flex rounded-md bg-blue-50 dark:bg-blue-900 p-3 mb-2 mx-2"
key={dropItem.id} key={dropItem.id}
> >
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<InformationCircleIcon <InformationCircleIcon
className="h-5 w-5 text-blue-400" className="h-5 w-5 text-blue-400 dark:text-blue-50"
aria-hidden="true" aria-hidden="true"
/> />
</div> </div>
<div className="ml-3 flex-1 md:flex md:justify-between"> <div className="ml-3 flex-1 md:flex md:justify-between">
<p className="text-sm text-blue-700">{dropItem.title}</p> <p className="text-sm text-blue-700 dark:text-white/80">{dropItem.title}</p>
<p className="mt-3 text-sm md:mt-0 md:ml-6"> <p className="mt-3 text-sm md:mt-0 md:ml-6">
{dropItem.link ? ( {dropItem.link ? (
<Link <Link
to={dropItem.link} to={dropItem.link}
className="whitespace-nowrap font-medium text-blue-700 hover:text-blue-600" className="whitespace-nowrap font-medium text-blue-700 dark:text-blue-50 dark:hover:text-blue-100 hover:text-blue-600"
> >
Details Details
</Link> </Link>
) : ( ) : (
<></> <></>
)} )}
</p> </p>
</div> </div>
<div className="ml-auto pl-3"> <div className="ml-auto pl-3">
<div className="-mx-1.5 -my-1.5"> <div className="-mx-1.5 -my-1.5">
<button <button
type="button" type="button"
onClick={() => { onClick={() => {
setShow(false); setShow(false);
setTimeout(() => { setTimeout(() => {
removeAlert(dropItem.id); removeAlert(dropItem.id);
}, 500); }, 500);
}} }}
className="inline-flex rounded-md bg-blue-50 p-1.5 text-blue-500" className="inline-flex rounded-md bg-blue-50 dark:bg-transparent p-1.5 text-blue-500 dark:text-blue-50"
> >
<span className="sr-only">Dismiss</span> <span className="sr-only">Dismiss</span>
<XMarkIcon className="h-5 w-5" aria-hidden="true" /> <XMarkIcon className="h-5 w-5" aria-hidden="true" />
</button> </button>
</div> </div>
</div> </div>
</div> </div>
) : ( ) : (
<div <div
className="flex bg-green-50 p-3 mb-2 mx-2 rounded-md" className="flex bg-green-50 dark:bg-green-900 p-3 mb-2 mx-2 rounded-md"
key={dropItem.id} key={dropItem.id}
> >
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<CheckCircleIcon <CheckCircleIcon
className="h-5 w-5 text-green-400" className="h-5 w-5 text-green-400 dark:text-green-50"
aria-hidden="true" aria-hidden="true"
/> />
</div> </div>
<div className="ml-3"> <div className="ml-3">
<p className="text-sm font-medium text-green-800"> <p className="text-sm font-medium text-green-800 dark:bg-white/80">
{dropItem.title} {dropItem.title}
</p> </p>
</div> </div>
<div className="ml-auto pl-3"> <div className="ml-auto pl-3">
<div className="-mx-1.5 -my-1.5"> <div className="-mx-1.5 -my-1.5">
<button <button
type="button" type="button"
onClick={() => { onClick={() => {
setShow(false); setShow(false);
setTimeout(() => { setTimeout(() => {
removeAlert(dropItem.id); removeAlert(dropItem.id);
}, 500); }, 500);
}} }}
className="inline-flex rounded-md bg-green-50 p-1.5 text-green-500" className="inline-flex rounded-md bg-green-50 dark:bg-transparent p-1.5 text-green-500 dark:text-green-50"
> >
<span className="sr-only">Dismiss</span> <span className="sr-only">Dismiss</span>
<XMarkIcon className="h-5 w-5" aria-hidden="true" /> <XMarkIcon className="h-5 w-5" aria-hidden="true" />
</button> </button>
</div> </div>
</div> </div>
</div> </div>
)} )}
</Transition> </Transition>
); );
} }

View file

@ -7,60 +7,60 @@ import { AlertDropdownType } from "../../types/alerts";
import { PopUpContext } from "../../contexts/popUpContext"; import { PopUpContext } from "../../contexts/popUpContext";
import { useOnClickOutside } from "../hooks/useOnClickOutside"; import { useOnClickOutside } from "../hooks/useOnClickOutside";
export default function AlertDropdown({}: AlertDropdownType) { export default function AlertDropdown({}: AlertDropdownType) {
const { closePopUp } = useContext(PopUpContext); const { closePopUp } = useContext(PopUpContext);
const componentRef = useRef<HTMLDivElement>(null); const componentRef = useRef<HTMLDivElement>(null);
// Use the custom hook // Use the custom hook
useOnClickOutside(componentRef, () => { useOnClickOutside(componentRef, () => {
closePopUp(); closePopUp();
}); });
const { const {
notificationList, notificationList,
clearNotificationList, clearNotificationList,
removeFromNotificationList, removeFromNotificationList,
} = useContext(alertContext); } = useContext(alertContext);
return ( return (
<div <div
ref={componentRef} ref={componentRef}
className="z-10 py-3 pb-4 rounded-md bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 shadow-lg focus:outline-none overflow-hidden w-[16rem] h-[28rem] flex flex-col" className="z-10 py-3 pb-4 px-2 rounded-md bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 shadow-lg focus:outline-none overflow-hidden w-[400px] h-[500px] flex flex-col"
> >
<div className="flex pl-3 flex-row justify-between text-md font-medium text-gray-800 dark:text-gray-200"> <div className="flex pl-3 flex-row justify-between text-md font-medium text-gray-800 dark:text-gray-200">
Notifications Notifications
<div className="flex gap-2 pr-3 "> <div className="flex gap-3 pr-3 ">
<button <button
className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500" className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500"
onClick={() => { onClick={() => {
closePopUp(); closePopUp();
setTimeout(clearNotificationList, 100); setTimeout(clearNotificationList, 100);
}} }}
> >
<TrashIcon className="w-[1.1rem] h-[1.1rem]" /> <TrashIcon className="w-[1.1rem] h-[1.1rem]" />
</button> </button>
<button <button
className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500" className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500"
onClick={closePopUp} onClick={closePopUp}
> >
<XMarkIcon className="h-5 w-5" /> <XMarkIcon className="h-5 w-5" />
</button> </button>
</div> </div>
</div> </div>
<div className="mt-2 flex flex-col overflow-y-scroll w-full h-full scrollbar-hide text-gray-900 dark:text-gray-300"> <div className="mt-3 flex flex-col overflow-y-scroll w-full h-full scrollbar-hide text-gray-900 dark:text-gray-300">
{notificationList.length !== 0 ? ( {notificationList.length !== 0 ? (
notificationList.map((alertItem, index) => ( notificationList.map((alertItem, index) => (
<SingleAlert <SingleAlert
key={alertItem.id} key={alertItem.id}
dropItem={alertItem} dropItem={alertItem}
removeAlert={removeFromNotificationList} removeAlert={removeFromNotificationList}
/> />
)) ))
) : ( ) : (
<div className="h-full w-full pb-16 text-gray-500 dark:text-gray-500 flex justify-center items-center"> <div className="h-full w-full pb-16 text-gray-500 dark:text-gray-500 flex justify-center items-center">
No new notifications No new notifications
</div> </div>
)} )}
</div> </div>
</div> </div>
); );
} }

View file

@ -39,16 +39,16 @@ export default function ErrorAlert({
removeAlert(id); removeAlert(id);
}, 500); }, 500);
}} }}
className="rounded-md w-96 mt-6 shadow-xl bg-red-50 p-4 cursor-pointer" className="rounded-md w-96 mt-6 shadow-xl bg-red-50 dark:bg-red-900 p-4 cursor-pointer"
> >
<div className="flex"> <div className="flex">
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<XCircleIcon className="h-5 w-5 text-red-400" aria-hidden="true" /> <XCircleIcon className="h-5 w-5 text-red-400 dark:text-red-50" aria-hidden="true" />
</div> </div>
<div className="ml-3"> <div className="ml-3">
<h3 className="text-sm font-medium text-red-800">{title}</h3> <h3 className="text-sm font-medium text-red-800 dark:text-white/80">{title}</h3>
{list.length !== 0 ? ( {list.length !== 0 ? (
<div className="mt-2 text-sm text-red-700"> <div className="mt-2 text-sm text-red-700 dark:text-red-50">
<ul className="list-disc space-y-1 pl-5"> <ul className="list-disc space-y-1 pl-5">
{list.map((item, index) => ( {list.map((item, index) => (
<li key={index}>{item}</li> <li key={index}>{item}</li>

View file

@ -1,33 +1,33 @@
import { useEffect } from "react"; import { useEffect } from "react";
export function useOnClickOutside(ref, handler) { export function useOnClickOutside(ref, handler) {
useEffect(() => { useEffect(() => {
const listener = (event) => { const listener = (event) => {
// Do nothing if clicking ref's element or its children // Do nothing if clicking ref's element or its children
if (!ref.current || ref.current.contains(event.target)) { if (!ref.current || ref.current.contains(event.target)) {
return; return;
} }
handler(event); handler(event);
}; };
// Attach the listener to the document // Attach the listener to the document
document.addEventListener("mousedown", listener, { passive: true }); document.addEventListener("mousedown", listener, { passive: true });
// Attach the listener to the react-flow instance // Attach the listener to the react-flow instance
const reactFlowContainer = document.querySelector(".react-flow"); const reactFlowContainer = document.querySelector(".react-flow");
if (reactFlowContainer) { if (reactFlowContainer) {
reactFlowContainer.addEventListener("mousedown", listener, { reactFlowContainer.addEventListener("mousedown", listener, {
passive: true, passive: true,
}); });
} }
// Clean up the listener when the component is unmounted // Clean up the listener when the component is unmounted
return () => { return () => {
document.removeEventListener("mousedown", listener); document.removeEventListener("mousedown", listener);
if (reactFlowContainer) { if (reactFlowContainer) {
reactFlowContainer.removeEventListener("mousedown", listener); reactFlowContainer.removeEventListener("mousedown", listener);
} }
}; };
}, [ref, handler]); // Rerun only if ref or handler changes }, [ref, handler]); // Rerun only if ref or handler changes
} }

View file

@ -36,22 +36,22 @@ export default function NoticeAlert({
setShow(false); setShow(false);
removeAlert(id); removeAlert(id);
}} }}
className="rounded-md w-96 mt-6 shadow-xl bg-blue-50 p-4" className="rounded-md w-96 mt-6 shadow-xl bg-blue-50 dark:bg-blue-900 p-4"
> >
<div className="flex"> <div className="flex">
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<InformationCircleIcon <InformationCircleIcon
className="h-5 w-5 text-blue-400" className="h-5 w-5 text-blue-400 dark:text-blue-50"
aria-hidden="true" aria-hidden="true"
/> />
</div> </div>
<div className="ml-3 flex-1 md:flex md:justify-between"> <div className="ml-3 flex-1 md:flex md:justify-between">
<p className="text-sm text-blue-700">{title}</p> <p className="text-sm text-blue-700 dark:text-white/80">{title}</p>
<p className="mt-3 text-sm md:mt-0 md:ml-6"> <p className="mt-3 text-sm md:mt-0 md:ml-6">
{link !== "" ? ( {link !== "" ? (
<Link <Link
to={link} to={link}
className="whitespace-nowrap font-medium text-blue-700 hover:text-blue-600" className="whitespace-nowrap font-medium text-blue-700 dark:text-blue-50 hover:dark:text-blue-10 hover:text-blue-600"
> >
Details Details
</Link> </Link>

View file

@ -34,17 +34,17 @@ export default function SuccessAlert({
setShow(false); setShow(false);
removeAlert(id); removeAlert(id);
}} }}
className="rounded-md w-96 mt-6 shadow-xl bg-green-50 p-4" className="rounded-md w-96 mt-6 shadow-xl bg-green-50 dark:bg-green-900 p-4"
> >
<div className="flex"> <div className="flex">
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<CheckCircleIcon <CheckCircleIcon
className="h-5 w-5 text-green-400" className="h-5 w-5 text-green-400 dark:text-green-50"
aria-hidden="true" aria-hidden="true"
/> />
</div> </div>
<div className="ml-3"> <div className="ml-3">
<p className="text-sm font-medium text-green-800">{title}</p> <p className="text-sm font-medium text-green-800 dark:text-white/80">{title}</p>
</div> </div>
</div> </div>
</div> </div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -2,12 +2,14 @@ export default function CrashErrorComponent({ error, resetErrorBoundary }) {
return ( return (
<div className="fixed top-0 left-0 w-full h-full flex items-center justify-center bg-gray-800 bg-opacity-50 z-50"> <div className="fixed top-0 left-0 w-full h-full flex items-center justify-center bg-gray-800 bg-opacity-50 z-50">
<div className="bg-white max-w-4xl h-1/3 min-h-fit rounded-lg shadow-lg p-8 text-start flex flex-col justify-evenly"> <div className="bg-white max-w-4xl h-1/3 min-h-fit rounded-lg shadow-lg p-8 text-start flex flex-col justify-evenly">
<h1 className="text-red-500 text-3xl mb-4">Oops! An unknown error has occurred.</h1> <h1 className="text-red-500 text-3xl mb-4">
Oops! An unknown error has occurred.
</h1>
<p className="text-gray-700 mb-4 text-xl"> <p className="text-gray-700 mb-4 text-xl">
Please click the 'Reset Application' button Please click the 'Reset Application' button to restore the
to restore the application's state. If the error persists, please application's state. If the error persists, please create an issue on
create an issue on our GitHub page. We apologize for any inconvenience our GitHub page. We apologize for any inconvenience this may have
this may have caused. caused.
</p> </p>
<div className="flex justify-center"> <div className="flex justify-center">
<button <button

View file

@ -6,119 +6,119 @@ import { classNames } from "../../utils";
import { locationContext } from "../../contexts/locationContext"; import { locationContext } from "../../contexts/locationContext";
export default function ExtraSidebar() { export default function ExtraSidebar() {
const { const {
current, current,
isStackedOpen, isStackedOpen,
setIsStackedOpen, setIsStackedOpen,
extraNavigation, extraNavigation,
extraComponent, extraComponent,
} = useContext(locationContext); } = useContext(locationContext);
return ( return (
<> <>
<aside <aside
className={` ${ className={` ${
isStackedOpen ? "w-52" : "w-0 " isStackedOpen ? "w-52" : "w-0 "
} flex-shrink-0 flex overflow-hidden flex-col border-r dark:border-r-gray-700 transition-all duration-500`} } 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="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"> <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 "> <span className="text-gray-900 dark:text-white py-[2px] font-medium ">
{extraNavigation.title} {extraNavigation.title}
</span> </span>
</div> </div>
<div className="flex flex-grow flex-col w-full"> <div className="flex flex-grow flex-col w-full">
{extraNavigation.options ? ( {extraNavigation.options ? (
<div className="p-4"> <div className="p-4">
<nav className="flex-1 space-y-1"> <nav className="flex-1 space-y-1">
{extraNavigation.options.map((item) => {extraNavigation.options.map((item) =>
!item.children ? ( !item.children ? (
<div key={item.name}> <div key={item.name}>
<Link <Link
to={item.href} to={item.href}
className={classNames( className={classNames(
item.href.split("/")[2] === current[4] item.href.split("/")[2] === current[4]
? "bg-gray-100 text-gray-900" ? "bg-gray-100 text-gray-900"
: "bg-white text-gray-600 hover:bg-gray-50 hover: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" "group w-full flex items-center pl-2 py-2 text-sm font-medium rounded-md"
)} )}
> >
<item.icon <item.icon
className={classNames( className={classNames(
item.href.split("/")[2] === current[4] item.href.split("/")[2] === current[4]
? "text-gray-500" ? "text-gray-500"
: "text-gray-400 group-hover:text-gray-500", : "text-gray-400 group-hover:text-gray-500",
"mr-3 flex-shrink-0 h-6 w-6" "mr-3 flex-shrink-0 h-6 w-6"
)} )}
/> />
{item.name} {item.name}
</Link> </Link>
</div> </div>
) : ( ) : (
<Disclosure <Disclosure
as="div" as="div"
key={item.name} key={item.name}
className="space-y-1" className="space-y-1"
> >
{({ open }) => ( {({ open }) => (
<> <>
<Disclosure.Button <Disclosure.Button
className={classNames( className={classNames(
item.href.split("/")[2] === current[4] item.href.split("/")[2] === current[4]
? "bg-gray-100 text-gray-900" ? "bg-gray-100 text-gray-900"
: "bg-white text-gray-600 hover:bg-gray-50 hover: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" "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 <item.icon
className="mr-3 h-6 w-6 flex-shrink-0 text-gray-400 group-hover:text-gray-500" className="mr-3 h-6 w-6 flex-shrink-0 text-gray-400 group-hover:text-gray-500"
aria-hidden="true" aria-hidden="true"
/> />
<span className="flex-1">{item.name}</span> <span className="flex-1">{item.name}</span>
<svg <svg
className={classNames( className={classNames(
open open
? "text-gray-400 rotate-90" ? "text-gray-400 rotate-90"
: "text-gray-300", : "text-gray-300",
"ml-3 h-5 w-5 flex-shrink-0 transition-rotate duration-150 ease-in-out group-hover:text-gray-400" "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" viewBox="0 0 20 20"
aria-hidden="true" aria-hidden="true"
> >
<path <path
d="M6 6L14 10L6 14V6Z" d="M6 6L14 10L6 14V6Z"
fill="currentColor" fill="currentColor"
/> />
</svg> </svg>
</Disclosure.Button> </Disclosure.Button>
<Disclosure.Panel className="space-y-1"> <Disclosure.Panel className="space-y-1">
{item.children.map((subItem) => ( {item.children.map((subItem) => (
<Link <Link
key={subItem.name} key={subItem.name}
to={subItem.href} to={subItem.href}
className={classNames( className={classNames(
subItem.href.split("/")[3] === current[5] subItem.href.split("/")[3] === current[5]
? "bg-gray-100 text-gray-900" ? "bg-gray-100 text-gray-900"
: "bg-white text-gray-600 hover:bg-gray-50 hover: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" "group flex w-full items-center rounded-md py-2 pl-11 pr-2 text-sm font-medium"
)} )}
> >
{subItem.name} {subItem.name}
</Link> </Link>
))} ))}
</Disclosure.Panel> </Disclosure.Panel>
</> </>
)} )}
</Disclosure> </Disclosure>
) )
)} )}
</nav> </nav>
</div> </div>
) : ( ) : (
extraComponent extraComponent
)} )}
</div> </div>
</div> </div>
</aside> </aside>
</> </>
); );
} }

View file

@ -1,17 +1,17 @@
import { styled } from '@mui/material/styles'; import { styled } from "@mui/material/styles";
import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip'; import Tooltip, { TooltipProps, tooltipClasses } from "@mui/material/Tooltip";
export const LightTooltip = styled(({ className, ...props }: TooltipProps) => ( export const LightTooltip = styled(({ className, ...props }: TooltipProps) => (
<Tooltip {...props} classes={{ popper: className }} /> <Tooltip {...props} classes={{ popper: className }} />
))(({ theme }) => ({ ))(({ theme }) => ({
[`& .${tooltipClasses.tooltip}`]: { [`& .${tooltipClasses.tooltip}`]: {
backgroundColor: theme.palette.common.white, backgroundColor: theme.palette.common.white,
color: 'rgba(0, 0, 0, 0.87)', color: "rgba(0, 0, 0, 0.87)",
boxShadow: theme.shadows[2], boxShadow: theme.shadows[2],
fontSize: 14, fontSize: 14,
}, },
[`& .${tooltipClasses.arrow}:before`]: { [`& .${tooltipClasses.arrow}:before`]: {
color: theme.palette.common.white, color: theme.palette.common.white,
boxShadow: theme.shadows[1], boxShadow: theme.shadows[1],
}, },
})); }));

View file

@ -2,6 +2,14 @@ import { ReactElement } from "react";
import { LightTooltip } from "../LightTooltipComponent"; import { LightTooltip } from "../LightTooltipComponent";
import { TooltipComponentType } from "../../types/components"; import { TooltipComponentType } from "../../types/components";
export default function Tooltip({ children, title,placement }:TooltipComponentType) { export default function Tooltip({
return <LightTooltip placement={placement} title={title} arrow>{children}</LightTooltip>; children,
title,
placement,
}: TooltipComponentType) {
return (
<LightTooltip placement={placement} title={title} arrow>
{children}
</LightTooltip>
);
} }

View file

@ -1,8 +1,12 @@
import { ChatBubbleLeftEllipsisIcon, ChatBubbleOvalLeftEllipsisIcon, PlusSmallIcon } from "@heroicons/react/24/outline"; import {
ChatBubbleLeftEllipsisIcon,
ChatBubbleOvalLeftEllipsisIcon,
PlusSmallIcon,
} from "@heroicons/react/24/outline";
import { useState } from "react"; import { useState } from "react";
import { ChatMessageType } from "../../../types/chat"; import { ChatMessageType } from "../../../types/chat";
import { nodeColors } from "../../../utils"; import { nodeColors } from "../../../utils";
var Convert = require('ansi-to-html'); import Convert from "ansi-to-html";
var convert = new Convert({newline:true}); var convert = new Convert({newline:true});
export default function ChatMessage({ chat }: { chat: ChatMessageType }) { export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
@ -29,13 +33,16 @@ export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
style={{ backgroundColor: nodeColors["thought"] }} style={{ backgroundColor: nodeColors["thought"] }}
className=" text-start inline-block w-full pb-3 pt-3 px-5 cursor-pointer" className=" text-start inline-block w-full pb-3 pt-3 px-5 cursor-pointer"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: convert.toHtml(chat.thought) __html: convert.toHtml(chat.thought),
}} }}
></div> ></div>
)} )}
{chat.thought && chat.thought !== "" && !hidden && <br></br>} {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"] }}> <div
{chat.message} className="w-full rounded-b-md px-4 pb-3 pt-3 pr-8"
style={{ backgroundColor: nodeColors["chat"] }}
>
{chat.message}
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,37 +1,45 @@
import { Transition } from "@headlessui/react"; import { Transition } from "@headlessui/react";
import { Bars3CenterLeftIcon, ChatBubbleBottomCenterTextIcon } from "@heroicons/react/24/outline"; import {
Bars3CenterLeftIcon,
ChatBubbleBottomCenterTextIcon,
} from "@heroicons/react/24/outline";
import { nodeColors } from "../../../utils"; import { nodeColors } from "../../../utils";
import { PopUpContext } from "../../../contexts/popUpContext"; import { PopUpContext } from "../../../contexts/popUpContext";
import { useContext } from "react"; import { useContext } from "react";
import ChatModal from "../../../modals/chatModal"; import ChatModal from "../../../modals/chatModal";
export default function ChatTrigger({open, setOpen,flow}){ export default function ChatTrigger({ open, setOpen }) {
const {openPopUp} = useContext(PopUpContext) const { openPopUp } = useContext(PopUpContext);
return(<Transition return (
show={!open} <Transition
appear={true} show={!open}
enter="transition ease-out duration-300" appear={true}
enterFrom="translate-y-96" enter="transition ease-out duration-300"
enterTo="translate-y-0" enterFrom="translate-y-96"
leave="transition ease-in duration-300" enterTo="translate-y-0"
leaveFrom="translate-y-0" leave="transition ease-in duration-300"
leaveTo="translate-y-96" leaveFrom="translate-y-0"
> leaveTo="translate-y-96"
<div className="absolute bottom-2 right-3"> >
<div style={{backgroundColor:nodeColors['chat']}} className="border flex justify-center align-center py-1 px-3 w-12 h-12 rounded-full dark:bg-gray-800 dark:border-gray-600 dark:text-white"> <div className="absolute bottom-2 right-3">
<button <div
onClick={() => { style={{ backgroundColor: nodeColors["chat"] }}
setOpen(true); className="border flex justify-center align-center py-1 px-3 w-12 h-12 rounded-full dark:bg-gray-800 dark:border-gray-600 dark:text-white"
}} >
> <button
<div className="flex gap-3 items-center"> onClick={() => {
<ChatBubbleBottomCenterTextIcon setOpen(true);
className="h-6 w-6 mt-1" }}
style={{ color: "white" }} >
/> <div className="flex gap-3 items-center">
</div> <ChatBubbleBottomCenterTextIcon
</button> className="h-6 w-6 mt-1"
</div> style={{ color: "white" }}
</div> />
</Transition>) </div>
} </button>
</div>
</div>
</Transition>
);
}

View file

@ -4,29 +4,29 @@ import { ChatMessageType, ChatType } from "../../types/chat";
import ChatTrigger from "./chatTrigger"; import ChatTrigger from "./chatTrigger";
import ChatModal from "../../modals/chatModal"; import ChatModal from "../../modals/chatModal";
const _ = require("lodash"); import _ from "lodash";
export default function Chat({ flow }: ChatType) { export default function Chat({ flow }: ChatType) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
useEffect(() => { useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => { const handleKeyDown = (event: KeyboardEvent) => {
if ( if (
(event.key === "K" || event.key === "k") && (event.key === "K" || event.key === "k") &&
(event.metaKey || event.ctrlKey) (event.metaKey || event.ctrlKey)
) { ) {
event.preventDefault(); event.preventDefault();
setOpen((oldState) => !oldState); setOpen((oldState) => !oldState);
} }
}; };
document.addEventListener("keydown", handleKeyDown); document.addEventListener("keydown", handleKeyDown);
return () => { return () => {
document.removeEventListener("keydown", handleKeyDown); document.removeEventListener("keydown", handleKeyDown);
}; };
}, []); }, []);
return ( return (
<> <>
<ChatModal key={flow.id} flow={flow} open={open} setOpen={setOpen} /> <ChatModal key={flow.id} flow={flow} open={open} setOpen={setOpen} />
<ChatTrigger open={open} setOpen={setOpen} flow={flow} /> <ChatTrigger open={open} setOpen={setOpen} />
</> </>
); );
} }

View file

@ -5,90 +5,90 @@ import { DropDownComponentType } from "../../types/components";
import { classNames } from "../../utils"; import { classNames } from "../../utils";
export default function Dropdown({ export default function Dropdown({
value, value,
options, options,
onSelect, onSelect,
}: DropDownComponentType) { }: DropDownComponentType) {
let [internalValue, setInternalValue] = useState( let [internalValue, setInternalValue] = useState(
value === "" || !value ? "Choose an option" : value value === "" || !value ? "Choose an option" : value
); );
return ( return (
<> <>
<Listbox <Listbox
value={internalValue} value={internalValue}
onChange={(value) => { onChange={(value) => {
setInternalValue(value); setInternalValue(value);
onSelect(value); onSelect(value);
}} }}
> >
{({ open }) => ( {({ open }) => (
<> <>
<div className="relative mt-1"> <div className="relative mt-1">
<Listbox.Button className="relative w-full cursor-default rounded-md border border-gray-300 bg-white dark:bg-gray-900 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"> <Listbox.Button className="relative w-full cursor-default rounded-md border border-gray-300 bg-white dark:bg-gray-900 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 w-max truncate">{internalValue}</span> <span className="block w-max truncate">{internalValue}</span>
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"> <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronUpDownIcon <ChevronUpDownIcon
className="h-5 w-5 text-gray-400" className="h-5 w-5 text-gray-400"
aria-hidden="true" aria-hidden="true"
/> />
</span> </span>
</Listbox.Button> </Listbox.Button>
<Transition <Transition
show={open} show={open}
as={Fragment} as={Fragment}
leave="transition ease-in duration-100" leave="transition ease-in duration-100"
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full dark:bg-gray-800 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"> <Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full dark:bg-gray-800 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) => ( {options.map((option, id) => (
<Listbox.Option <Listbox.Option
key={id} key={id}
className={({ active }) => className={({ active }) =>
classNames( classNames(
active active
? "text-white bg-indigo-600 dark:bg-indigo-500" ? "text-white bg-indigo-600 dark:bg-indigo-500"
: "text-gray-900", : "text-gray-900",
"relative cursor-default select-none py-2 pl-3 pr-9 dark:text-gray-300 dark:bg-gray-800" "relative cursor-default select-none py-2 pl-3 pr-9 dark:text-gray-300 dark:bg-gray-800"
) )
} }
value={option} value={option}
> >
{({ selected, active }) => ( {({ selected, active }) => (
<> <>
<span <span
className={classNames( className={classNames(
selected ? "font-semibold" : "font-normal", selected ? "font-semibold" : "font-normal",
"block truncate" "block truncate"
)} )}
> >
{option} {option}
</span> </span>
{selected ? ( {selected ? (
<span <span
className={classNames( className={classNames(
active ? "text-white" : "text-indigo-600", active ? "text-white" : "text-indigo-600",
"absolute inset-y-0 right-0 flex items-center pr-4" "absolute inset-y-0 right-0 flex items-center pr-4"
)} )}
> >
<CheckIcon <CheckIcon
className="h-5 w-5" className="h-5 w-5"
aria-hidden="true" aria-hidden="true"
/> />
</span> </span>
) : null} ) : null}
</> </>
)} )}
</Listbox.Option> </Listbox.Option>
))} ))}
</Listbox.Options> </Listbox.Options>
</Transition> </Transition>
</div> </div>
</> </>
)} )}
</Listbox> </Listbox>
</> </>
); );
} }

View file

@ -1,26 +1,41 @@
import { useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { FloatComponentType } from "../../types/components"; import { FloatComponentType } from "../../types/components";
import { TabsContext } from "../../contexts/tabsContext";
export default function FloatComponent({value, onChange, disabled}: FloatComponentType){ export default function FloatComponent({
const [myValue, setMyValue] = useState(value ?? ""); value,
useEffect(()=> { onChange,
if(disabled){ disabled,
setMyValue(""); }: FloatComponentType) {
onChange(""); const [myValue, setMyValue] = useState(value ?? "");
} useEffect(() => {
}, [disabled, onChange]) if (disabled) {
return ( setMyValue("");
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}> onChange("");
<input }
type="number" }, [disabled, onChange]);
value={myValue} const {setDisableCP} = useContext(TabsContext)
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" : "")} return (
placeholder="Type a number from zero to one" <div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
onChange={(e) => { <input
setMyValue(e.target.value); type="number"
onChange(e.target.value); 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" +
</div> (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);
}}
onBlur={() => {
setDisableCP(false)
}}
onFocus={() => {
setDisableCP(true)
}}
/>
</div>
);
}

View file

@ -1,86 +1,94 @@
import { useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { InputComponentType } from "../../types/components"; import { InputComponentType } from "../../types/components";
import { classNames } from "../../utils"; import { classNames } from "../../utils";
import { TabsContext } from "../../contexts/tabsContext";
export default function InputComponent({ export default function InputComponent({
value, value,
onChange, onChange,
disabled, disabled,
password, password,
}: InputComponentType) { }: InputComponentType) {
const [myValue, setMyValue] = useState(value ?? ""); const [myValue, setMyValue] = useState(value ?? "");
const [pwdVisible, setPwdVisible] = useState(false); const [pwdVisible, setPwdVisible] = useState(false);
useEffect(() => { const {setDisableCP} = useContext(TabsContext)
if (disabled) { useEffect(() => {
setMyValue(""); if (disabled) {
onChange(""); setMyValue("");
} onChange("");
}, [disabled, onChange]); }
return ( }, [disabled, onChange]);
<div return (
className={ <div
disabled className={
? "relative pointer-events-none cursor-not-allowed" disabled
: "relative" ? "relative pointer-events-none cursor-not-allowed"
} : "relative"
> }
<input >
value={myValue} <input
className={classNames( value={myValue}
"block w-full pr-12 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", onBlur={() => {
disabled ? " bg-gray-200 dark:bg-gray-700" : "", setDisableCP(false)
password && !pwdVisible && myValue!=="" ? "password" : "" }}
)} onFocus={() => {
placeholder="Type a text" setDisableCP(true)
onChange={(e) => { }}
setMyValue(e.target.value); className={classNames(
onChange(e.target.value); "block w-full pr-12 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" : "",
/> password && !pwdVisible && myValue !== "" ? "password" : ""
<button )}
className="absolute inset-y-0 right-0 items-center px-4 text-gray-600" placeholder="Type a text"
onClick={() => { onChange={(e) => {
setPwdVisible(!pwdVisible); setMyValue(e.target.value);
}} onChange(e.target.value);
> }}
{password && />
(pwdVisible ? ( <button
<svg className="absolute inset-y-0 right-0 items-center px-4 text-gray-600"
xmlns="http://www.w3.org/2000/svg" onClick={() => {
fill="none" setPwdVisible(!pwdVisible);
viewBox="0 0 24 24" }}
strokeWidth={1.5} >
stroke="currentColor" {password &&
className="w-5 h-5" (pwdVisible ? (
> <svg
<path xmlns="http://www.w3.org/2000/svg"
strokeLinecap="round" fill="none"
strokeLinejoin="round" viewBox="0 0 24 24"
d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88" strokeWidth={1.5}
/> stroke="currentColor"
</svg> className="w-5 h-5"
) : ( >
<svg <path
xmlns="http://www.w3.org/2000/svg" strokeLinecap="round"
fill="none" strokeLinejoin="round"
viewBox="0 0 24 24" d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"
strokeWidth={1.5} />
stroke="currentColor" </svg>
className="w-5 h-5" ) : (
> <svg
<path xmlns="http://www.w3.org/2000/svg"
strokeLinecap="round" fill="none"
strokeLinejoin="round" viewBox="0 0 24 24"
d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" strokeWidth={1.5}
/> stroke="currentColor"
<path className="w-5 h-5"
strokeLinecap="round" >
strokeLinejoin="round" <path
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" strokeLinecap="round"
/> strokeLinejoin="round"
</svg> d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"
))} />
</button> <path
</div> strokeLinecap="round"
); strokeLinejoin="round"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
))}
</button>
</div>
);
} }

View file

@ -9,7 +9,7 @@ export default function InputFileComponent({
disabled, disabled,
suffixes, suffixes,
fileTypes, fileTypes,
onFileChange onFileChange,
}: FileComponentType) { }: FileComponentType) {
const [myValue, setMyValue] = useState(value); const [myValue, setMyValue] = useState(value);
const { setErrorData } = useContext(alertContext); const { setErrorData } = useContext(alertContext);
@ -17,23 +17,23 @@ export default function InputFileComponent({
if (disabled) { if (disabled) {
setMyValue(""); setMyValue("");
onChange(""); onChange("");
onFileChange("") onFileChange("");
} }
}, [disabled, onChange]); }, [disabled, onChange]);
function attachFile(fileReadEvent: ProgressEvent<FileReader>) { function attachFile(fileReadEvent: ProgressEvent<FileReader>) {
fileReadEvent.preventDefault(); fileReadEvent.preventDefault();
const file = fileReadEvent.target.result; const file = fileReadEvent.target.result;
onFileChange(file as string) onFileChange(file as string);
} }
function checkFileType(fileName:string):boolean{ function checkFileType(fileName: string): boolean {
for (let index = 0; index < suffixes.length; index++) { for (let index = 0; index < suffixes.length; index++) {
if(fileName.endsWith(suffixes[index])){ if (fileName.endsWith(suffixes[index])) {
return true return true;
} }
} }
return false return false;
} }
const handleButtonClick = () => { const handleButtonClick = () => {
@ -69,9 +69,9 @@ export default function InputFileComponent({
> >
<div className="w-full flex items-center gap-3"> <div className="w-full flex items-center gap-3">
<span <span
onClick={handleButtonClick} onClick={handleButtonClick}
className={ 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" + "truncate block w-full text-gray-500 dark:text-gray-300 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" : "") (disabled ? " bg-gray-200" : "")
} }
> >

View file

@ -1,54 +1,84 @@
import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline"; import { PlusIcon, XMarkIcon } from "@heroicons/react/24/outline";
import { useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { InputListComponentType } from "../../types/components"; import { InputListComponentType } from "../../types/components";
import { TabsContext } from "../../contexts/tabsContext";
var _ = require("lodash"); import _ from "lodash";
export default function InputListComponent({ value, onChange, disabled}:InputListComponentType) { export default function InputListComponent({
const [inputList, setInputList] = useState(value ?? [""]); value,
useEffect(()=> { onChange,
if(disabled){ disabled,
setInputList([""]); }: InputListComponentType) {
onChange([""]); const [inputList, setInputList] = useState(value ?? [""]);
} useEffect(() => {
}, [disabled, onChange]) if (disabled) {
return ( setInputList([""]);
<div className={(disabled ? "pointer-events-none cursor-not-allowed" : "") + "flex flex-col gap-3"}> onChange([""]);
{inputList.map((i, idx) => ( }
<div key={idx} className="w-full flex gap-3"> }, [disabled, onChange]);
<input const {setDisableCP} = useContext(TabsContext)
type="text" return (
value={i} <div
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" : "")} className={
placeholder="Type a text" (disabled ? "pointer-events-none cursor-not-allowed" : "") +
onChange={(e) => { "flex flex-col gap-3"
setInputList((old) => { }
let newInputList = _.cloneDeep(old); >
newInputList[idx] = e.target.value; {inputList.map((i, idx) => (
return newInputList; <div key={idx} className="w-full flex gap-3">
}); <input
onChange(inputList); type="text"
}} value={i}
/> className={
{idx === inputList.length - 1 ? "block w-full form-input rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" +
<button onClick={() => {setInputList((old) => { (disabled ? " bg-gray-200" : "")
let newInputList = _.cloneDeep(old); }
newInputList.push(''); placeholder="Type a text"
return newInputList; onChange={(e) => {
}); setInputList((old) => {
onChange(inputList);}}> let newInputList = _.cloneDeep(old);
<PlusIcon className="w-4 h-4 hover:text-blue-600" /> newInputList[idx] = e.target.value;
</button> return newInputList;
: <button onClick={() => {setInputList((old) => { });
let newInputList = _.cloneDeep(old); onChange(inputList);
newInputList.splice(idx, 1); }}
return newInputList; onBlur={() => {
}); setDisableCP(false)
onChange(inputList);}}> }}
<XMarkIcon className="w-4 h-4 hover:text-red-600" /> onFocus={() => {
</button>} setDisableCP(true)
</div> }}
))} />
</div> {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>
);
} }

View file

@ -1,5 +1,6 @@
import { useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { FloatComponentType } from "../../types/components"; import { FloatComponentType } from "../../types/components";
import { TabsContext } from "../../contexts/tabsContext";
export default function IntComponent({ export default function IntComponent({
value, value,
@ -13,13 +14,25 @@ export default function IntComponent({
onChange(""); onChange("");
} }
}, [disabled, onChange]); }, [disabled, onChange]);
const {setDisableCP} =useContext(TabsContext)
return ( return (
<div className={disabled ? "pointer-events-none cursor-not-allowed w-full" : "w-full"}> <div
className={
disabled ? "pointer-events-none cursor-not-allowed w-full" : "w-full"
}
>
<input <input
onKeyDown={(event) => { onKeyDown={(event) => {
if (event.key !== 'Backspace' && event.key !== 'Enter' && event.key !== 'Delete' && event.key !== 'ArrowLeft' && event.key !== 'ArrowRight' && !/^[-]?\d*$/.test(event.key)) { if (
event.preventDefault(); event.key !== "Backspace" &&
} event.key !== "Enter" &&
event.key !== "Delete" &&
event.key !== "ArrowLeft" &&
event.key !== "ArrowRight" &&
!/^[-]?\d*$/.test(event.key)
) {
event.preventDefault();
}
}} }}
type="number" type="number"
value={myValue} value={myValue}
@ -32,7 +45,14 @@ export default function IntComponent({
setMyValue(e.target.value); setMyValue(e.target.value);
onChange(e.target.value); onChange(e.target.value);
}} }}
onBlur={() => {
setDisableCP(false)
}}
onFocus={() => {
setDisableCP(true)
}}
/> />
</div> </div>
); );
} }

View file

@ -1,17 +1,28 @@
type LoadingComponentProps={ type LoadingComponentProps = {
remSize:number 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>
<br></br>
<span className="animate-pulse text-blue-600 text-lg">Loading...</span>
</div>
);
} }
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>
<br></br>
<span className="animate-pulse text-blue-600 text-lg">Loading...</span>
</div>
)
}

View file

@ -18,7 +18,7 @@ export default function TextAreaComponent({ value, onChange, disabled }:TextArea
<div className="w-full flex items-center gap-3"> <div className="w-full flex items-center gap-3">
<span onClick={()=>{openPopUp(<TextAreaModal value={myValue} setValue={(t:string) => {setMyValue(t); onChange(t);}}/>)}} <span onClick={()=>{openPopUp(<TextAreaModal value={myValue} setValue={(t:string) => {setMyValue(t); onChange(t);}}/>)}}
className={ 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" + "truncate block w-full text-gray-500 dark:text-gray-100 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" : "") (disabled ? " bg-gray-200" : "")
} }
> >

View file

@ -3,73 +3,80 @@ import { classNames } from "../../utils";
import { useEffect } from "react"; import { useEffect } from "react";
import { ToggleComponentType } from "../../types/components"; import { ToggleComponentType } from "../../types/components";
export default function ToggleComponent({ enabled, setEnabled, disabled }:ToggleComponentType) { export default function ToggleComponent({
useEffect(()=> { enabled,
if(disabled){ setEnabled,
setEnabled(false); disabled,
} }: ToggleComponentType) {
}, [disabled, setEnabled]) useEffect(() => {
return ( if (disabled) {
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}> setEnabled(false);
<Switch }
checked={enabled} }, [disabled, setEnabled]);
onChange={(x:boolean) => { return (
setEnabled(x); <div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
}} <Switch
className={classNames( checked={enabled}
enabled ? "bg-indigo-600" : "bg-gray-200 dark:bg-gray-600", onChange={(x: boolean) => {
"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 " setEnabled(x);
)} }}
> className={classNames(
<span className="sr-only">Use setting</span> enabled ? "bg-indigo-600" : "bg-gray-200 dark:bg-gray-600",
<span "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 "
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="sr-only">Use setting</span>
)} <span
> className={classNames(
<span enabled ? "translate-x-5" : "translate-x-0",
className={classNames( "pointer-events-none relative inline-block h-5 w-5 transform rounded-full shadow ring-0 transition duration-200 ease-in-out",
enabled disabled
? "opacity-0 ease-out duration-100" ? "bg-gray-200 dark:bg-gray-600"
: "opacity-100 ease-in duration-200", : "bg-white dark:bg-gray-800"
"absolute inset-0 flex h-full w-full items-center justify-center transition-opacity" )}
)} >
aria-hidden="true" <span
> className={classNames(
<svg enabled
className="h-3 w-3 text-gray-400" ? "opacity-0 ease-out duration-100"
fill="none" : "opacity-100 ease-in duration-200",
viewBox="0 0 12 12" "absolute inset-0 flex h-full w-full items-center justify-center transition-opacity"
> )}
<path aria-hidden="true"
d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2" >
stroke="currentColor" <svg
strokeWidth={2} className="h-3 w-3 text-gray-400"
strokeLinecap="round" fill="none"
strokeLinejoin="round" viewBox="0 0 12 12"
/> >
</svg> <path
</span> d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2"
<span stroke="currentColor"
className={classNames( strokeWidth={2}
enabled strokeLinecap="round"
? "opacity-100 ease-in duration-200" strokeLinejoin="round"
: "opacity-0 ease-out duration-100", />
"absolute inset-0 flex h-full w-full items-center justify-center transition-opacity" </svg>
)} </span>
aria-hidden="true" <span
> className={classNames(
<svg enabled
className="h-3 w-3 text-indigo-600" ? "opacity-100 ease-in duration-200"
fill="currentColor" : "opacity-0 ease-out duration-100",
viewBox="0 0 12 12" "absolute inset-0 flex h-full w-full items-center justify-center transition-opacity"
> )}
<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" /> aria-hidden="true"
</svg> >
</span> <svg
</span> className="h-3 w-3 text-indigo-600"
</Switch> fill="currentColor"
</div> 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>
);
} }

View file

@ -1,7 +1,7 @@
import { createContext, ReactNode, useState } from "react"; import { createContext, ReactNode, useState } from "react";
import { AlertItemType } from "../types/alerts"; import { AlertItemType } from "../types/alerts";
var _ = require("lodash"); import _ from "lodash";
//types for alertContextType //types for alertContextType
type alertContextType = { type alertContextType = {
@ -20,13 +20,13 @@ type alertContextType = {
notificationCenter: boolean; notificationCenter: boolean;
setNotificationCenter: (newState: boolean) => void; setNotificationCenter: (newState: boolean) => void;
notificationList: Array<AlertItemType>; notificationList: Array<AlertItemType>;
pushNotificationList: (Object:AlertItemType) => void; pushNotificationList: (Object: AlertItemType) => void;
clearNotificationList: () => void; clearNotificationList: () => void;
removeFromNotificationList: (index: string) => void; removeFromNotificationList: (index: string) => void;
}; };
//initial values to alertContextType //initial values to alertContextType
const initialValue:alertContextType = { const initialValue: alertContextType = {
errorData: { title: "", list: [] }, errorData: { title: "", list: [] },
setErrorData: () => {}, setErrorData: () => {},
errorOpen: false, errorOpen: false,
@ -49,7 +49,7 @@ const initialValue:alertContextType = {
export const alertContext = createContext<alertContextType>(initialValue); export const alertContext = createContext<alertContextType>(initialValue);
export function AlertProvider({ children }:{children:ReactNode}) { export function AlertProvider({ children }: { children: ReactNode }) {
const [errorData, setErrorDataState] = useState<{ const [errorData, setErrorDataState] = useState<{
title: string; title: string;
list?: Array<string>; list?: Array<string>;
@ -73,68 +73,69 @@ export function AlertProvider({ children }:{children:ReactNode}) {
return newNotificationList; return newNotificationList;
}); });
}; };
/** /**
* Sets the error data state, opens the error dialog and pushes the new error notification to the notification list * 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 * @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> }) { function setErrorData(newState: { title: string; list?: Array<string> }) {
setErrorDataState(newState); setErrorDataState(newState);
setErrorOpen(true); setErrorOpen(true);
if (newState.title) { if (newState.title) {
setNotificationCenter(true); setNotificationCenter(true);
pushNotificationList({ pushNotificationList({
type: "error", type: "error",
title: newState.title, title: newState.title,
list: newState.list, list: newState.list,
id: _.uniqueId(), 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.
* 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.
* @param newState An object containing the title of the notice and optionally a link. */
*/ function setNoticeData(newState: { title: string; link?: string }) {
function setNoticeData(newState: { title: string; link?: string }) { setNoticeDataState(newState);
setNoticeDataState(newState); setNoticeOpen(true);
setNoticeOpen(true); if (newState.title) {
if (newState.title) { // Add new notice to notification center
// Add new notice to notification center setNotificationCenter(true);
setNotificationCenter(true); pushNotificationList({
pushNotificationList({ type: "notice",
type: "notice", title: newState.title,
title: newState.title, link: newState.link,
link: newState.link, id: _.uniqueId(),
id: _.uniqueId(), });
}); }
} }
} /**
/** * Update the success data state and show a success alert notification.
* 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.
* @param newState - A state object with a "title" property to set in the success data state. */
*/ function setSuccessData(newState: { title: string }) {
function setSuccessData(newState: { title: string }) { setSuccessDataState(newState); // update the success data state with the provided new state
setSuccessDataState(newState); // update the success data state with the provided new state setSuccessOpen(true); // open the success alert
setSuccessOpen(true); // open the success alert
// If the new state has a "title" property, add a new success notification to the list // If the new state has a "title" property, add a new success notification to the list
if (newState.title) { if (newState.title) {
setNotificationCenter(true); // show the notification center setNotificationCenter(true); // show the notification center
pushNotificationList({ // add the new notification to the list pushNotificationList({
type: "success", // add the new notification to the list
title: newState.title, type: "success",
id: _.uniqueId(), title: newState.title,
}); id: _.uniqueId(),
});
}
} }
}
function clearNotificationList() { function clearNotificationList() {
setNotificationList([]); setNotificationList([]);
} }
function removeFromNotificationList(index: string) { function removeFromNotificationList(index: string) {
// set the notification list to a new array that filters out the alert with the matching id // set the notification list to a new array that filters out the alert with the matching id
setNotificationList((prevAlertsList) => setNotificationList((prevAlertsList) =>
prevAlertsList.filter((alert) => alert.id !== index) prevAlertsList.filter((alert) => alert.id !== index)
); );
} }
return ( return (
<alertContext.Provider <alertContext.Provider
value={{ value={{

View file

@ -14,13 +14,13 @@ export const darkContext = createContext<darkContextType>(initialValue);
export function DarkProvider({ children }) { export function DarkProvider({ children }) {
const [dark, setDark] = useState(false); const [dark, setDark] = useState(false);
useEffect(()=>{ useEffect(() => {
if(dark){ if (dark) {
document.getElementById("body").classList.add("dark"); document.getElementById("body").classList.add("dark");
} else { } else {
document.getElementById("body").classList.remove("dark"); document.getElementById("body").classList.remove("dark");
} }
}, [dark]) }, [dark]);
return ( return (
<darkContext.Provider <darkContext.Provider
value={{ value={{
@ -31,4 +31,4 @@ export function DarkProvider({ children }) {
{children} {children}
</darkContext.Provider> </darkContext.Provider>
); );
} }

View file

@ -32,7 +32,7 @@ type locationContextType = {
//initial value for location context //initial value for location context
const initialValue = { const initialValue = {
//actual //actual
current: window.location.pathname.replace(/\/$/g, "").split("/"), current: window.location.pathname.replace(/\/$/g, "").split("/"),
isStackedOpen: isStackedOpen:
window.innerWidth > 1024 && window.location.pathname.split("/")[1] window.innerWidth > 1024 && window.location.pathname.split("/")[1]
@ -50,7 +50,7 @@ const initialValue = {
export const locationContext = createContext<locationContextType>(initialValue); export const locationContext = createContext<locationContextType>(initialValue);
export function LocationProvider({ children }:{children:ReactNode}) { export function LocationProvider({ children }: { children: ReactNode }) {
const [current, setCurrent] = useState(initialValue.current); const [current, setCurrent] = useState(initialValue.current);
const [isStackedOpen, setIsStackedOpen] = useState( const [isStackedOpen, setIsStackedOpen] = useState(
initialValue.isStackedOpen initialValue.isStackedOpen

View file

@ -3,31 +3,31 @@ import React, { useState } from "react";
// context to set JSX element on the DOM // context to set JSX element on the DOM
export const PopUpContext = createContext({ export const PopUpContext = createContext({
openPopUp: (popUpElement: JSX.Element) => {}, openPopUp: (popUpElement: JSX.Element) => {},
closePopUp: () => {}, closePopUp: () => {},
}); });
interface PopUpProviderProps { interface PopUpProviderProps {
children: React.ReactNode; children: React.ReactNode;
} }
const PopUpProvider = ({ children }: PopUpProviderProps) => { const PopUpProvider = ({ children }: PopUpProviderProps) => {
const [popUpElements, setPopUpElements] = useState<JSX.Element[]>([]); const [popUpElements, setPopUpElements] = useState<JSX.Element[]>([]);
const openPopUp = (element: JSX.Element) => { const openPopUp = (element: JSX.Element) => {
setPopUpElements(prevPopUps => [element, ...prevPopUps]); setPopUpElements((prevPopUps) => [element, ...prevPopUps]);
}; };
const closePopUp = () => { const closePopUp = () => {
setPopUpElements(prevPopUps => prevPopUps.slice(1)); setPopUpElements((prevPopUps) => prevPopUps.slice(1));
}; };
return ( return (
<PopUpContext.Provider value={{ openPopUp, closePopUp }}> <PopUpContext.Provider value={{ openPopUp, closePopUp }}>
{children} {children}
{popUpElements[0]} {popUpElements[0]}
</PopUpContext.Provider> </PopUpContext.Provider>
); );
}; };
export default PopUpProvider; export default PopUpProvider;

View file

@ -1,10 +1,10 @@
import { import {
createContext, createContext,
useEffect, useEffect,
useState, useState,
useRef, useRef,
ReactNode, ReactNode,
useContext, useContext,
} from "react"; } from "react";
import { FlowType } from "../types/flow"; import { FlowType } from "../types/flow";
import { LangFlowState, TabsContextType } from "../types/tabs"; import { LangFlowState, TabsContextType } from "../types/tabs";
@ -12,224 +12,229 @@ import { normalCaseToSnakeCase, updateObject, updateTemplate } from "../utils";
import { alertContext } from "./alertContext"; import { alertContext } from "./alertContext";
import { typesContext } from "./typesContext"; import { typesContext } from "./typesContext";
import { APITemplateType, TemplateVariableType } from "../types/api"; import { APITemplateType, TemplateVariableType } from "../types/api";
const { v4: uuidv4 } = require("uuid"); import { v4 as uuidv4 } from "uuid";
const TabsContextInitialValue: TabsContextType = { const TabsContextInitialValue: TabsContextType = {
save: () => {}, save: () => {},
tabIndex: 0, tabIndex: 0,
setTabIndex: (index: number) => {}, setTabIndex: (index: number) => {},
flows: [], flows: [],
removeFlow: (id: string) => {}, removeFlow: (id: string) => {},
addFlow: (flowData?: any) => {}, addFlow: (flowData?: any) => {},
updateFlow: (newFlow: FlowType) => {}, updateFlow: (newFlow: FlowType) => {},
incrementNodeId: () => 0, incrementNodeId: () => 0,
downloadFlow: (flow: FlowType) => {}, downloadFlow: (flow: FlowType) => {},
uploadFlow: () => {}, uploadFlow: () => {},
hardReset: () => {}, hardReset: () => {},
disableCP:false,
setDisableCP:(state:boolean)=>{},
}; };
export const TabsContext = createContext<TabsContextType>( export const TabsContext = createContext<TabsContextType>(
TabsContextInitialValue TabsContextInitialValue
); );
export function TabsProvider({ children }: { children: ReactNode }) { export function TabsProvider({ children }: { children: ReactNode }) {
const { setNoticeData } = useContext(alertContext); const { setNoticeData } = useContext(alertContext);
const [tabIndex, setTabIndex] = useState(0); const [tabIndex, setTabIndex] = useState(0);
const [flows, setFlows] = useState<Array<FlowType>>([]); const [flows, setFlows] = useState<Array<FlowType>>([]);
const [id, setId] = useState(""); const [id, setId] = useState("");
const { templates } = useContext(typesContext); const { templates } = useContext(typesContext);
const newNodeId = useRef(0); const newNodeId = useRef(0);
function incrementNodeId() { function incrementNodeId() {
newNodeId.current = newNodeId.current + 1; newNodeId.current = newNodeId.current + 1;
return newNodeId.current; return newNodeId.current;
} }
function save() { function save() {
if (flows.length !== 0) if (flows.length !== 0)
window.localStorage.setItem( window.localStorage.setItem(
"tabsData", "tabsData",
JSON.stringify({ tabIndex, flows, id, nodeId: newNodeId.current }) JSON.stringify({ tabIndex, flows, id, nodeId: newNodeId.current })
); );
} }
useEffect(() => { useEffect(() => {
//save tabs locally //save tabs locally
save(); save();
}, [flows, id, tabIndex, newNodeId]); }, [flows, id, tabIndex, newNodeId]);
useEffect(() => { useEffect(() => {
//get tabs locally saved //get tabs locally saved
let cookie = window.localStorage.getItem("tabsData"); let cookie = window.localStorage.getItem("tabsData");
if (cookie && Object.keys(templates).length > 0) { if (cookie && Object.keys(templates).length > 0) {
let cookieObject: LangFlowState = JSON.parse(cookie); let cookieObject: LangFlowState = JSON.parse(cookie);
cookieObject.flows.forEach((flow) => { cookieObject.flows.forEach((flow) => {
flow.data.nodes.forEach((node) => { flow.data.nodes.forEach((node) => {
if (Object.keys(templates[node.data.type]["template"]).length > 0) { if (Object.keys(templates[node.data.type]["template"]).length > 0) {
node.data.node.template = updateTemplate( node.data.node.template = updateTemplate(
templates[node.data.type][ templates[node.data.type][
"template" "template"
] as unknown as APITemplateType, ] as unknown as APITemplateType,
node.data.node.template as APITemplateType node.data.node.template as APITemplateType
); );
} }
}); });
}); });
setTabIndex(cookieObject.tabIndex); setTabIndex(cookieObject.tabIndex);
setFlows(cookieObject.flows); setFlows(cookieObject.flows);
setId(cookieObject.id); setId(cookieObject.id);
newNodeId.current = cookieObject.nodeId; newNodeId.current = cookieObject.nodeId;
} }
}, [templates]); }, [templates]);
function hardReset() { function hardReset() {
newNodeId.current = 0; newNodeId.current = 0;
setTabIndex(0); setTabIndex(0);
setFlows([]); setFlows([]);
setId(""); setId("");
} }
/** /**
* Downloads the current flow as a JSON file * Downloads the current flow as a JSON file
*/ */
function downloadFlow(flow: FlowType) { function downloadFlow(flow: FlowType) {
// create a data URI with the current flow data // create a data URI with the current flow data
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent( const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
JSON.stringify(flow) JSON.stringify(flow)
)}`; )}`;
// create a link element and set its properties // create a link element and set its properties
const link = document.createElement("a"); const link = document.createElement("a");
link.href = jsonString; link.href = jsonString;
link.download = `${flows[tabIndex].name}.json`; link.download = `${flows[tabIndex].name}.json`;
// simulate a click on the link element to trigger the download // simulate a click on the link element to trigger the download
link.click(); link.click();
setNoticeData({ setNoticeData({
title: "Warning: Critical data,JSON file may including API keys.", title: "Warning: Critical data,JSON file may including API keys.",
}); });
} }
/** /**
* Creates a file input and listens to a change event to upload a JSON flow file. * 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. * 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. * The resulting JSON object is passed to the addFlow function.
*/ */
function uploadFlow() { function uploadFlow() {
// create a file input // create a file input
const input = document.createElement("input"); const input = document.createElement("input");
input.type = "file"; input.type = "file";
// add a change event listener to the file input // add a change event listener to the file input
input.onchange = (e: Event) => { input.onchange = (e: Event) => {
// check if the file type is application/json // check if the file type is application/json
if ((e.target as HTMLInputElement).files[0].type === "application/json") { if ((e.target as HTMLInputElement).files[0].type === "application/json") {
// get the file from the file input // get the file from the file input
const file = (e.target as HTMLInputElement).files[0]; const file = (e.target as HTMLInputElement).files[0];
// read the file as text // read the file as text
file.text().then((text) => { file.text().then((text) => {
// parse the text into a JSON object // parse the text into a JSON object
let flow: FlowType = JSON.parse(text); let flow: FlowType = JSON.parse(text);
addFlow(flow); addFlow(flow);
}); });
} }
}; };
// trigger the file input click event to open the file dialog // trigger the file input click event to open the file dialog
input.click(); input.click();
} }
/** /**
* Removes a flow from an array of flows based on its id. * Removes a flow from an array of flows based on its id.
* Updates the state of flows and tabIndex using setFlows and setTabIndex hooks. * Updates the state of flows and tabIndex using setFlows and setTabIndex hooks.
* @param {string} id - The id of the flow to remove. * @param {string} id - The id of the flow to remove.
*/ */
function removeFlow(id: string) { function removeFlow(id: string) {
setFlows((prevState) => { setFlows((prevState) => {
const newFlows = [...prevState]; const newFlows = [...prevState];
const index = newFlows.findIndex((flow) => flow.id === id); const index = newFlows.findIndex((flow) => flow.id === id);
if (index >= 0) { if (index >= 0) {
if (index === tabIndex) { if (index === tabIndex) {
setTabIndex(flows.length - 2); setTabIndex(flows.length - 2);
newFlows.splice(index, 1); newFlows.splice(index, 1);
} else { } else {
let flowId = flows[tabIndex].id; let flowId = flows[tabIndex].id;
newFlows.splice(index, 1); newFlows.splice(index, 1);
setTabIndex(newFlows.findIndex((flow) => flow.id === flowId)); setTabIndex(newFlows.findIndex((flow) => flow.id === flowId));
} }
} }
return newFlows; return newFlows;
}); });
} }
/** /**
* Add a new flow to the list of flows. * Add a new flow to the list of flows.
* @param flow Optional flow to add. * @param flow Optional flow to add.
*/ */
function addFlow(flow?: FlowType) { function addFlow(flow?: FlowType) {
// Get data from the flow or set it to null if there's no flow provided. // Get data from the flow or set it to null if there's no flow provided.
const data = flow?.data ? flow.data : null; const data = flow?.data ? flow.data : null;
const description = flow?.description ? flow.description : ""; const description = flow?.description ? flow.description : "";
if (data) { if (data) {
data.nodes.forEach((node) => { data.nodes.forEach((node) => {
if (Object.keys(templates[node.data.type]["template"]).length > 0) { if (Object.keys(templates[node.data.type]["template"]).length > 0) {
node.data.node.template = updateTemplate( node.data.node.template = updateTemplate(
templates[node.data.type]["template"] as unknown as APITemplateType, templates[node.data.type]["template"] as unknown as APITemplateType,
node.data.node.template as APITemplateType node.data.node.template as APITemplateType
); );
} }
}); });
} }
// Create a new flow with a default name if no flow is provided. // Create a new flow with a default name if no flow is provided.
let newFlow: FlowType = { let newFlow: FlowType = {
description, description,
name: flow?.name ?? "New Flow", name: flow?.name ?? "New Flow",
id: id.toString(), id: id.toString(),
data, data,
}; };
// Increment the ID counter. // Increment the ID counter.
setId(uuidv4()); setId(uuidv4());
// Add the new flow to the list of flows. // Add the new flow to the list of flows.
setFlows((prevState) => { setFlows((prevState) => {
const newFlows = [...prevState, newFlow]; const newFlows = [...prevState, newFlow];
return newFlows; return newFlows;
}); });
// Set the tab index to the new flow. // Set the tab index to the new flow.
setTabIndex(flows.length); setTabIndex(flows.length);
} }
/** /**
* Updates an existing flow with new data * Updates an existing flow with new data
* @param newFlow - The new flow object containing the updated data * @param newFlow - The new flow object containing the updated data
*/ */
function updateFlow(newFlow: FlowType) { function updateFlow(newFlow: FlowType) {
setFlows((prevState) => { setFlows((prevState) => {
const newFlows = [...prevState]; const newFlows = [...prevState];
const index = newFlows.findIndex((flow) => flow.id === newFlow.id); const index = newFlows.findIndex((flow) => flow.id === newFlow.id);
if (index !== -1) { if (index !== -1) {
newFlows[index].description = newFlow.description ?? ""; newFlows[index].description = newFlow.description ?? "";
newFlows[index].data = newFlow.data; newFlows[index].data = newFlow.data;
newFlows[index].name = newFlow.name; newFlows[index].name = newFlow.name;
} }
return newFlows; return newFlows;
}); });
} }
const [disableCP, setDisableCP] = useState(false);
return ( return (
<TabsContext.Provider <TabsContext.Provider
value={{ value={{
save, disableCP,
hardReset, setDisableCP,
tabIndex, save,
setTabIndex, hardReset,
flows, tabIndex,
incrementNodeId, setTabIndex,
removeFlow, flows,
addFlow, incrementNodeId,
updateFlow, removeFlow,
downloadFlow, addFlow,
uploadFlow, updateFlow,
}} downloadFlow,
> uploadFlow,
{children} }}
</TabsContext.Provider> >
); {children}
</TabsContext.Provider>
);
} }

View file

@ -1,12 +1,12 @@
import { createContext, ReactNode, useEffect, useState } from "react"; import { createContext, ReactNode, useEffect, useState } from "react";
import { Node} from "reactflow"; import { Node } from "reactflow";
import { typesContextType } from "../types/typesContext"; import { typesContextType } from "../types/typesContext";
import { getAll } from "../controllers/API"; import { getAll } from "../controllers/API";
import { APIKindType } from "../types/api"; import { APIKindType } from "../types/api";
//context to share types adn functions from nodes to flow //context to share types adn functions from nodes to flow
const initialValue:typesContextType = { const initialValue: typesContextType = {
reactFlowInstance: null, reactFlowInstance: null,
setReactFlowInstance: () => {}, setReactFlowInstance: () => {},
deleteNode: () => {}, deleteNode: () => {},
@ -14,13 +14,13 @@ const initialValue:typesContextType = {
setTypes: () => {}, setTypes: () => {},
templates: {}, templates: {},
setTemplates: () => {}, setTemplates: () => {},
data:{}, data: {},
setData:()=>{} setData: () => {},
}; };
export const typesContext = createContext<typesContextType>(initialValue); export const typesContext = createContext<typesContextType>(initialValue);
export function TypesProvider({ children }:{children:ReactNode}) { export function TypesProvider({ children }: { children: ReactNode }) {
const [types, setTypes] = useState({}); const [types, setTypes] = useState({});
const [reactFlowInstance, setReactFlowInstance] = useState(null); const [reactFlowInstance, setReactFlowInstance] = useState(null);
const [templates, setTemplates] = useState({}); const [templates, setTemplates] = useState({});
@ -35,11 +35,11 @@ export function TypesProvider({ children }:{children:ReactNode}) {
setData(result.data); setData(result.data);
setTemplates( setTemplates(
Object.keys(result.data).reduce((acc, curr) => { Object.keys(result.data).reduce((acc, curr) => {
Object.keys(result.data[curr]).forEach((c: keyof APIKindType)=>{ Object.keys(result.data[curr]).forEach((c: keyof APIKindType) => {
acc[c] = result.data[curr][c] acc[c] = result.data[curr][c];
}) });
return acc; return acc;
},{}) }, {})
); );
// Set the types by reducing over the keys of the result data and updating the accumulator. // Set the types by reducing over the keys of the result data and updating the accumulator.
setTypes( setTypes(
@ -59,11 +59,15 @@ export function TypesProvider({ children }:{children:ReactNode}) {
getTypes(); getTypes();
}, [setTypes]); }, [setTypes]);
function deleteNode(idx:string) { function deleteNode(idx: string) {
reactFlowInstance.setNodes( reactFlowInstance.setNodes(
reactFlowInstance.getNodes().filter((n:Node) => n.id !== idx) reactFlowInstance.getNodes().filter((n: Node) => n.id !== idx)
);
reactFlowInstance.setEdges(
reactFlowInstance
.getEdges()
.filter((ns) => ns.source !== idx && ns.target !== idx)
); );
reactFlowInstance.setEdges(reactFlowInstance.getEdges().filter((ns) => ns.source !== idx && ns.target !== idx));
} }
return ( return (
<typesContext.Provider <typesContext.Provider
@ -76,7 +80,7 @@ export function TypesProvider({ children }:{children:ReactNode}) {
setTemplates, setTemplates,
templates, templates,
data, data,
setData setData,
}} }}
> >
{children} {children}

View file

@ -4,40 +4,40 @@ import axios, { AxiosResponse } from "axios";
import { FlowType } from "../../types/flow"; import { FlowType } from "../../types/flow";
export async function getAll(): Promise<AxiosResponse<APIObjectType>> { export async function getAll(): Promise<AxiosResponse<APIObjectType>> {
return await axios.get(`/all`); return await axios.get(`/all`);
} }
export async function sendAll(data: sendAllProps) { export async function sendAll(data: sendAllProps) {
return await axios.post(`/predict`, data); return await axios.post(`/predict`, data);
} }
export async function checkCode( export async function checkCode(
code: string code: string
): Promise<AxiosResponse<errorsTypeAPI>> { ): Promise<AxiosResponse<errorsTypeAPI>> {
return await axios.post("/validate/code", { code }); return await axios.post("/validate/code", { code });
} }
export async function checkPrompt( export async function checkPrompt(
template: string template: string
): Promise<AxiosResponse<PromptTypeAPI>> { ): Promise<AxiosResponse<PromptTypeAPI>> {
return await axios.post("/validate/prompt", { template }); return await axios.post("/validate/prompt", { template });
} }
export async function getExamples(): Promise<FlowType[]> { export async function getExamples(): Promise<FlowType[]> {
const url = const url =
"https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples"; "https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples";
const response = await axios.get(url); const response = await axios.get(url);
const jsonFiles = response.data.filter((file: any) => { const jsonFiles = response.data.filter((file: any) => {
return file.name.endsWith(".json"); return file.name.endsWith(".json");
}); });
const contentsPromises = jsonFiles.map(async (file: any) => { const contentsPromises = jsonFiles.map(async (file: any) => {
const contentResponse = await axios.get(file.download_url); const contentResponse = await axios.get(file.download_url);
return contentResponse.data; return contentResponse.data;
}); });
const contents = await Promise.all(contentsPromises); const contents = await Promise.all(contentsPromises);
return contents; return contents;
} }

View file

@ -1,15 +1,17 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body { body {
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif; sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
code { code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace; monospace;
} }

View file

@ -5,14 +5,15 @@ import reportWebVitals from "./reportWebVitals";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import ContextWrapper from "./contexts"; import ContextWrapper from "./contexts";
import './index.css';
const root = ReactDOM.createRoot( const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement document.getElementById("root") as HTMLElement
); );
root.render( root.render(
<ContextWrapper> <ContextWrapper>
<BrowserRouter> <BrowserRouter>
<App />
<App />
</BrowserRouter> </BrowserRouter>
</ContextWrapper> </ContextWrapper>
); );

View file

@ -0,0 +1,186 @@
import { Dialog, Transition } from "@headlessui/react";
import { IconCheck, IconClipboard, IconDownload } from '@tabler/icons-react';
import { XMarkIcon, CommandLineIcon, CodeBracketSquareIcon } from "@heroicons/react/24/outline";
import { Fragment, useContext, useRef, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext";
import "ace-builds/src-noconflict/mode-python";
import "ace-builds/src-noconflict/theme-github";
import "ace-builds/src-noconflict/theme-twilight";
import "ace-builds/src-noconflict/ext-language_tools";
// import "ace-builds/webpack-resolver";
import { darkContext } from "../../contexts/darkContext";
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { oneDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
export default function ApiModal({ flowName }) {
const [open, setOpen] = useState(true);
const { dark } = useContext(darkContext);
const { closePopUp } = useContext(PopUpContext);
const [activeTab, setActiveTab] = useState(0);
const ref = useRef();
const [isCopied, setIsCopied] = useState<Boolean>(false);
const copyToClipboard = () => {
if (!navigator.clipboard || !navigator.clipboard.writeText) {
return;
}
navigator.clipboard.writeText(tabs[activeTab].code).then(() => {
setIsCopied(true);
setTimeout(() => {
setIsCopied(false);
}, 2000);
});
};
function setModalOpen(x: boolean) {
setOpen(x);
if (x === false) {
setTimeout(() => {
closePopUp();
}, 300);
}
}
const pythonApiCode = `import requests
API_URL = "${window.location.protocol}//${window.location.host}/predict"
def predict(message):
with open("${flowName}.json", "r") as f:
json_data = json.load(f)
payload = {'exported_flow': json_data, 'message': message}
response = requests.post(API_URL, json=payload)
return response.json() # JSON {"result": "Response"}
print(predict("Your message"))`;
const pythonCode = `from langflow import load_flow_from_json
flow = load_flow_from_json("${flowName}.json")
# Now you can use it like any chain
flow("Hey, have you heard of LangFlow?")`;
const tabs = [
{
name: "Python API",
mode: "python",
image: "https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
code: pythonApiCode,
},
{
name: "Python Code",
mode: "python",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
code: pythonCode,
},
]
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"
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">
<CodeBracketSquareIcon
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"
>
Code
</Dialog.Title>
</div>
</div>
<div className="h-full w-full bg-gray-200 overflow-auto dark:bg-gray-900 p-4 gap-4 flex flex-row justify-center items-center">
<div className="flex flex-col h-full w-full ">
<div className="flex px-5 z-10">
{tabs.map((tab, index) => (
<button onClick={() => {
setActiveTab(index);
}} className={"p-2 rounded-t-lg w-44 border border-b-0 border-gray-300 dark:border-gray-700 dark:text-gray-300 -mr-px flex justify-center items-center gap-4 " + (activeTab === index ? " bg-white dark:bg-gray-800" : "bg-gray-100 dark:bg-gray-900")}>
{tab.name}
<img src={tab.image} className="w-6" />
</button>
))}
</div>
<div className="overflow-hidden px-4 py-5 sm:p-6 w-full h-full rounded-lg shadow bg-white dark:bg-gray-800">
<div className="w-full flex items-center justify-between mb-2">
<span className="text-sm text-gray-500 dark:text-gray-300">
Export your flow to use it with this code.
</span>
<button
className="flex gap-1.5 items-center rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300"
onClick={copyToClipboard}
>
{isCopied ? <IconCheck size={18} /> : <IconClipboard size={18} />}
{isCopied ? 'Copied!' : 'Copy code'}
</button>
</div>
<SyntaxHighlighter
className="h-[370px]"
language={tabs[activeTab].mode}
style={oneDark}
customStyle={{ margin: 0 }}
>
{tabs[activeTab].code}
</SyntaxHighlighter>
</div>
</div>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
}

View file

@ -35,7 +35,7 @@ export default function ModalField({ data, title, required, id, name, type }) {
> >
{display && ( {display && (
<div> <div>
<span className="mx-2">{title}</span> <span className="mx-2 dark:text-gray-300">{title}</span>
<span className="text-red-600">{required ? " *" : ""}</span> <span className="text-red-600">{required ? " *" : ""}</span>
</div> </div>
)} )}

View file

@ -54,9 +54,9 @@ export default function NodeModal({ data }: { data: NodeDataType }) {
> >
<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]"> <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"> <div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
<button <button
type="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" className="rounded-md text-gray-400 hover:text-gray-500"
onClick={() => { onClick={() => {
setModalOpen(false); setModalOpen(false);
}} }}
@ -87,37 +87,38 @@ export default function NodeModal({ data }: { data: NodeDataType }) {
<div className="flex h-full w-full"> <div className="flex h-full w-full">
<div className="overflow-hidden px-4 sm:p-4 w-full rounded-lg bg-white dark:bg-gray-800 shadow"> <div className="overflow-hidden px-4 sm:p-4 w-full rounded-lg bg-white dark:bg-gray-800 shadow">
<div className="flex flex-col h-full gap-5"> <div className="flex flex-col h-full gap-5">
{ {Object.keys(data.node.template)
Object.keys(data.node.template) .filter(
.filter((t) => t.charAt(0) !== "_"&& data.node.template[t].advanced && data.node.template[t].show) (t) =>
.map((t: string, idx) => { t.charAt(0) !== "_" &&
return ( data.node.template[t].advanced &&
<ModalField data.node.template[t].show
)
.map((t: string, idx) => {
return (
<ModalField
key={idx} key={idx}
data={data} data={data}
title={ title={
data.node.template[t].display_name data.node.template[t].display_name
? data.node.template[t].display_name ? data.node.template[t].display_name
: data.node.template[t].name : data.node.template[t].name
? toNormalCase( ? toNormalCase(data.node.template[t].name)
data.node.template[t].name : toNormalCase(t)
) }
: toNormalCase(t) required={data.node.template[t].required}
} id={
required={data.node.template[t].required} data.node.template[t].type +
id={ "|" +
data.node.template[t].type + t +
"|" + "|" +
t + data.id
"|" + }
data.id name={t}
} type={data.node.template[t].type}
name={t} />
type={data.node.template[t].type} );
/> })}
);
})
}
</div> </div>
</div> </div>
</div> </div>

View file

@ -24,14 +24,14 @@ export default function ChatInput({
}} }}
ref={inputRef} ref={inputRef}
disabled={lockChat} disabled={lockChat}
style={{resize: "none" }} style={{ resize: "none" }}
value={lockChat ? "Thinking..." : chatValue} value={lockChat ? "Thinking..." : chatValue}
onChange={(e) => { onChange={(e) => {
setChatValue(e.target.value); setChatValue(e.target.value);
}} }}
className={classNames( className={classNames(
lockChat ? "bg-gray-500 text-white" : "dark:bg-gray-700", lockChat ? "bg-gray-300 text-black dark:bg-gray-700 dark:text-gray-300" : "bg-gray-200 text-black dark:bg-gray-900 dark:text-gray-300",
"form-input block w-full custom-scroll h-10 rounded-md border-gray-300 dark:border-gray-600 dark:text-white pr-10 sm:text-sm" "form-input block w-full custom-scroll h-10 rounded-md border-gray-300 dark:border-gray-600 pr-10 sm:text-sm"
)} )}
placeholder={"Send a message..."} placeholder={"Send a message..."}
/> />
@ -39,12 +39,12 @@ export default function ChatInput({
<button disabled={lockChat} onClick={() => sendMessage()}> <button disabled={lockChat} onClick={() => sendMessage()}>
{lockChat ? ( {lockChat ? (
<LockClosedIcon <LockClosedIcon
className="h-5 w-5 text-gray-400 dark:hover:text-gray-300 animate-pulse" className="h-5 w-5 text-gray-500 dark:hover:text-gray-300 animate-pulse"
aria-hidden="true" aria-hidden="true"
/> />
) : ( ) : (
<PaperAirplaneIcon <PaperAirplaneIcon
className="h-5 w-5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300" className="h-5 w-5 text-gray-500 hover:text-gray-600 dark:hover:text-gray-300"
aria-hidden="true" aria-hidden="true"
/> />
)} )}

View file

@ -0,0 +1,82 @@
import { IconCheck, IconClipboard, IconDownload } from "@tabler/icons-react";
import { FC, memo, useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
import { programmingLanguages } from "../../../../utils";
interface Props {
language: string;
value: string;
}
export const CodeBlock: FC<Props> = memo(({ language, value }) => {
const [isCopied, setIsCopied] = useState<Boolean>(false);
const copyToClipboard = () => {
if (!navigator.clipboard || !navigator.clipboard.writeText) {
return;
}
navigator.clipboard.writeText(value).then(() => {
setIsCopied(true);
setTimeout(() => {
setIsCopied(false);
}, 2000);
});
};
const downloadAsFile = () => {
const fileExtension = programmingLanguages[language] || ".file";
const suggestedFileName = `${"generated-code"}${fileExtension}`;
const fileName = window.prompt("enter file name", suggestedFileName);
if (!fileName) {
// user pressed cancel on prompt
return;
}
const blob = new Blob([value], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.download = fileName;
link.href = url;
link.style.display = "none";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
return (
<div className="codeblock font-sans text-[16px]">
<div className="flex items-center justify-between py-1.5 px-4">
<span className="text-xs lowercase text-white">{language}</span>
<div className="flex items-center">
<button
className="flex gap-1.5 items-center rounded bg-none p-1 text-xs text-white"
onClick={copyToClipboard}
>
{isCopied ? <IconCheck size={18} /> : <IconClipboard size={18} />}
{isCopied ? "Copied!" : "Copy code"}
</button>
<button
className="flex items-center rounded bg-none p-1 text-xs text-white"
onClick={downloadAsFile}
>
<IconDownload size={18} />
</button>
</div>
</div>
<SyntaxHighlighter
className=" w-[570px]"
language={language}
style={oneDark}
customStyle={{ margin: 0 }}
>
{value}
</SyntaxHighlighter>
</div>
);
});
CodeBlock.displayName = "CodeBlock";

View file

@ -1,82 +1,143 @@
import { ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline"; import { ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline";
import { useState } from "react"; import { useEffect, useRef, useState } from "react";
import { ChatMessageType } from "../../../types/chat"; import { ChatMessageType } from "../../../types/chat";
import { classNames } from "../../../utils"; import { classNames } from "../../../utils";
import AiIcon from "../../../assets/Gooey Ring-5s-271px.svg"; import AiIcon from "../../../assets/Gooey Ring-5s-271px.svg";
import AiIconStill from "../../../assets/froze-flow.png"
import { UserIcon } from "@heroicons/react/24/solid"; import { UserIcon } from "@heroicons/react/24/solid";
import FileCard from "../fileComponent"; import FileCard from "../fileComponent";
var Convert = require("ansi-to-html"); import ReactMarkdown from "react-markdown";
var convert = new Convert({ newline: true }); import rehypeMathjax from "rehype-mathjax";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import { CodeBlock } from "./codeBlock";
import Convert from "ansi-to-html";
export default function ChatMessage({ chat }: { chat: ChatMessageType }) { export default function ChatMessage({ chat, lockChat }: { chat: ChatMessageType, lockChat: boolean }) {
const [hidden, setHidden] = useState(true); const [message, setMessage] = useState("");
return ( const imgRef = useRef(null);
<div useEffect(() => {
className={classNames( setMessage(chat.message);
"w-full py-2 pl-2 flex", }, [chat.message]);
chat.isSend ? "bg-white dark:bg-gray-800 " : "bg-gray-200 dark:bg-gray-700" const [hidden, setHidden] = useState(true);
)} return (
> <div
<div className={classNames(
className={classNames( "w-full py-2 pl-2 flex",
"rounded-full w-8 h-8 flex items-center my-3 justify-center", chat.isSend
chat.isSend ? "bg-gray-900" : "bg-gray-200" ? "bg-white dark:bg-gray-900 "
)} : "bg-gray-200 dark:bg-gray-800"
> )}
{!chat.isSend && <img className="scale-150" src={AiIcon} />} >
{chat.isSend && <UserIcon className="w-6 h-6 -mb-1 text-gray-200" />} <div
</div> className={classNames(
{!chat.isSend ? ( "rounded-full overflow-hidden w-8 h-8 flex items-center my-3 justify-center"
<div className="w-full text-start flex items-center"> )}
<div className="w-full relative text-start inline-block text-gray-600 text-sm font-normal"> >
{hidden && chat.thought && chat.thought !== "" && ( {!chat.isSend && <div className="relative w-8 h-8">
<div <img className={"absolute transition-opacity duration-500 scale-150 " + (lockChat ? "opacity-100" : "opacity-0")} src={AiIcon} />
onClick={() => setHidden((prev) => !prev)} <img className={"absolute transition-opacity duration-500 scale-150 " + (lockChat ? "opacity-0" : "opacity-100")} src={AiIconStill} />
className="absolute -top-1 -left-2 cursor-pointer" </div>}
> {chat.isSend && <UserIcon className="w-6 h-6 -mb-1 text-gray-800 dark:text-gray-200" />}
<ChatBubbleOvalLeftEllipsisIcon className="w-5 h-5 animate-bounce dark:text-white" /> </div>
</div> {!chat.isSend ? (
)} <div className="w-full text-start flex items-center">
{chat.thought && chat.thought !== "" && !hidden && ( <div className="w-full relative text-start inline-block text-gray-600 dark:text-gray-300 text-sm font-normal">
<div {hidden && chat.thought && chat.thought !== "" && (
onClick={() => setHidden((prev) => !prev)} <div
className=" text-start inline-block rounded-md h-full border border-gray-300 onClick={() => setHidden((prev) => !prev)}
bg-gray-100 w-[95%] pb-3 pt-3 px-2 ml-3 cursor-pointer scrollbar-hide overflow-scroll" className="absolute -top-1 -left-2 cursor-pointer"
dangerouslySetInnerHTML={{ >
__html: convert.toHtml(chat.thought), <ChatBubbleOvalLeftEllipsisIcon className="w-5 h-5 animate-bounce dark:text-white" />
}} </div>
></div> )}
)} {chat.thought && chat.thought !== "" && !hidden && (
{chat.thought && chat.thought !== "" && !hidden && <br></br>} <div
<div className="w-full px-4 pb-3 pt-3 pr-8"> onClick={() => setHidden((prev) => !prev)}
<span className="dark:text-white"> className=" text-start inline-block rounded-md text-gray-600 dark:text-gray-200 h-full border border-gray-300 dark:border-gray-500
{chat.message} bg-gray-100 dark:bg-gray-800 w-[95%] pb-3 pt-3 px-2 ml-3 cursor-pointer scrollbar-hide overflow-scroll"
{chat.files && ( dangerouslySetInnerHTML={{
<div className="my-2 w-full"> __html: convert.toHtml(chat.thought),
{chat.files.map((file, index) => { }}
return ( ></div>
<div key={index} className="my-2 w-full"> )}
<FileCard {chat.thought && chat.thought !== "" && !hidden && <br></br>}
fileName={"Generated File"} <div className="w-full px-4 pb-3 pt-3 pr-8">
fileType={file.data_type} <div className="dark:text-white w-full">
content={file.data} <div className="w-full">
/> <ReactMarkdown
</div> remarkPlugins={[remarkGfm, remarkMath]}
); rehypePlugins={[rehypeMathjax]}
})} className="markdown prose dark:prose-invert text-gray-600 dark:text-gray-200"
</div> components={{
)} code({ node, inline, className, children, ...props }) {
</span> if (children.length) {
</div> if (children[0] == "▍") {
</div> return (
</div> <span className="animate-pulse cursor-default mt-1">
) : ( ▍
<div className="w-full flex items-center"> </span>
<div className="text-start inline-block px-3 text-sm text-gray-600 dark:text-white"> );
{chat.message} }
</div>
</div> children[0] = (children[0] as string).replace(
)} "`▍`",
</div> "▍"
); );
}
const match = /language-(\w+)/.exec(className || "");
return !inline ? (
<CodeBlock
key={Math.random()}
language={(match && match[1]) || ""}
value={String(children).replace(/\n$/, "")}
{...props}
/>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
}}
>
{message}
</ReactMarkdown>
</div>
{chat.files && (
<div className="my-2 w-full">
{chat.files.map((file, index) => {
return (
<div key={index} className="my-2 w-full">
<FileCard
fileName={"Generated File"}
fileType={file.data_type}
content={file.data}
/>
</div>
);
})}
</div>
)}
</div>
</div>
</div>
</div>
) : (
<div className="w-full flex items-center">
<div className="text-start inline-block px-3 text-sm text-gray-600 dark:text-white">
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax]}
className="markdown prose dark:prose-invert text-gray-600 dark:text-gray-200"
>
{message}
</ReactMarkdown>
</div>
</div>
)}
</div>
);
} }

View file

@ -1,19 +1,55 @@
import { CloudArrowDownIcon, DocumentIcon } from "@heroicons/react/24/outline"; import { CloudArrowDownIcon, DocumentIcon } from "@heroicons/react/24/outline";
import * as base64js from 'base64-js'; import * as base64js from "base64-js";
import { useState } from "react";
export default function FileCard({ fileName, content, fileType }) { export default function FileCard({ fileName, content, fileType }) {
const handleDownload = () => { const handleDownload = () => {
const byteArray = new Uint8Array(base64js.toByteArray(content)); const byteArray = new Uint8Array(base64js.toByteArray(content));
const blob = new Blob([byteArray], { type: 'application/octet-stream' }); const blob = new Blob([byteArray], { type: "application/octet-stream" });
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const link = document.createElement('a'); const link = document.createElement("a");
link.href = url; link.href = url;
link.download = fileName+".png"; link.download = fileName + ".png";
document.body.appendChild(link); document.body.appendChild(link);
link.click(); link.click();
document.body.removeChild(link); document.body.removeChild(link);
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
}; };
const [isHovered, setIsHovered] = useState(false);
function handleMouseEnter() {
setIsHovered(true);
}
function handleMouseLeave() {
setIsHovered(false);
}
if (fileType === "image") {
return (
<div
className="relative w-1/4 h-1/4"
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<img
src={`data:image/png;base64,${content}`}
alt="generated image"
className="rounded-lg w-full h-full"
/>
{isHovered && (
<div
className={`absolute top-0 right-0 bg-gray-100 text-gray-700 rounded-bl-lg px-1 text-sm font-bold dark:bg-gray-700 dark:text-gray-300`}
>
<button
className="text-gray-500 py-1 px-2 dark:bg-gray-700 dark:text-gray-300"
onClick={handleDownload}
>
<CloudArrowDownIcon className="hover:scale-110 w-5 h-5 text-current"></CloudArrowDownIcon>
</button>
</div>
)}
</div>
);
}
return ( return (
<button <button
@ -22,12 +58,19 @@ export default function FileCard({ fileName, content, fileType }) {
> >
<div className="flex gap-2 text-current items-center w-full mr-2"> <div className="flex gap-2 text-current items-center w-full mr-2">
{" "} {" "}
<DocumentIcon className="w-8 h-8" /> {fileType === "image" ? (
<img
src={`data:image/png;base64,${content}`}
alt=""
className="w-8 h-8"
/>
) : (
<DocumentIcon className="w-8 h-8" />
)}
<div className="flex flex-col items-start"> <div className="flex flex-col items-start">
{" "} {" "}
<div className="truncate text-sm text-current">{fileName}</div> <div className="truncate text-sm text-current">{fileName}</div>
<div className="truncate text-xs text-gray-500">{fileType}</div> <div className="truncate text-xs text-gray-500">{fileType}</div>
</div> </div>
<CloudArrowDownIcon className="w-6 h-6 text-current ml-auto" /> <CloudArrowDownIcon className="w-6 h-6 text-current ml-auto" />
</div> </div>

View file

@ -1,23 +1,18 @@
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import { import { ChatBubbleOvalLeftEllipsisIcon, XMarkIcon } from "@heroicons/react/24/outline";
ChatBubbleOvalLeftEllipsisIcon,
LockClosedIcon,
PaperAirplaneIcon,
} from "@heroicons/react/24/outline";
import { Fragment, useContext, useEffect, useRef, useState } from "react"; import { Fragment, useContext, useEffect, useRef, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext";
import { FlowType, NodeType } from "../../types/flow"; import { FlowType, NodeType } from "../../types/flow";
import { TabsContext } from "../../contexts/tabsContext";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
import { toNormalCase } from "../../utils"; import { toNormalCase } from "../../utils";
import { typesContext } from "../../contexts/typesContext"; import { typesContext } from "../../contexts/typesContext";
import ChatMessage from "./chatMessage"; import ChatMessage from "./chatMessage";
import { FaEraser } from "react-icons/fa"; import { FaEraser } from "react-icons/fa";
import { HiX } from "react-icons/hi";
import { sendAllProps } from "../../types/api"; import { sendAllProps } from "../../types/api";
import { ChatMessageType, ChatType } from "../../types/chat"; import { ChatMessageType, ChatType } from "../../types/chat";
import ChatInput from "./chatInput"; import ChatInput from "./chatInput";
const _ = require("lodash"); import _ from "lodash";
export default function ChatModal({ export default function ChatModal({
flow, flow,
@ -32,8 +27,15 @@ export default function ChatModal({
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]); const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
const { reactFlowInstance } = useContext(typesContext); const { reactFlowInstance } = useContext(typesContext);
const { setErrorData, setNoticeData } = useContext(alertContext); const { setErrorData, setNoticeData } = useContext(alertContext);
const [ws, setWs] = useState<WebSocket | null>(null); const ws = useRef<WebSocket | null>(null);
const [lockChat, setLockChat] = useState(false); const [lockChat, setLockChat] = useState(false);
const isOpen = useRef(open);
useEffect(() => {
isOpen.current = open;
}, [open]);
var isStream = false;
const addChatHistory = ( const addChatHistory = (
message: string, message: string,
isSend: boolean, isSend: boolean,
@ -53,135 +55,176 @@ export default function ChatModal({
}); });
}; };
//add proper type signature for function
function updateLastMessage({
str,
thought,
end = false,
files,
}: {
str?: string;
thought?: string;
// end param default is false
end?: boolean;
files?: Array<any>;
}) {
setChatHistory((old) => {
let newChat = [...old];
if (str) {
if (end) {
newChat[newChat.length - 1].message = str;
} else {
newChat[newChat.length - 1].message =
newChat[newChat.length - 1].message + str;
}
}
if (thought) {
newChat[newChat.length - 1].thought = thought;
}
if (files) {
newChat[newChat.length - 1].files = files;
}
return newChat;
});
}
function handleOnClose(event: CloseEvent) {
if (isOpen.current) {
setErrorData({ title: event.reason });
setLockChat(false);
setTimeout(() => {
connectWS();
}, 1000);
}
}
function handleWsMessage(data: any) {
if (Array.isArray(data)) {
//set chat history
setChatHistory((_) => {
let newChatHistory: ChatMessageType[] = [];
data.forEach(
(chatItem: {
intermediate_steps?: "string";
is_bot: boolean;
message: string;
type: string;
files?: Array<any>;
}) => {
if (chatItem.message) {
newChatHistory.push(
chatItem.files
? {
isSend: !chatItem.is_bot,
message: chatItem.message,
thought: chatItem.intermediate_steps,
files: chatItem.files,
}
: {
isSend: !chatItem.is_bot,
message: chatItem.message,
thought: chatItem.intermediate_steps,
}
);
}
}
);
return newChatHistory;
});
}
if (data.type === "start") {
addChatHistory("", false);
isStream = true;
}
if (data.type === "end") {
if (data.intermediate_steps) {
updateLastMessage({
str: data.message,
thought: data.intermediate_steps,
end: true,
});
}
if (data.files) {
updateLastMessage({
end: true,
files: data.files,
});
}
setLockChat(false);
isStream = false;
}
if (data.type === "stream" && isStream) {
updateLastMessage({ str: data.message });
}
}
function connectWS() { function connectWS() {
console.log("conectou");
try { try {
const urlWs = const urlWs =
process.env.NODE_ENV === "development" process.env.NODE_ENV === "development"
? `ws://localhost:7860/chat/${flow.id}` ? `ws://localhost:7860/chat/${flow.id}`
: `${window.location.protocol === "https:" ? "wss" : "ws"}://${ : `${window.location.protocol === "https:" ? "wss" : "ws"}://${window.location.host
window.location.host }/chat/${flow.id}`;
}/chat/${flow.id}`;
const newWs = new WebSocket(urlWs); const newWs = new WebSocket(urlWs);
newWs.onopen = () => { newWs.onopen = () => {
console.log("WebSocket connection established!"); console.log("WebSocket connection established!");
}; };
console.log(flow.id);
newWs.onmessage = (event) => { newWs.onmessage = (event) => {
try { const data = JSON.parse(event.data);
setLockChat(false); console.log("Received data:", data);
const data = JSON.parse(event.data); handleWsMessage(data);
console.log("Received data:", data); //get chat history
//get chat history
if (Array.isArray(data)) {
console.log(data);
setChatHistory((_) => {
let newChatHistory: ChatMessageType[] = [];
data.forEach(
(chatItem: {
intermediate_steps?: "string";
is_bot: boolean;
message: string;
type: string;
files?: Array<any>;
}) => {
if (chatItem.message) {
newChatHistory.push(
chatItem.files
? {
isSend: !chatItem.is_bot,
message: chatItem.message,
thought: chatItem.intermediate_steps,
files: chatItem.files,
}
: {
isSend: !chatItem.is_bot,
message: chatItem.message,
thought: chatItem.intermediate_steps,
}
);
}
}
);
return newChatHistory;
});
}
if (data.type === "end") {
if (data.files) {
addChatHistory(
data.message,
false,
data.intermediate_steps,
data.files
);
} else {
addChatHistory(data.message, false, data.intermediate_steps);
}
}
if (data.type == "file") {
console.log(data);
}
} catch (error) {
if (event.data !== "Error: 1005") {
setErrorData({ title: event.data });
newWs.close();
connectWS();
}
}
}; };
newWs.onclose = (_) => { newWs.onclose = (event) => {
if (open) { handleOnClose(event);
setLockChat(false);
setTimeout(() => {
connectWS();
}, 1000);
}
}; };
newWs.onerror = (ev) => { newWs.onerror = (ev) => {
console.log(ev, "error"); console.log(ev, "error");
setErrorData({
title: "There was an error on web connection, please: ",
list: [
"Refresh the page",
"Use a new flow tab",
"Check if the backend is up",
],
});
}; };
setWs(newWs); ws.current = newWs;
return newWs;
} catch { } catch {
setErrorData({ setErrorData({
title: "There was an error on web connection, please: ", title: "There was an error on web connection, please: ",
list: [ list: [
"refresh the page", "Refresh the page",
"use a new flow tab", "Use a new flow tab",
"check if the backend is up", "Check if the backend is up",
], ],
}); });
} }
} }
useEffect(() => { useEffect(() => {
if (ws && (ws.readyState === ws.CLOSED || ws.readyState === ws.CLOSING)) { connectWS();
let newWs = connectWS();
return () => {
console.log("trigger");
newWs.close();
};
}
}, [lockChat]);
useEffect(() => {
let newWs = connectWS();
return () => { return () => {
console.log("trigger"); console.log("unmount");
newWs.close(); console.log(ws);
if (ws) {
ws.current.close();
}
}; };
}, []); }, []);
async function sendAll(data: sendAllProps) { async function sendAll(data: sendAllProps) {
try { try {
if (ws) { if (ws) {
ws.send(JSON.stringify(data)); ws.current.send(JSON.stringify(data));
} }
} catch (error) { } catch (error) {
setErrorData({ setErrorData({
title: "There was an erro sending the message", title: "There was an error sending the message",
list: [error.message], list: [error.message],
}); });
setChatValue(data.message); setChatValue(data.message);
@ -193,12 +236,6 @@ export default function ChatModal({
if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" }); if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" });
}, [chatHistory]); }, [chatHistory]);
useEffect(() => {
if (ws && ws.readyState === ws.CLOSED) {
setLockChat(false);
}
}, [lockChat]);
function validateNode(n: NodeType): Array<string> { function validateNode(n: NodeType): Array<string> {
if (!n.data?.node?.template || !Object.keys(n.data.node.template)) { if (!n.data?.node?.template || !Object.keys(n.data.node.template)) {
setNoticeData({ setNoticeData({
@ -227,12 +264,11 @@ export default function ChatModal({
e.targetHandle.split("|")[2] === n.id e.targetHandle.split("|")[2] === n.id
) )
? [ ? [
`${type} is missing ${ `${type} is missing ${template.display_name
template.display_name ? template.display_name
? template.display_name : toNormalCase(template[t].name)
: toNormalCase(template[t].name) }.`,
}.`, ]
]
: [] : []
), ),
[] as string[] [] as string[]
@ -255,7 +291,6 @@ export default function ChatModal({
let message = chatValue; let message = chatValue;
setChatValue(""); setChatValue("");
addChatHistory(message, true); addChatHistory(message, true);
sendAll({ sendAll({
...reactFlowInstance.toObject(), ...reactFlowInstance.toObject(),
message, message,
@ -278,20 +313,14 @@ export default function ChatModal({
} }
function clearChat() { function clearChat() {
setChatHistory([]); setChatHistory([]);
ws.send(JSON.stringify({ clear_history: true })); ws.current.send(JSON.stringify({ clear_history: true }));
} }
const { closePopUp } = useContext(PopUpContext);
function setModalOpen(x: boolean) { function setModalOpen(x: boolean) {
setOpen(x); setOpen(x);
if (x === false) {
setTimeout(() => {
closePopUp();
}, 300);
}
} }
return ( return (
<Transition.Root show={open} appear={true} as={Fragment}> <Transition.Root show={open} appear={open} as={Fragment}>
<Dialog <Dialog
as="div" as="div"
className="relative z-10" className="relative z-10"
@ -321,28 +350,34 @@ export default function ChatModal({
leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
> >
<Dialog.Panel className=" drop-shadow-2xl relative flex flex-col justify-between transform h-[95%] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[690px]"> <Dialog.Panel className=" drop-shadow-2xl relative flex flex-col justify-between transform h-[95%] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all w-[690px]">
<div className="relative w-full"> <div className="relative w-full p-4">
<button <button
onClick={() => clearChat()} onClick={() => clearChat()}
className="absolute top-2 right-2 hover:text-red-500" className="absolute top-2 right-10 hover:text-red-500 text-gray-600 dark:text-gray-300 dark:hover:text-red-500 z-30"
> >
<FaEraser className="w-4 h-4" /> <FaEraser className="w-4 h-4" />
</button> </button>
<button
onClick={() => setModalOpen(false)}
className="absolute top-1.5 right-2 hover:text-red-500 text-gray-600 dark:text-gray-300 dark:hover:text-red-500 z-30"
>
<HiX className="w-5 h-5" />
</button>
</div> </div>
<div className="w-full h-full bg-white dark:bg-gray-800 border-t dark:border-t-gray-600 flex-col flex items-center overflow-scroll scrollbar-hide"> <div className="w-full h-full bg-white dark:bg-gray-800 border-t dark:border-t-gray-600 flex-col flex items-center overflow-scroll scrollbar-hide">
{chatHistory.length > 0 ? ( {chatHistory.length > 0 ? (
chatHistory.map((c, i) => <ChatMessage chat={c} key={i} />) chatHistory.map((c, i) => <ChatMessage lockChat={lockChat} chat={c} key={i} />)
) : ( ) : (
<div className="flex flex-col h-full text-center justify-center w-full items-center align-middle "> <div className="flex flex-col h-full text-center justify-center w-full items-center align-middle">
<span> <span>
👋{" "} 👋{" "}
<span className="text-gray-600 text-lg"> <span className="text-gray-600 dark:text-gray-300 text-lg">
LangFlow Chat LangFlow Chat
</span> </span>
</span> </span>
<br /> <br />
<div className="bg-gray-100 rounded-md w-2/4 px-6 py-8 border border-gray-200"> <div className="bg-gray-100 dark:bg-gray-900 rounded-md w-2/4 px-6 py-8 border border-gray-200 dark:border-gray-700">
<span className="text-base text-gray-500"> <span className="text-base text-gray-500">
Start a conversation and click the agent’s thoughts{" "} Start a conversation and click the agent’s thoughts{" "}
<span> <span>

View file

@ -7,7 +7,7 @@ import "ace-builds/src-noconflict/mode-python";
import "ace-builds/src-noconflict/theme-github"; import "ace-builds/src-noconflict/theme-github";
import "ace-builds/src-noconflict/theme-twilight"; import "ace-builds/src-noconflict/theme-twilight";
import "ace-builds/src-noconflict/ext-language_tools"; import "ace-builds/src-noconflict/ext-language_tools";
import "ace-builds/webpack-resolver"; // import "ace-builds/webpack-resolver";
import { darkContext } from "../../contexts/darkContext"; import { darkContext } from "../../contexts/darkContext";
import { checkCode } from "../../controllers/API"; import { checkCode } from "../../controllers/API";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
@ -65,9 +65,9 @@ export default function CodeAreaModal({
> >
<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]"> <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"> <div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
<button <button
type="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" className="rounded-md text-gray-400 hover:text-gray-500"
onClick={() => { onClick={() => {
setModalOpen(false); setModalOpen(false);
}} }}
@ -134,7 +134,7 @@ export default function CodeAreaModal({
title: "Code is ready to run", title: "Code is ready to run",
}); });
setModalOpen(false); setModalOpen(false);
setValue(code) setValue(code);
} else { } else {
if (funcErrors.length !== 0) { if (funcErrors.length !== 0) {
setErrorData({ setErrorData({
@ -142,7 +142,7 @@ export default function CodeAreaModal({
list: funcErrors, list: funcErrors,
}); });
} }
if(importsErrors.length!==0){ if (importsErrors.length !== 0) {
setErrorData({ setErrorData({
title: "There is an error in your imports", title: "There is an error in your imports",
list: importsErrors, list: importsErrors,

View file

@ -1,9 +1,9 @@
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import { import {
XMarkIcon, XMarkIcon,
ArrowDownTrayIcon, ArrowDownTrayIcon,
DocumentDuplicateIcon, DocumentDuplicateIcon,
ComputerDesktopIcon, ComputerDesktopIcon,
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
import { Fragment, useContext, useRef, useState } from "react"; import { Fragment, useContext, useRef, useState } from "react";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
@ -12,169 +12,187 @@ import { TabsContext } from "../../contexts/tabsContext";
import { removeApiKeys } from "../../utils"; import { removeApiKeys } from "../../utils";
export default function ExportModal() { export default function ExportModal() {
const [open, setOpen] = useState(true); const [open, setOpen] = useState(true);
const { closePopUp } = useContext(PopUpContext); const { closePopUp } = useContext(PopUpContext);
const ref = useRef(); const ref = useRef();
const { setErrorData } = useContext(alertContext); const { setErrorData } = useContext(alertContext);
const { flows, tabIndex, updateFlow, downloadFlow } = useContext(TabsContext); const { flows, tabIndex, updateFlow, downloadFlow,setDisableCP } = useContext(TabsContext);
function setModalOpen(x: boolean) { function setModalOpen(x: boolean) {
setOpen(x); setOpen(x);
if (x === false) { if (x === false) {
setTimeout(() => { setTimeout(() => {
closePopUp(); closePopUp();
}, 300); }, 300);
} }
} }
const [checked, setChecked] = useState(true); const [checked, setChecked] = useState(true);
const [name, setName] = useState(flows[tabIndex].name); const [name, setName] = useState(flows[tabIndex].name);
return ( return (
<Transition.Root show={open} appear={true} as={Fragment}> <Transition.Root show={open} appear={true} as={Fragment}>
<Dialog <Dialog
as="div" as="div"
className="relative z-10" className="relative z-10"
onClose={setModalOpen} onClose={setModalOpen}
initialFocus={ref} initialFocus={ref}
> >
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter="ease-out duration-300" enter="ease-out duration-300"
enterFrom="opacity-0" enterFrom="opacity-0"
enterTo="opacity-100" enterTo="opacity-100"
leave="ease-in duration-200" leave="ease-in duration-200"
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" /> <div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
</Transition.Child> </Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto"> <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"> <div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter="ease-out duration-300" enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100" enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200" leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" 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]"> <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"> <div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
<button <button
type="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" className="rounded-md text-gray-400 hover:text-gray-500"
onClick={() => { onClick={() => {
setModalOpen(false); setModalOpen(false);
}} }}
> >
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" /> <XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button> </button>
</div> </div>
<div className="h-full w-full flex flex-col justify-center items-center"> <div className="h-full w-full flex flex-col justify-center items-center">
<div className="flex w-full pb-4 z-10 justify-center shadow-sm"> <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"> <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">
<ArrowDownTrayIcon <ArrowDownTrayIcon
className="h-6 w-6 text-blue-600" className="h-6 w-6 text-blue-600"
aria-hidden="true" aria-hidden="true"
/> />
</div> </div>
<div className="mt-4 text-center sm:ml-4 sm:text-left"> <div className="mt-4 text-center sm:ml-4 sm:text-left">
<Dialog.Title <Dialog.Title
as="h3" as="h3"
className="text-lg font-medium dark:text-white leading-10 text-gray-900" className="text-lg font-medium dark:text-white leading-10 text-gray-900"
> >
Export as Export as
</Dialog.Title> </Dialog.Title>
</div> </div>
</div> </div>
<div className="pt-16 flex flex-col items-start justify-start h-full w-full bg-gray-200 dark:bg-gray-900 p-4 gap-16"> <div className="pt-16 flex flex-col items-start justify-start h-full w-full bg-gray-200 dark:bg-gray-900 p-4 gap-16">
<div className="w-full"> <div className="w-full">
<label <label
htmlFor="name" htmlFor="name"
className="block mb-2 font-medium text-gray-700 dark:text-white" className="block mb-2 font-medium text-gray-700 dark:text-white"
> >
Name Name
</label> </label>
<input <input
onChange={(event) => { onChange={(event) => {
if (event.target.value != "") { if (event.target.value != "") {
let newFlow = flows[tabIndex]; let newFlow = flows[tabIndex];
newFlow.name = event.target.value; newFlow.name = event.target.value;
setName(event.target.value); setName(event.target.value);
updateFlow(newFlow); updateFlow(newFlow);
} else { } else {
setName(event.target.value); setName(event.target.value);
} }
}} }}
type="text" type="text"
name="name" name="name"
value={name ?? null} value={name ?? null}
placeholder="File name" placeholder="File name"
id="name" id="name"
className="focus:border focus:border-blue block w-full px-3 py-2 border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500 text-gray-900 dark:text-gray-100" className="focus:border focus:border-blue block w-full px-3 py-2 border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500 text-gray-900 dark:text-gray-100"
/> onBlur={() => {
</div> setDisableCP(false);
<div className="w-full"> }}
<label onFocus={() => {
htmlFor="description" setDisableCP(true);
className="block mb-2 font-medium text-gray-700 dark:text-white" }}
> />
Description{" "} </div>
<span className="text-gray-400 text-sm"> <div className="w-full">
{" "} <label
(optional) htmlFor="description"
</span> className="block mb-2 font-medium text-gray-700 dark:text-white"
</label> >
<textarea Description{" "}
name="description" <span className="text-gray-400 text-sm">
id="description" {" "}
onChange={(event) => { (optional)
let newFlow = flows[tabIndex]; </span>
newFlow.description = event.target.value; </label>
updateFlow(newFlow); <textarea
}} onBlur={() => {
value={flows[tabIndex].description ?? null} setDisableCP(false);
placeholder="Flow description" }}
rows={3} onFocus={() => {
className=" focus:border focus:border-blue block w-full px-3 py-2 border-gray-300 text-gray-900 dark:text-gray-100 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500" setDisableCP(true);
></textarea> }}
</div> name="description"
id="description"
onChange={(event) => {
let newFlow = flows[tabIndex];
newFlow.description = event.target.value;
updateFlow(newFlow);
}}
value={flows[tabIndex].description ?? null}
placeholder="Flow description"
rows={3}
className=" focus:border focus:border-blue block w-full px-3 py-2 border-gray-300 text-gray-900 dark:text-gray-100 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500"
></textarea>
</div>
<div> <div>
<label htmlFor="checkbox" className="flex items-center"> <label htmlFor="checkbox" className="flex items-center">
<input <input
onChange={(event) => { onChange={(event) => {
setChecked(event.target.checked); setChecked(event.target.checked);
}} }}
checked={checked} checked={checked}
id="checkbox" id="checkbox"
type="checkbox" type="checkbox"
className="h-4 w-4 text-blue-600 border-gray-300 rounded dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500" className="h-4 w-4 text-blue-600 border-gray-300 rounded dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500"
/> onBlur={() => {
<span className="ml-2 font-medium text-gray-700 dark:text-white"> setDisableCP(false);
Save with my API keys }}
</span> onFocus={() => {
</label> setDisableCP(true);
</div> }}
<div className="w-full flex justify-end"> />
<button <span className="ml-2 font-medium text-gray-700 dark:text-white">
onClick={() => { Save with my API keys
if (checked) downloadFlow(flows[tabIndex]); </span>
else downloadFlow(removeApiKeys(flows[tabIndex])); </label>
}} </div>
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" <div className="w-full flex justify-end">
> <button
Download Flow onClick={() => {
</button> if (checked) downloadFlow(flows[tabIndex]);
</div> else downloadFlow(removeApiKeys(flows[tabIndex]));
</div> }}
</div> className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
</Dialog.Panel> >
</Transition.Child> Download Flow
</div> </button>
</div> </div>
</Dialog> </div>
</Transition.Root> </div>
); </Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
} }

View file

@ -29,93 +29,81 @@ export default function ButtonBox({
let padding: string; let padding: string;
let marginTop: string; let marginTop: string;
let height: string; let height: string;
let widht: string; let width: string;
switch (size) { switch (size) {
case "small": case "small":
bigCircle = "h-12 w-12"; bigCircle = "h-12 w-12";
smallCircle = "h-8 w-8"; smallCircle = "h-8 w-8";
titleFontSize = "text-sm"; titleFontSize = "text-sm";
descriptionFontSize = "text-xs"; descriptionFontSize = "text-xs";
padding = "p-2"; padding = "p-2 py-3";
marginTop = "mt-2"; marginTop = "mt-2";
height = "h-36"; height = "h-36";
widht = "w-32"; width = "w-32";
break; break;
case "medium": case "medium":
bigCircle = "h-16 w-16"; bigCircle = "h-16 w-16";
smallCircle = "h-12 w-12"; smallCircle = "h-12 w-12";
titleFontSize = "text-base"; titleFontSize = "text-base";
descriptionFontSize = "text-sm"; descriptionFontSize = "text-sm";
padding = "p-4"; padding = "p-4 py-5";
marginTop = "mt-3"; marginTop = "mt-3";
height = "h-44"; height = "h-44";
widht = "w-36"; width = "w-36";
break; break;
case "big": case "big":
bigCircle = "h-20 w-20"; bigCircle = "h-20 w-20";
smallCircle = "h-16 w-16"; smallCircle = "h-16 w-16";
titleFontSize = "text-lg"; titleFontSize = "text-lg";
descriptionFontSize = "text-sm"; descriptionFontSize = "text-sm";
padding = "p-8"; padding = "p-8 py-10";
marginTop = "mt-6"; marginTop = "mt-6";
height = "h-56"; height = "h-56";
widht = "w-44"; width = "w-44";
break; break;
default: default:
bigCircle = "h-20 w-20"; bigCircle = "h-20 w-20";
smallCircle = "h-16 w-16"; smallCircle = "h-16 w-16";
titleFontSize = "text-lg"; titleFontSize = "text-lg";
descriptionFontSize = "text-sm"; descriptionFontSize = "text-sm";
padding = "p-8"; padding = "p-8 py-10";
marginTop = "mt-6"; marginTop = "mt-6";
height = "h-56"; height = "h-56";
widht = "w-44"; width = "w-44";
break; break;
} }
return ( return (
<button disabled={deactivate} onClick={onClick}> <button disabled={deactivate} onClick={onClick}>
<Tooltip title={description} placement="bottom">
<div <div
className={classNames( className={classNames(
"col-span-1 flex flex-col divide-y divide-gray-200 rounded-lg text-center shadow border border-gray-300 hover:shadow-lg transform hover:scale-105", "flex flex-col justify-center items-center rounded-lg text-center shadow border border-gray-300 dark:border-gray-800 hover:shadow-lg transform hover:scale-105",
bgColor, bgColor,
height, height,
widht width,
padding
)} )}
> >
<div className={`flex flex-1 flex-col ${padding}`}>
<div <div
className={`mx-auto flex items-center justify-center ${bigCircle} bg-white/30 rounded-full`} className={`flex items-center justify-center ${bigCircle} bg-white/30 dark:bg-white/30 rounded-full`}
> >
<div <div
className={`mx-auto flex items-center justify-center ${smallCircle} bg-white rounded-full`} className={`flex items-center justify-center ${smallCircle} bg-white dark:bg-white/80 rounded-full`}
> >
<div className={textColor}>{icon}</div> <div className={textColor}>{icon}</div>
</div> </div>
</div> </div>
<div className="mt-auto mb-auto">
<h3 <h3
className={classNames( className={classNames(
"font-semibold text-white", "font-semibold text-white dark:text-white/80",
titleFontSize, titleFontSize,
marginTop marginTop
)} )}
> >
{title} {title}
</h3> </h3>
<div className="mt-1 flex flex-grow flex-col justify-between">
<dt className="sr-only">{title}</dt>
{/* <dd
className={classNames(
"text-gray-100 line-clamp-2",
descriptionFontSize
)}
>
{deactivate ? "Coming soon" : description}
</dd> */}
</div> </div>
</div>
</div> </div>
</Tooltip>
</button> </button>
); );
} }

View file

@ -1,11 +1,11 @@
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import { import {
XMarkIcon, XMarkIcon,
ArrowDownTrayIcon, ArrowDownTrayIcon,
DocumentDuplicateIcon, DocumentDuplicateIcon,
ComputerDesktopIcon, ComputerDesktopIcon,
ArrowUpTrayIcon, ArrowUpTrayIcon,
ArrowLeftIcon, ArrowLeftIcon,
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
import { Fragment, useContext, useRef, useState } from "react"; import { Fragment, useContext, useRef, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext"; import { PopUpContext } from "../../contexts/popUpContext";
@ -16,217 +16,217 @@ import { error } from "console";
import { alertContext } from "../../contexts/alertContext"; import { alertContext } from "../../contexts/alertContext";
import LoadingComponent from "../../components/loadingComponent"; import LoadingComponent from "../../components/loadingComponent";
import { FlowType } from "../../types/flow"; import { FlowType } from "../../types/flow";
import { classNames, toNormalCase } from "../../utils"; import { classNames, snakeToSpaces, toNormalCase } from "../../utils";
export default function ImportModal() { export default function ImportModal() {
const [open, setOpen] = useState(true); const [open, setOpen] = useState(true);
const { setErrorData } = useContext(alertContext); const { setErrorData } = useContext(alertContext);
const { closePopUp } = useContext(PopUpContext); const { closePopUp } = useContext(PopUpContext);
const ref = useRef(); const ref = useRef();
const [showExamples, setShowExamples] = useState(false); const [showExamples, setShowExamples] = useState(false);
const [loadingExamples, setLoadingExamples] = useState(false); const [loadingExamples, setLoadingExamples] = useState(false);
const [examples, setExamples] = useState<FlowType[]>([]); const [examples, setExamples] = useState<FlowType[]>([]);
const { uploadFlow, addFlow } = useContext(TabsContext); const { uploadFlow, addFlow } = useContext(TabsContext);
function setModalOpen(x: boolean) { function setModalOpen(x: boolean) {
setOpen(x); setOpen(x);
if (x === false) { if (x === false) {
setTimeout(() => { setTimeout(() => {
closePopUp(); closePopUp();
}, 300); }, 300);
} }
} }
function handleExamples() { function handleExamples() {
setLoadingExamples(true); setLoadingExamples(true);
getExamples() getExamples()
.then((result) => { .then((result) => {
setLoadingExamples(false); setLoadingExamples(false);
setExamples(result); setExamples(result);
}) })
.catch((error) => .catch((error) =>
setErrorData({ setErrorData({
title: "there was an error loading examples, please try again", title: "there was an error loading examples, please try again",
list: [error.message], list: [error.message],
}) })
); );
} }
return ( return (
<Transition.Root show={open} appear={true} as={Fragment}> <Transition.Root show={open} appear={true} as={Fragment}>
<Dialog <Dialog
as="div" as="div"
className="relative z-10" className="relative z-10"
onClose={setModalOpen} onClose={setModalOpen}
initialFocus={ref} initialFocus={ref}
> >
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter="ease-out duration-300" enter="ease-out duration-300"
enterFrom="opacity-0" enterFrom="opacity-0"
enterTo="opacity-100" enterTo="opacity-100"
leave="ease-in duration-200" leave="ease-in duration-200"
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0" leaveTo="opacity-0"
> >
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" /> <div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
</Transition.Child> </Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto"> <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"> <div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter="ease-out duration-300" enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100" enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200" leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" 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]"> <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-[776px]">
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block"> <div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
<button <button
type="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" className="rounded-md text-gray-400 hover:text-gray-500"
onClick={() => { onClick={() => {
setModalOpen(false); setModalOpen(false);
}} }}
> >
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" /> <XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button> </button>
</div> </div>
{showExamples && ( {showExamples && (
<> <>
<div className="z-50 absolute top-2 left-0 hidden pt-4 pl-4 sm:block"> <div className="z-50 absolute top-2 left-0 hidden pt-4 pl-4 sm:block">
<button <button
type="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" 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={() => { onClick={() => {
setShowExamples(false); setShowExamples(false);
}} }}
> >
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
<ArrowLeftIcon className="h-6 w-6" aria-hidden="true" /> <ArrowLeftIcon className="h-6 w-6" aria-hidden="true" />
</button> </button>
</div> </div>
<div className="z-50 absolute bottom-2 left-1/2 transform -translate-x-1/2 hidden pt-4 pl-2 sm:block text-center"> </>
<a )}
href="https://github.com/logspace-ai/langflow_examples" <div className="h-full w-full flex flex-col justify-center items-center">
target="_blank" <div className="flex w-full pb-4 z-10 justify-center shadow-sm">
className="flex items-center justify-center" <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">
rel="noreferrer" <ArrowUpTrayIcon
> className="h-6 w-6 text-blue-600"
<svg aria-hidden="true"
width="24" />
viewBox="0 0 98 96" </div>
xmlns="http://www.w3.org/2000/svg" <div className="mt-4 text-center sm:ml-4 sm:text-left">
> <Dialog.Title
<path as="h3"
fill-rule="evenodd" className="text-lg font-medium dark:text-white leading-10 text-gray-900"
clip-rule="evenodd" >
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" {showExamples ? "Select an example" : "Import from"}
fill="#24292f" </Dialog.Title>
/> </div>
</svg> </div>
<span className="ml-2">LangFlow Examples</span> <div
</a> className={classNames(
</div> "h-full w-full bg-gray-200 dark:bg-gray-900 gap-4 overflow-y-auto scrollbar-hide",
</> showExamples && !loadingExamples
)} ? "flex flex-row start justify-start items-start p-9 flex-wrap overflow-auto"
<div className="h-full w-full flex flex-col justify-center items-center"> : "flex flex-row justify-center items-center p-4"
<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"> >
<ArrowUpTrayIcon {!showExamples && (
className="h-6 w-6 text-blue-600" <div className="flex h-full w-full justify-evenly items-center">
aria-hidden="true" <ButtonBox
/> size="big"
</div> bgColor="bg-emerald-500 dark:bg-emerald-500/75"
<div className="mt-4 text-center sm:ml-4 sm:text-left"> description="Prebuilt Examples"
<Dialog.Title icon={
as="h3" <DocumentDuplicateIcon className="h-10 w-10 flex-shrink-0" />
className="text-lg font-medium dark:text-white leading-10 text-gray-900" }
> onClick={() => {
{showExamples ? "Select an example" : "Import from"} setShowExamples(true);
</Dialog.Title> handleExamples();
</div> }}
</div> textColor="text-emerald-500 dark:text-emerald-500/75"
<div title="Examples"
className={classNames( ></ButtonBox>
"h-full w-full bg-gray-200 dark:bg-gray-900 gap-4", <ButtonBox
showExamples && !loadingExamples size="big"
? "flex flex-row start justify-start items-start p-9 flex-wrap overflow-auto" bgColor="bg-blue-500 dark:bg-blue-500/75"
: "flex flex-row justify-center items-center p-4" description="Import from Local"
)} icon={
> <ComputerDesktopIcon className="h-10 w-10 flex-shrink-0" />
{!showExamples && ( }
<div className="flex h-full w-full justify-evenly items-center"> onClick={() => {
<ButtonBox uploadFlow();
size="big" setModalOpen(false);
bgColor="bg-emerald-500" }}
description="Prebuilt Examples" textColor="text-blue-500 dark:text-blue-500/75"
icon={ title="Local File"
<DocumentDuplicateIcon className="h-10 w-10 flex-shrink-0" /> ></ButtonBox>
} </div>
onClick={() => { )}
setShowExamples(true); {showExamples && loadingExamples && (
handleExamples(); <div className="flex align-middle justify-center items-center">
}} <LoadingComponent remSize={30} />
textColor="text-emerald-400" </div>
title="Examples" )}
></ButtonBox> {showExamples &&
<ButtonBox !loadingExamples &&
size="big" examples.map((example, index) => {
bgColor="bg-blue-500" return (
description="Import from Local" <div id="index">
icon={ {" "}
<ComputerDesktopIcon className="h-10 w-10 flex-shrink-0" /> <ButtonBox
} size="small"
onClick={() => { bgColor="bg-emerald-500 dark:bg-emerald-500/75"
uploadFlow(); description={
setModalOpen(false); example.description ?? "Prebuilt Examples"
}} }
textColor="text-blue-500" icon={
title="Local file" <DocumentDuplicateIcon className="h-6 w-6 flex-shrink-0" />
></ButtonBox> }
</div> onClick={() => {
)} addFlow(example);
{showExamples && loadingExamples && ( setModalOpen(false);
<div className="flex align-middle justify-center items-center"> }}
<LoadingComponent remSize={30} /> textColor="text-emerald-500 dark:text-emerald-500/75"
</div> title={example.name}
)} ></ButtonBox>
{showExamples && </div>
!loadingExamples && );
examples.map((example, index) => { })}
return ( </div>
<div id="index"> <div className="bg-white dark:bg-gray-800 w-full h-20 flex items-center justify-center">
{" "} <a
<ButtonBox href="https://github.com/logspace-ai/langflow_examples"
size="small" target="_blank"
bgColor="bg-emerald-500" className="flex items-center justify-center text-gray-600 dark:text-gray-300"
description={ rel="noreferrer"
example.description ?? "Prebuilt Examples" >
} <svg
icon={ width="24"
<DocumentDuplicateIcon className="h-6 w-6 flex-shrink-0" /> viewBox="0 0 98 96"
} xmlns="http://www.w3.org/2000/svg"
onClick={() => { >
addFlow(example); <path
setModalOpen(false); fill-rule="evenodd"
}} clip-rule="evenodd"
textColor="text-emerald-400" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
title={toNormalCase(example.name)} fill="currentColor"
></ButtonBox> />
</div> </svg>
); <span className="ml-2 ">LangFlow Examples</span>
})} </a>
</div> </div>
</div> </div>
</Dialog.Panel> </Dialog.Panel>
</Transition.Child> </Transition.Child>
</div> </div>
</div> </div>
</Dialog> </Dialog>
</Transition.Root> </Transition.Root>
); );
} }

View file

@ -59,9 +59,9 @@ export default function PromptAreaModal({
> >
<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]"> <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"> <div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
<button <button
type="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" className="rounded-md text-gray-400 hover:text-gray-500"
onClick={() => { onClick={() => {
setModalOpen(false); setModalOpen(false);
}} }}
@ -135,7 +135,8 @@ export default function PromptAreaModal({
return setErrorData({ return setErrorData({
title: title:
"There is something wrong with this prompt, please review it", "There is something wrong with this prompt, please review it",
list:[error.response.data.detail]}); list: [error.response.data.detail],
});
}); });
}} }}
> >

View file

@ -1,104 +1,123 @@
import { Dialog, Transition } from "@headlessui/react"; import { Dialog, Transition } from "@headlessui/react";
import { XMarkIcon, ClipboardDocumentListIcon } from "@heroicons/react/24/outline"; import {
XMarkIcon,
ClipboardDocumentListIcon,
} from "@heroicons/react/24/outline";
import { Fragment, useContext, useRef, useState } from "react"; import { Fragment, useContext, useRef, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext"; import { PopUpContext } from "../../contexts/popUpContext";
export default function TextAreaModal({value, setValue}:{setValue:(value:string)=>void,value:string|string[]}){ export default function TextAreaModal({
const [open, setOpen] = useState(true); value,
const [myValue, setMyValue] = useState(value); setValue,
const { closePopUp } = useContext(PopUpContext); }: {
const ref = useRef(); setValue: (value: string) => void;
function setModalOpen(x:boolean){ value: string | string[];
setOpen(x); }) {
if(x === false){ const [open, setOpen] = useState(true);
setTimeout(() => {closePopUp()}, 300); const [myValue, setMyValue] = useState(value);
} const { closePopUp } = useContext(PopUpContext);
} const ref = useRef();
return ( function setModalOpen(x: boolean) {
<Transition.Root show={open} appear={true} as={Fragment}> setOpen(x);
<Dialog if (x === false) {
as="div" setTimeout(() => {
className="relative z-10" closePopUp();
onClose={setModalOpen} }, 300);
initialFocus={ref} }
> }
<Transition.Child return (
as={Fragment} <Transition.Root show={open} appear={true} as={Fragment}>
enter="ease-out duration-300" <Dialog
enterFrom="opacity-0" as="div"
enterTo="opacity-100" className="relative z-10"
leave="ease-in duration-200" onClose={setModalOpen}
leaveFrom="opacity-100" initialFocus={ref}
leaveTo="opacity-0" >
> <Transition.Child
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" /> as={Fragment}
</Transition.Child> 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="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"> <div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
enter="ease-out duration-300" enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100" enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200" leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" 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]"> <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"> <div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
<button <button
type="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" className="rounded-md text-gray-400 hover:text-gray-500"
onClick={() => { onClick={() => {
setModalOpen(false); setModalOpen(false);
}} }}
> >
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" /> <XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button> </button>
</div> </div>
<div className="h-full w-full flex flex-col justify-center items-center"> <div className="h-full w-full flex flex-col justify-center items-center">
<div className="flex w-full pb-4 z-10 justify-center shadow-sm"> <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"> <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 <ClipboardDocumentListIcon
className="h-6 w-6 text-blue-600" className="h-6 w-6 text-blue-600"
aria-hidden="true" aria-hidden="true"
/> />
</div> </div>
<div className="mt-4 text-center sm:ml-4 sm:text-left"> <div className="mt-4 text-center sm:ml-4 sm:text-left">
<Dialog.Title <Dialog.Title
as="h3" as="h3"
className="text-lg font-medium dark:text-white leading-10 text-gray-900" className="text-lg font-medium dark:text-white leading-10 text-gray-900"
> >
Edit text Edit text
</Dialog.Title> </Dialog.Title>
</div> </div>
</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="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="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"> <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)}}/> <textarea
</div> ref={ref}
</div> className="form-input h-full w-full rounded-lg border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-white"
</div> value={myValue}
<div className="bg-gray-200 dark:bg-gray-900 w-full pb-3 flex flex-row-reverse px-4"> onChange={(e) => {
<button setMyValue(e.target.value);
type="button" setValue(e.target.value);
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); </div>
}} </div>
> </div>
Finish editing <div className="bg-gray-200 dark:bg-gray-900 w-full pb-3 flex flex-row-reverse px-4">
</button> <button
</div> type="button"
</div> 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"
</Dialog.Panel> onClick={() => {
</Transition.Child> setModalOpen(false);
</div> }}
</div> >
</Dialog> Finish editing
</Transition.Root> </button>
) </div>
} </div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
}

View file

@ -1,14 +1,12 @@
import { ConnectionLineComponentProps } from 'reactflow'; import { ConnectionLineComponentProps } from "reactflow";
const ConnectionLineComponent = ({ const ConnectionLineComponent = ({
fromX, fromX,
fromY, fromY,
toX, toX,
toY, toY,
connectionLineStyle = {} // provide a default value for connectionLineStyle connectionLineStyle = {}, // provide a default value for connectionLineStyle
}:ConnectionLineComponentProps) => { }: ConnectionLineComponentProps) => {
return ( return (
<g> <g>
<path <path

View file

@ -1,23 +1,20 @@
import { import { ChevronRightIcon } from "@heroicons/react/24/solid";
ChevronRightIcon,
} from "@heroicons/react/24/solid";
import { Disclosure } from "@headlessui/react"; import { Disclosure } from "@headlessui/react";
import { DisclosureComponentType } from "../../../../types/components"; import { DisclosureComponentType } from "../../../../types/components";
export default function DisclosureComponent({ export default function DisclosureComponent({
button: { title, Icon, buttons = [] }, button: { title, Icon, buttons = [] },
children, children,
}: DisclosureComponentType }: DisclosureComponentType) {
) {
return ( return (
<Disclosure as="div" key={title}> <Disclosure as="div" key={title}>
{({ open }) => ( {({ open }) => (
<> <>
<div> <div>
<Disclosure.Button 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"> <Disclosure.Button className="select-none bg-gray-50 dark:bg-gray-700/60 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"> <div className="flex gap-4">
<Icon className="w-6 text-gray-800 dark:text-white" /> <Icon className="w-6 text-gray-800 dark:text-white/80" />
<span className="flex items-center text-sm text-gray-800 dark:text-white font-medium"> <span className="flex items-center text-sm text-gray-800 dark:text-white/80 font-medium">
{title} {title}
</span> </span>
</div> </div>

View file

@ -3,13 +3,10 @@ import DisclosureComponent from "../DisclosureComponent";
import { nodeColors, nodeIcons, nodeNames } from "../../../../utils"; import { nodeColors, nodeIcons, nodeNames } from "../../../../utils";
import { useContext, useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { typesContext } from "../../../../contexts/typesContext"; import { typesContext } from "../../../../contexts/typesContext";
import { import { APIClassType, APIObjectType } from "../../../../types/api";
APIClassType,
APIObjectType,
} from "../../../../types/api";
export default function ExtraSidebar() { export default function ExtraSidebar() {
const {data} = useContext(typesContext) const { data } = useContext(typesContext);
function onDragStart( function onDragStart(
event: React.DragEvent<any>, event: React.DragEvent<any>,
@ -22,45 +19,49 @@ export default function ExtraSidebar() {
return ( return (
<div className="mt-1 w-full"> <div className="mt-1 w-full">
{Object.keys(data).sort().map((d: keyof APIObjectType, i) => ( {Object.keys(data)
<DisclosureComponent .sort()
key={i} .map((d: keyof APIObjectType, i) => (
button={{ <DisclosureComponent
title: nodeNames[d] ?? nodeNames.unknown, key={i}
Icon: nodeIcons[d] ?? nodeIcons.unknown, button={{
}} title: nodeNames[d] ?? nodeNames.unknown,
> Icon: nodeIcons[d] ?? nodeIcons.unknown,
<div className="p-2 flex flex-col gap-2"> }}
{Object.keys(data[d]).sort().map((t: string, k) => ( >
<div key={k}> <div className="p-2 flex flex-col gap-2">
<div {Object.keys(data[d])
draggable .sort()
className={" cursor-grab border-l-8 rounded-l-md"} .map((t: string, k) => (
style={{ <div key={k}>
borderLeftColor: nodeColors[d] ?? nodeColors.unknown, <div
}} draggable
onDragStart={(event) => className={" cursor-grab border-l-8 rounded-l-md"}
onDragStart(event, { style={{
type: t, borderLeftColor: nodeColors[d] ?? nodeColors.unknown,
node: data[d][t], }}
}) onDragStart={(event) =>
} onDragStart(event, {
> type: 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"> node: data[d][t],
<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 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>
</div> ))}
</div> {Object.keys(data[d]).length === 0 && (
))} <div className="text-gray-400 text-center">Coming soon</div>
{Object.keys(data[d]).length === 0 && ( )}
<div className="text-gray-400 text-center">Coming soon</div> </div>
)} </DisclosureComponent>
</div> ))}
</DisclosureComponent>
))}
</div> </div>
); );
} }

View file

@ -3,11 +3,18 @@ import { useContext, useState } from "react";
import { TabsContext } from "../../../../contexts/tabsContext"; import { TabsContext } from "../../../../contexts/tabsContext";
import { FlowType } from "../../../../types/flow"; import { FlowType } from "../../../../types/flow";
var _ = require("lodash"); import _ from "lodash";
export default function TabComponent({ selected, flow, onClick }:{flow:FlowType,selected:boolean,onClick:()=>void}) { export default function TabComponent({
const { removeFlow, updateFlow, flows } = selected,
useContext(TabsContext); flow,
onClick,
}: {
flow: FlowType;
selected: boolean;
onClick: () => void;
}) {
const { removeFlow, updateFlow, flows, setDisableCP } = useContext(TabsContext);
const [isRename, setIsRename] = useState(false); const [isRename, setIsRename] = useState(false);
const [value, setValue] = useState(""); const [value, setValue] = useState("");
return ( return (
@ -19,7 +26,7 @@ export default function TabComponent({ selected, flow, onClick }:{flow:FlowType,
onClick={onClick} onClick={onClick}
> >
<span className="w-32 truncate text-left">{flow.name}</span> <span className="w-32 truncate text-left">{flow.name}</span>
<button <button
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
@ -30,14 +37,18 @@ export default function TabComponent({ selected, flow, onClick }:{flow:FlowType,
</button> </button>
</div> </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"> <div className="bg-white dark:text-white dark:bg-gray-700/60 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 ? ( {isRename ? (
<input <input
onFocus={() => {
setDisableCP(true);
}}
autoFocus autoFocus
className="bg-transparent focus:border-none active:outline hover:outline focus:outline outline-gray-300 rounded-md w-28" className="bg-transparent focus:border-none active:outline hover:outline focus:outline outline-gray-300 rounded-md w-28"
onBlur={() => { onBlur={() => {
setIsRename(false); setIsRename(false);
if (value !== "") { if (value !== "") {
setDisableCP(false);
let newFlow = _.cloneDeep(flow); let newFlow = _.cloneDeep(flow);
newFlow.name = value; newFlow.name = value;
updateFlow(newFlow); updateFlow(newFlow);
@ -85,4 +96,3 @@ export default function TabComponent({ selected, flow, onClick }:{flow:FlowType,
</> </>
); );
} }

View file

@ -8,6 +8,7 @@ import {
ArrowDownTrayIcon, ArrowDownTrayIcon,
ArrowUpTrayIcon, ArrowUpTrayIcon,
BellIcon, BellIcon,
CodeBracketSquareIcon,
MoonIcon, MoonIcon,
SunIcon, SunIcon,
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
@ -17,13 +18,14 @@ import { alertContext } from "../../../../contexts/alertContext";
import ImportModal from "../../../../modals/importModal"; import ImportModal from "../../../../modals/importModal";
import ExportModal from "../../../../modals/exportModal"; import ExportModal from "../../../../modals/exportModal";
import { typesContext } from "../../../../contexts/typesContext"; import { typesContext } from "../../../../contexts/typesContext";
import ApiModal from "../../../../modals/ApiModal";
export default function TabsManagerComponent() { export default function TabsManagerComponent() {
const { flows, addFlow, tabIndex, setTabIndex, uploadFlow, downloadFlow } = const { flows, addFlow, tabIndex, setTabIndex, uploadFlow, downloadFlow } =
useContext(TabsContext); useContext(TabsContext);
const { openPopUp } = useContext(PopUpContext); const { openPopUp } = useContext(PopUpContext);
const { templates } = useContext(typesContext); const { templates } = useContext(typesContext);
const AlertWidth = 256; const AlertWidth = 384;
const { dark, setDark } = useContext(darkContext); const { dark, setDark } = useContext(darkContext);
const { notificationCenter, setNotificationCenter } = const { notificationCenter, setNotificationCenter } =
useContext(alertContext); useContext(alertContext);
@ -55,20 +57,26 @@ export default function TabsManagerComponent() {
flow={null} flow={null}
/> />
<div className="ml-auto mr-2 flex gap-3"> <div className="ml-auto mr-2 flex gap-3">
<button
onClick={() => openPopUp(<ApiModal flowName={flows[tabIndex].name} />)}
className="flex items-center gap-1 pr-2 border-gray-400 border-r text-sm text-gray-600 hover:text-gray-500 dark:text-gray-300 dark:hover:text-gray-200"
>
Code <CodeBracketSquareIcon className="w-5 h-5" />
</button>
<button <button
onClick={() => openPopUp(<ImportModal />)} onClick={() => openPopUp(<ImportModal />)}
className="flex items-center gap-1 pr-2 border-gray-400 border-r text-sm text-gray-600 hover:text-gray-500" className="flex items-center gap-1 pr-2 border-gray-400 border-r text-sm text-gray-600 hover:text-gray-500 dark:text-gray-300 dark:hover:text-gray-200"
> >
Import <ArrowUpTrayIcon className="w-5 h-5" /> Import <ArrowUpTrayIcon className="w-5 h-5" />
</button> </button>
<button <button
onClick={() => openPopUp(<ExportModal />)} onClick={() => openPopUp(<ExportModal />)}
className="flex items-center gap-1 mr-2 text-sm text-gray-600 hover:text-gray-500" className="flex items-center gap-1 pr-2 text-sm text-gray-600 border-gray-400 border-r hover:text-gray-500 dark:text-gray-300 dark:hover:text-gray-200"
> >
Export <ArrowDownTrayIcon className="h-5 w-5" /> Export <ArrowDownTrayIcon className="h-5 w-5" />
</button> </button>
<button <button
className="text-gray-600 hover:text-gray-500 " className="text-gray-600 hover:text-gray-500 dark:text-gray-300 dark:hover:text-gray-200"
onClick={() => { onClick={() => {
setDark(!dark); setDark(!dark);
}} }}
@ -80,7 +88,7 @@ export default function TabsManagerComponent() {
)} )}
</button> </button>
<button <button
className="text-gray-600 hover:text-gray-500 relative" className="text-gray-600 hover:text-gray-500 dark:text-gray-300 dark:hover:text-gray-200 relative"
onClick={(event: React.MouseEvent<HTMLElement>) => { onClick={(event: React.MouseEvent<HTMLElement>) => {
setNotificationCenter(false); setNotificationCenter(false);
const top = (event.target as Element).getBoundingClientRect().top; const top = (event.target as Element).getBoundingClientRect().top;
@ -90,13 +98,11 @@ export default function TabsManagerComponent() {
<> <>
<div <div
className="z-10 absolute" className="z-10 absolute"
style={{ top: top + 20, left: left - AlertWidth }} style={{ top: top + 34, left: left - AlertWidth }}
> >
<AlertDropdown /> <AlertDropdown />
</div> </div>
<div className="h-screen w-screen fixed top-0 left-0"> <div className="h-screen w-screen fixed top-0 left-0"></div>
</div>
</> </>
); );
}} }}

View file

@ -1,22 +1,23 @@
import { useCallback, useContext, useEffect, useRef, useState } from "react"; import { useCallback, useContext, useEffect, useRef, useState } from "react";
import ReactFlow, { import ReactFlow, {
Background, Background,
Controls, Controls,
addEdge, addEdge,
useEdgesState, useEdgesState,
useNodesState, useNodesState,
useReactFlow, useReactFlow,
updateEdge, updateEdge,
EdgeChange, EdgeChange,
Connection, Connection,
Edge, Edge,
useKeyPress, useKeyPress,
useOnSelectionChange, useOnSelectionChange,
NodeDragHandler, NodeDragHandler,
OnEdgesDelete, OnEdgesDelete,
OnNodesDelete, OnNodesDelete,
SelectionDragHandler, SelectionDragHandler,
} from "reactflow"; } from "reactflow";
import _ from "lodash";
import { locationContext } from "../../contexts/locationContext"; import { locationContext } from "../../contexts/locationContext";
import ExtraSidebar from "./components/extraSidebarComponent"; import ExtraSidebar from "./components/extraSidebarComponent";
import Chat from "../../components/chatComponent"; import Chat from "../../components/chatComponent";
@ -31,270 +32,269 @@ import { isValidConnection } from "../../utils";
import useUndoRedo from "./hooks/useUndoRedo"; import useUndoRedo from "./hooks/useUndoRedo";
const nodeTypes = { const nodeTypes = {
genericNode: GenericNode, genericNode: GenericNode,
}; };
var _ = require("lodash");
export default function FlowPage({ flow }:{flow:FlowType}) { export default function FlowPage({ flow }: { flow: FlowType }) {
let { updateFlow, incrementNodeId} = let { updateFlow, incrementNodeId, disableCP} =
useContext(TabsContext); useContext(TabsContext);
const { types, reactFlowInstance, setReactFlowInstance, templates } = const { types, reactFlowInstance, setReactFlowInstance, templates } =
useContext(typesContext); useContext(typesContext);
const reactFlowWrapper = useRef(null); const reactFlowWrapper = useRef(null);
const { undo, redo, canUndo, canRedo, takeSnapshot } = useUndoRedo(); const { undo, redo, canUndo, canRedo, takeSnapshot } = useUndoRedo();
const onKeyDown = (event : React.KeyboardEvent<HTMLDivElement>) => { const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
if ((event.ctrlKey || event.metaKey) && (event.key === 'c') && lastSelection){ if ((event.ctrlKey || event.metaKey) && (event.key === 'c') && lastSelection && !disableCP) {
event.preventDefault(); event.preventDefault();
setLastCopiedSelection(lastSelection); setLastCopiedSelection(lastSelection);
} }
if ((event.ctrlKey || event.metaKey) && (event.key === 'v') && lastCopiedSelection){ if ((event.ctrlKey || event.metaKey) && (event.key === 'v') && lastCopiedSelection && !disableCP) {
event.preventDefault(); event.preventDefault();
paste(); paste();
} }
} }
const [lastSelection, setLastSelection] = useState(null); const [lastSelection, setLastSelection] = useState(null);
const [lastCopiedSelection, setLastCopiedSelection] = useState(null); const [lastCopiedSelection, setLastCopiedSelection] = useState(null);
const [position, setPosition] = useState({ x: 0, y: 0 }); const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = (event) => { const handleMouseMove = (event) => {
setPosition({ x: event.clientX, y: event.clientY }); setPosition({ x: event.clientX, y: event.clientY });
}; };
useOnSelectionChange({ useOnSelectionChange({
onChange: (flow) => {setLastSelection(flow);}, onChange: (flow) => { setLastSelection(flow); },
}) })
let paste = () => { let paste = () => {
let minimumX = Infinity; let minimumX = Infinity;
let minimumY = Infinity; let minimumY = Infinity;
let idsMap = {}; let idsMap = {};
lastCopiedSelection.nodes.forEach((n) => { lastCopiedSelection.nodes.forEach((n) => {
if(n.position.y < minimumY){ if (n.position.y < minimumY) {
minimumY = n.position.y minimumY = n.position.y
} }
if(n.position.x < minimumX){ if (n.position.x < minimumX) {
minimumX = n.position.x; minimumX = n.position.x;
} }
}); });
const bounds = reactFlowWrapper.current.getBoundingClientRect(); const bounds = reactFlowWrapper.current.getBoundingClientRect();
const insidePosition = reactFlowInstance.project({ const insidePosition = reactFlowInstance.project({
x: position.x - bounds.left, x: position.x - bounds.left,
y: position.y - bounds.top y: position.y - bounds.top
}); });
lastCopiedSelection.nodes.forEach((n) => { lastCopiedSelection.nodes.forEach((n) => {
// Generate a unique node ID // Generate a unique node ID
let newId = getId(); let newId = getId();
idsMap[n.id] = newId; idsMap[n.id] = newId;
// Create a new node object // Create a new node object
const newNode: NodeType = { const newNode: NodeType = {
id: newId, id: newId,
type: "genericNode", type: "genericNode",
position: { position: {
x: insidePosition.x + n.position.x - minimumX, x: insidePosition.x + n.position.x - minimumX,
y: insidePosition.y + n.position.y - minimumY, y: insidePosition.y + n.position.y - minimumY,
}, },
data: { data: {
...n.data, ...n.data,
id: newId, id: newId,
}, },
}; };
// Add the new node to the list of nodes in state // Add the new node to the list of nodes in state
setNodes((nds) => nds.map((e) => ({...e, selected: false})).concat({...newNode, selected: false})); setNodes((nds) => nds.map((e) => ({ ...e, selected: false })).concat({ ...newNode, selected: false }));
}) })
lastCopiedSelection.edges.forEach((e) => { lastCopiedSelection.edges.forEach((e) => {
let source = idsMap[e.source]; let source = idsMap[e.source];
let target = idsMap[e.target]; let target = idsMap[e.target];
let sourceHandleSplitted = e.sourceHandle.split('|'); let sourceHandleSplitted = e.sourceHandle.split('|');
let sourceHandle = sourceHandleSplitted[0] + '|' + source + '|' + sourceHandleSplitted.slice(2).join('|'); let sourceHandle = sourceHandleSplitted[0] + '|' + source + '|' + sourceHandleSplitted.slice(2).join('|');
let targetHandleSplitted = e.targetHandle.split('|'); let targetHandleSplitted = e.targetHandle.split('|');
let targetHandle = targetHandleSplitted.slice(0, -1).join('|') + '|' + target; let targetHandle = targetHandleSplitted.slice(0, -1).join('|') + '|' + target;
let id = "reactflow__edge-" + source + sourceHandle + "-" + target + targetHandle; let id = "reactflow__edge-" + source + sourceHandle + "-" + target + targetHandle;
setEdges((eds) => setEdges((eds) =>
addEdge({ source, target, sourceHandle, targetHandle, id, className: "animate-pulse", selected: false }, eds.map((e) => ({...e, selected: false}))) addEdge({ source, target, sourceHandle, targetHandle, id, className: "animate-pulse", selected: false }, eds.map((e) => ({ ...e, selected: false })))
); );
}) })
} }
const { setExtraComponent, setExtraNavigation } = useContext(locationContext); const { setExtraComponent, setExtraNavigation } = useContext(locationContext);
const { setErrorData } = useContext(alertContext); const { setErrorData } = useContext(alertContext);
const [nodes, setNodes, onNodesChange] = useNodesState( const [nodes, setNodes, onNodesChange] = useNodesState(
flow.data?.nodes ?? [] flow.data?.nodes ?? []
); );
const [edges, setEdges, onEdgesChange] = useEdgesState( const [edges, setEdges, onEdgesChange] = useEdgesState(
flow.data?.edges ?? [] flow.data?.edges ?? []
); );
const { setViewport } = useReactFlow(); const { setViewport } = useReactFlow();
const edgeUpdateSuccessful = useRef(true); const edgeUpdateSuccessful = useRef(true);
function getId() { function getId() {
return `dndnode_` + incrementNodeId(); return `dndnode_` + incrementNodeId();
} }
useEffect(() => { useEffect(() => {
if (reactFlowInstance && flow) { if (reactFlowInstance && flow) {
flow.data = reactFlowInstance.toObject(); flow.data = reactFlowInstance.toObject();
updateFlow(flow); updateFlow(flow);
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [nodes, edges]); }, [nodes, edges]);
//update flow when tabs change //update flow when tabs change
useEffect(() => { useEffect(() => {
setNodes(flow?.data?.nodes ?? []); setNodes(flow?.data?.nodes ?? []);
setEdges(flow?.data?.edges ?? []); setEdges(flow?.data?.edges ?? []);
if (reactFlowInstance) { if (reactFlowInstance) {
setViewport(flow?.data?.viewport ?? { x: 1, y: 0, zoom: 0.5 }); setViewport(flow?.data?.viewport ?? { x: 1, y: 0, zoom: 0.5 });
} }
}, [flow, reactFlowInstance, setEdges, setNodes, setViewport]); }, [flow, reactFlowInstance, setEdges, setNodes, setViewport]);
//set extra sidebar //set extra sidebar
useEffect(() => { useEffect(() => {
setExtraComponent(<ExtraSidebar />); setExtraComponent(<ExtraSidebar />);
setExtraNavigation({ title: "Components" }); setExtraNavigation({ title: "Components" });
}, [setExtraComponent, setExtraNavigation]); }, [setExtraComponent, setExtraNavigation]);
const onEdgesChangeMod = useCallback( const onEdgesChangeMod = useCallback(
(s: EdgeChange[]) => { (s: EdgeChange[]) => {
onEdgesChange(s); onEdgesChange(s);
setNodes((x) => { setNodes((x) => {
let newX = _.cloneDeep(x); let newX = _.cloneDeep(x);
return newX; return newX;
}); });
}, },
[onEdgesChange, setNodes] [onEdgesChange, setNodes]
); );
const onConnect = useCallback( const onConnect = useCallback(
(params: Connection) => { (params: Connection) => {
takeSnapshot(); takeSnapshot();
setEdges((eds) => setEdges((eds) =>
addEdge({ ...params, className: "animate-pulse" }, eds) addEdge({ ...params, className: "animate-pulse" }, eds)
); );
setNodes((x) => { setNodes((x) => {
let newX = _.cloneDeep(x); let newX = _.cloneDeep(x);
return newX; return newX;
}); });
}, },
[setEdges, setNodes, takeSnapshot] [setEdges, setNodes, takeSnapshot]
); );
const onNodeDragStart: NodeDragHandler = useCallback(() => { const onNodeDragStart: NodeDragHandler = useCallback(() => {
// 👇 make dragging a node undoable // 👇 make dragging a node undoable
takeSnapshot(); takeSnapshot();
// 👉 you can place your event handlers here // 👉 you can place your event handlers here
}, [takeSnapshot]); }, [takeSnapshot]);
const onSelectionDragStart: SelectionDragHandler = useCallback(() => { const onSelectionDragStart: SelectionDragHandler = useCallback(() => {
// 👇 make dragging a selection undoable // 👇 make dragging a selection undoable
takeSnapshot(); takeSnapshot();
}, [takeSnapshot]); }, [takeSnapshot]);
const onEdgesDelete: OnEdgesDelete = useCallback(() => { const onEdgesDelete: OnEdgesDelete = useCallback(() => {
// 👇 make deleting edges undoable // 👇 make deleting edges undoable
takeSnapshot(); takeSnapshot();
}, [takeSnapshot]); }, [takeSnapshot]);
const onDragOver = useCallback((event: React.DragEvent) => { const onDragOver = useCallback((event: React.DragEvent) => {
event.preventDefault(); event.preventDefault();
event.dataTransfer.dropEffect = "move"; event.dataTransfer.dropEffect = "move";
}, []); }, []);
const onDrop = useCallback( const onDrop = useCallback(
(event: React.DragEvent) => { (event: React.DragEvent) => {
event.preventDefault(); event.preventDefault();
takeSnapshot(); takeSnapshot();
// Get the current bounds of the ReactFlow wrapper element // Get the current bounds of the ReactFlow wrapper element
const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect(); const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect();
// Extract the data from the drag event and parse it as a JSON object // Extract the data from the drag event and parse it as a JSON object
let data: { type: string; node?: APIClassType } = JSON.parse( let data: { type: string; node?: APIClassType } = JSON.parse(
event.dataTransfer.getData("json") 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 is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node
if ( if (
data.type !== "chatInput" || data.type !== "chatInput" ||
(data.type === "chatInput" && (data.type === "chatInput" &&
!reactFlowInstance.getNodes().some((n) => n.type === "chatInputNode")) !reactFlowInstance.getNodes().some((n) => n.type === "chatInputNode"))
) { ) {
// Calculate the position where the node should be created // Calculate the position where the node should be created
const position = reactFlowInstance.project({ const position = reactFlowInstance.project({
x: event.clientX - reactflowBounds.left, x: event.clientX - reactflowBounds.left,
y: event.clientY - reactflowBounds.top, y: event.clientY - reactflowBounds.top,
}); });
// Generate a unique node ID // Generate a unique node ID
let newId = getId(); let newId = getId();
// Create a new node object // Create a new node object
const newNode: NodeType = { const newNode: NodeType = {
id: newId, id: newId,
type: "genericNode", type: "genericNode",
position, position,
data: { data: {
...data, ...data,
id: newId, id: newId,
value: null, value: null,
}, },
}; };
// Add the new node to the list of nodes in state // Add the new node to the list of nodes in state
setNodes((nds) => nds.concat(newNode)); setNodes((nds) => nds.concat(newNode));
} else { } else {
// If a chat input node already exists, set an error message // If a chat input node already exists, set an error message
setErrorData({ setErrorData({
title: "Error creating node", title: "Error creating node",
list: ["There can't be more than one chat input."], list: ["There can't be more than one chat input."],
}); });
} }
}, },
// Specify dependencies for useCallback // Specify dependencies for useCallback
[incrementNodeId, reactFlowInstance, setErrorData, setNodes, takeSnapshot] [incrementNodeId, reactFlowInstance, setErrorData, setNodes, takeSnapshot]
); );
const onDelete = useCallback((mynodes) => { const onDelete = useCallback((mynodes) => {
takeSnapshot(); takeSnapshot();
setEdges( setEdges(
edges.filter( edges.filter(
(ns) => !mynodes.some((n) => ns.source === n.id || ns.target === n.id) (ns) => !mynodes.some((n) => ns.source === n.id || ns.target === n.id)
) )
); );
}, [takeSnapshot, edges, setEdges]); }, [takeSnapshot, edges, setEdges]);
const onEdgeUpdateStart = useCallback(() => { const onEdgeUpdateStart = useCallback(() => {
edgeUpdateSuccessful.current = false; edgeUpdateSuccessful.current = false;
}, []); }, []);
const onEdgeUpdate = useCallback( const onEdgeUpdate = useCallback(
(oldEdge: Edge, newConnection: Connection) => { (oldEdge: Edge, newConnection: Connection) => {
if (isValidConnection(newConnection, reactFlowInstance)) { if (isValidConnection(newConnection, reactFlowInstance)) {
edgeUpdateSuccessful.current = true; edgeUpdateSuccessful.current = true;
setEdges((els) => updateEdge(oldEdge, newConnection, els)); setEdges((els) => updateEdge(oldEdge, newConnection, els));
} }
}, },
[] []
); );
const onEdgeUpdateEnd = useCallback((_, edge) => { const onEdgeUpdateEnd = useCallback((_, edge) => {
if (!edgeUpdateSuccessful.current) { if (!edgeUpdateSuccessful.current) {
setEdges((eds) => eds.filter((e) => e.id !== edge.id)); setEdges((eds) => eds.filter((e) => e.id !== edge.id));
} }
edgeUpdateSuccessful.current = true; edgeUpdateSuccessful.current = true;
}, []); }, []);
return ( return (
<div className="w-full h-full" onMouseMove={handleMouseMove} ref={reactFlowWrapper}> <div className="w-full h-full" onMouseMove={handleMouseMove} ref={reactFlowWrapper}>
{Object.keys(templates).length > 0 && Object.keys(types).length > 0 ? ( {Object.keys(templates).length > 0 && Object.keys(types).length > 0 ? (
@ -307,7 +307,7 @@ export default function FlowPage({ flow }:{flow:FlowType}) {
edges={edges} edges={edges}
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
onEdgesChange={onEdgesChangeMod} onEdgesChange={onEdgesChangeMod}
onKeyDown={(e) => onKeyDown(e)} onKeyDown={(e) => onKeyDown(e)}
onConnect={onConnect} onConnect={onConnect}
onLoad={setReactFlowInstance} onLoad={setReactFlowInstance}
onInit={setReactFlowInstance} onInit={setReactFlowInstance}
@ -315,16 +315,16 @@ export default function FlowPage({ flow }:{flow:FlowType}) {
onEdgeUpdate={onEdgeUpdate} onEdgeUpdate={onEdgeUpdate}
onEdgeUpdateStart={onEdgeUpdateStart} onEdgeUpdateStart={onEdgeUpdateStart}
onEdgeUpdateEnd={onEdgeUpdateEnd} onEdgeUpdateEnd={onEdgeUpdateEnd}
onNodeDragStart={onNodeDragStart} onNodeDragStart={onNodeDragStart}
onSelectionDragStart={onSelectionDragStart} onSelectionDragStart={onSelectionDragStart}
onEdgesDelete={onEdgesDelete} onEdgesDelete={onEdgesDelete}
connectionLineComponent={ConnectionLineComponent} connectionLineComponent={ConnectionLineComponent}
onDragOver={onDragOver} onDragOver={onDragOver}
onDrop={onDrop} onDrop={onDrop}
onNodesDelete={onDelete} onNodesDelete={onDelete}
selectNodesOnDrag={false} selectNodesOnDrag={false}
> >
<Background className="dark:bg-gray-900"/> <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"> <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">
</Controls> </Controls>
</ReactFlow> </ReactFlow>

4
src/frontend/src/png.d.ts vendored Normal file
View file

@ -0,0 +1,4 @@
declare module "*.png" {
const content: any;
export default content;
}

View file

@ -1,15 +1,15 @@
import { ReportHandler } from "web-vitals"; import { ReportHandler } from "web-vitals";
const reportWebVitals = (onPerfEntry?: ReportHandler) => { const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) { if (onPerfEntry && onPerfEntry instanceof Function) {
import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry); getCLS(onPerfEntry);
getFID(onPerfEntry); getFID(onPerfEntry);
getFCP(onPerfEntry); getFCP(onPerfEntry);
getLCP(onPerfEntry); getLCP(onPerfEntry);
getTTFB(onPerfEntry); getTTFB(onPerfEntry);
}); });
} }
}; };
export default reportWebVitals; export default reportWebVitals;

View file

@ -1,4 +1,4 @@
declare module "*.svg" { declare module "*.svg" {
const content: any; const content: any;
export default content; export default content;
} }

View file

@ -1,29 +1,29 @@
export type ErrorAlertType = { export type ErrorAlertType = {
title: string; title: string;
list: Array<string>; list: Array<string>;
id: string; id: string;
removeAlert: (id: string) => void; removeAlert: (id: string) => void;
}; };
export type NoticeAlertType = { export type NoticeAlertType = {
title: string; title: string;
link: string; link: string;
id: string; id: string;
removeAlert: (id: string) => void; removeAlert: (id: string) => void;
}; };
export type SuccessAlertType = { export type SuccessAlertType = {
title: string; title: string;
id: string; id: string;
removeAlert: (id: string) => void; removeAlert: (id: string) => void;
}; };
export type SingleAlertComponentType = { export type SingleAlertComponentType = {
dropItem: AlertItemType; dropItem: AlertItemType;
removeAlert: (index: string) => void; removeAlert: (index: string) => void;
}; };
export type AlertDropdownType = {}; export type AlertDropdownType = {};
export type AlertItemType = { export type AlertItemType = {
type: "notice" | "error" | "success"; type: "notice" | "error" | "success";
title: string; title: string;
link?: string; link?: string;
list?: Array<string>; list?: Array<string>;
id: string; id: string;
}; };

View file

@ -4,37 +4,37 @@ import { Node, Edge, Viewport } from "reactflow";
export type APIObjectType = { kind: APIKindType; [key: string]: APIKindType }; export type APIObjectType = { kind: APIKindType; [key: string]: APIKindType };
export type APIKindType = { class: APIClassType; [key: string]: APIClassType }; export type APIKindType = { class: APIClassType; [key: string]: APIClassType };
export type APITemplateType = { export type APITemplateType = {
variable: TemplateVariableType; variable: TemplateVariableType;
[key: string]: TemplateVariableType; [key: string]: TemplateVariableType;
}; };
export type APIClassType = { export type APIClassType = {
base_classes: Array<string>; base_classes: Array<string>;
description: string; description: string;
template: APITemplateType; template: APITemplateType;
[key: string]: Array<string> | string | APITemplateType; [key: string]: Array<string> | string | APITemplateType;
}; };
export type TemplateVariableType = { export type TemplateVariableType = {
type: string; type: string;
required: boolean; required: boolean;
placeholder?: string; placeholder?: string;
list: boolean; list: boolean;
show: boolean; show: boolean;
multiline?: boolean; multiline?: boolean;
value?: any; value?: any;
[key: string]: any; [key: string]: any;
}; };
export type sendAllProps = { export type sendAllProps = {
nodes: Node[]; nodes: Node[];
edges: Edge[]; edges: Edge[];
name: string; name: string;
description: string; description: string;
viewport: Viewport; viewport: Viewport;
message: string; message: string;
chatHistory: { message: string; isSend: boolean }[]; chatHistory: { message: string; isSend: boolean }[];
}; };
export type errorsTypeAPI = { export type errorsTypeAPI = {
function: { errors: Array<string> }; function: { errors: Array<string> };
imports: { errors: Array<string> }; imports: { errors: Array<string> };
}; };
export type PromptTypeAPI = { input_variables: Array<string> }; export type PromptTypeAPI = { input_variables: Array<string> };

View file

@ -3,8 +3,8 @@ import { FlowType } from "../flow";
export type ChatType = { flow: FlowType; reactFlowInstance: ReactFlowInstance }; export type ChatType = { flow: FlowType; reactFlowInstance: ReactFlowInstance };
export type ChatMessageType = { export type ChatMessageType = {
message: string; message: string;
isSend: boolean; isSend: boolean;
thought?: string; thought?: string;
files?: Array<{ data: string; type: string; data_type: string }>; files?: Array<{ data: string; type: string; data_type: string }>;
}; };

View file

@ -1,74 +1,74 @@
import { ForwardRefExoticComponent, ReactElement, ReactNode } from "react"; import { ForwardRefExoticComponent, ReactElement, ReactFragment, ReactNode } from "react";
import { NodeDataType } from "../flow/index"; import { NodeDataType } from "../flow/index";
export type InputComponentType = { export type InputComponentType = {
value: string; value: string;
disabled?: boolean; disabled?: boolean;
onChange: (value: string) => void; onChange: (value: string) => void;
password: boolean; password: boolean;
}; };
export type ToggleComponentType = { export type ToggleComponentType = {
enabled: boolean; enabled: boolean;
setEnabled: (state: boolean) => void; setEnabled: (state: boolean) => void;
disabled: boolean; disabled: boolean;
}; };
export type DropDownComponentType = { export type DropDownComponentType = {
value: string; value: string;
options: string[]; options: string[];
onSelect: (value: string) => void; onSelect: (value: string) => void;
}; };
export type ParameterComponentType = { export type ParameterComponentType = {
data: NodeDataType; data: NodeDataType;
title: string; title: string;
id: string; id: string;
color: string; color: string;
left: boolean; left: boolean;
type: string; type: string;
required?: boolean; required?: boolean;
name?: string; name?: string;
tooltipTitle: string; tooltipTitle: string;
}; };
export type InputListComponentType = { export type InputListComponentType = {
value: string[]; value: string[];
onChange: (value: string[]) => void; onChange: (value: string[]) => void;
disabled: boolean; disabled: boolean;
}; };
export type TextAreaComponentType = { export type TextAreaComponentType = {
disabled: boolean; disabled: boolean;
onChange: (value: string[] | string) => void; onChange: (value: string[] | string) => void;
value: string; value: string;
}; };
export type FileComponentType = { export type FileComponentType = {
disabled: boolean; disabled: boolean;
onChange: (value: string[] | string) => void; onChange: (value: string[] | string) => void;
value: string; value: string;
suffixes: Array<string>; suffixes: Array<string>;
fileTypes: Array<string>; fileTypes: Array<string>;
onFileChange: (value: string) => void; onFileChange: (value: string) => void;
}; };
export type DisclosureComponentType = { export type DisclosureComponentType = {
children: ReactNode; children: ReactNode;
button: { button: {
title: string; title: string;
Icon: ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>; Icon: ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>;
buttons?: { buttons?: {
Icon: ReactElement; Icon: ReactElement;
title: string; title: string;
onClick: (event?: React.MouseEvent) => void; onClick: (event?: React.MouseEvent) => void;
}[]; }[];
}; };
}; };
export type FloatComponentType = { export type FloatComponentType = {
value: string; value: string;
disabled?: boolean; disabled?: boolean;
onChange: (value: string) => void; onChange: (value: string) => void;
}; };
export type TooltipComponentType = { export type TooltipComponentType = {
children: ReactElement; children: ReactElement;
title: string; title: string | ReactElement;
placement?: placement?:
| "bottom-end" | "bottom-end"
| "bottom-start" | "bottom-start"

View file

@ -1,8 +1,8 @@
import { HomeIcon } from "@heroicons/react/24/outline"; import { HomeIcon } from "@heroicons/react/24/outline";
export type sidebarNavigationItemType = { export type sidebarNavigationItemType = {
name: string; name: string;
href: string; href: string;
icon: React.ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>; icon: React.ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>;
current: boolean; current: boolean;
}; };

View file

@ -3,20 +3,20 @@ import { APIClassType } from "../api/index";
import { ReactFlowJsonObject, XYPosition } from "reactflow"; import { ReactFlowJsonObject, XYPosition } from "reactflow";
export type FlowType = { export type FlowType = {
name: string; name: string;
id: string; id: string;
data: ReactFlowJsonObject; data: ReactFlowJsonObject;
description: string; description: string;
}; };
export type NodeType = { export type NodeType = {
id: string; id: string;
type?: string; type?: string;
position: XYPosition; position: XYPosition;
data: NodeDataType; data: NodeDataType;
}; };
export type NodeDataType = { export type NodeDataType = {
type: string; type: string;
node?: APIClassType; node?: APIClassType;
id: string; id: string;
value: any; value: any;
}; };

View file

@ -1,17 +1,25 @@
import { FlowType } from "../flow"; import { FlowType } from "../flow";
export type TabsContextType = { export type TabsContextType = {
save: () => void; save: () => void;
tabIndex: number; tabIndex: number;
setTabIndex: (index: number) => void; setTabIndex: (index: number) => void;
flows: Array<FlowType>; flows: Array<FlowType>;
removeFlow: (id: string) => void; removeFlow: (id: string) => void;
addFlow: (flowData?: FlowType) => void; addFlow: (flowData?: FlowType) => void;
updateFlow: (newFlow: FlowType) => void; updateFlow: (newFlow: FlowType) => void;
incrementNodeId: () => number; incrementNodeId: () => number;
downloadFlow: (flow: FlowType) => void; downloadFlow: (flow: FlowType) => void;
uploadFlow: () => void; uploadFlow: () => void;
hardReset: () => void; hardReset: () => void;
//disable CopyPaste
disableCP: boolean;
setDisableCP: (value: boolean) => void;
}; };
export type LangFlowState={ tabIndex:number, flows:FlowType[], id:string, nodeId:number } export type LangFlowState = {
tabIndex: number;
flows: FlowType[];
id: string;
nodeId: number;
};

View file

@ -1,7 +1,6 @@
const template: { [char: string]: string } = {};
const template:{[char: string]: string}={}
export type TemplateContextType = { export type TemplateContextType = {
templates: typeof template; templates: typeof template;
setTemplates: (newState: {}) => void; setTemplates: (newState: {}) => void;
}; };

View file

@ -1,12 +1,11 @@
import { ReactFlowInstance } from "reactflow"; import { ReactFlowInstance } from "reactflow";
const types:{[char: string]: string}={}; const types: { [char: string]: string } = {};
const template:{[char: string]: string}={} const template: { [char: string]: string } = {};
const data:{[char: string]: string}={} const data: { [char: string]: string } = {};
export type typesContextType = { export type typesContextType = {
reactFlowInstance: ReactFlowInstance|null; reactFlowInstance: ReactFlowInstance | null;
setReactFlowInstance: any; setReactFlowInstance: any;
deleteNode: (idx: string) => void; deleteNode: (idx: string) => void;
types: typeof types; types: typeof types;

View file

@ -19,7 +19,7 @@ import {
import { Connection, Edge, Node, ReactFlowInstance } from "reactflow"; import { Connection, Edge, Node, ReactFlowInstance } from "reactflow";
import { FlowType } from "./types/flow"; import { FlowType } from "./types/flow";
import { APITemplateType, TemplateVariableType } from "./types/api"; import { APITemplateType, TemplateVariableType } from "./types/api";
var _ = require("lodash"); import _ from "lodash";
export function classNames(...classes: Array<string>) { export function classNames(...classes: Array<string>) {
return classes.filter(Boolean).join(" "); return classes.filter(Boolean).join(" ");
@ -316,25 +316,34 @@ export function toFirstUpperCase(str: string) {
.join(""); .join("");
} }
export function toNormalCase(str: string) { export function snakeToSpaces(str: string) {
let result = str let result = str
.split("_") .split("_")
.map((word, index) => { .join(" ");
if (index === 0) {
return word[0].toUpperCase() + word.slice(1).toLowerCase();
}
return word.toLowerCase();
})
.join(" ");
return result.split("-") return result
.map((word, index) => { }
if (index === 0) {
return word[0].toUpperCase() + word.slice(1).toLowerCase(); export function toNormalCase(str: string) {
} let result = str
return word.toLowerCase(); .split("_")
}) .map((word, index) => {
.join(" "); if (index === 0) {
return word[0].toUpperCase() + word.slice(1).toLowerCase();
}
return word.toLowerCase();
})
.join(" ");
return result
.split("-")
.map((word, index) => {
if (index === 0) {
return word[0].toUpperCase() + word.slice(1).toLowerCase();
}
return word.toLowerCase();
})
.join(" ");
} }
export function normalCaseToSnakeCase(str: string) { export function normalCaseToSnakeCase(str: string) {
@ -397,15 +406,15 @@ export function isValidConnection(
} }
export function removeApiKeys(flow: FlowType): FlowType { export function removeApiKeys(flow: FlowType): FlowType {
let cleanFLow = _.cloneDeep(flow); let cleanFLow = _.cloneDeep(flow);
cleanFLow.data.nodes.forEach((node) => { cleanFLow.data.nodes.forEach((node) => {
for (const key in node.data.node.template) { for (const key in node.data.node.template) {
if (key.includes("api")) { if (key.includes("api")) {
node.data.node.template[key].value = ""; node.data.node.template[key].value = "";
} }
} }
}); });
return cleanFLow; return cleanFLow;
} }
export function updateObject<T extends Record<string, any>>( export function updateObject<T extends Record<string, any>>(
@ -443,7 +452,7 @@ export function updateTemplate(
reference: APITemplateType, reference: APITemplateType,
objectToUpdate: APITemplateType objectToUpdate: APITemplateType
): APITemplateType { ): APITemplateType {
let clonedObject:APITemplateType = _.cloneDeep(reference); let clonedObject: APITemplateType = _.cloneDeep(reference);
// Loop through each key in the reference object // Loop through each key in the reference object
for (const key in clonedObject) { for (const key in clonedObject) {
@ -454,3 +463,34 @@ export function updateTemplate(
} }
return clonedObject; return clonedObject;
} }
interface languageMap {
[key: string]: string | undefined;
}
export const programmingLanguages: languageMap = {
javascript: ".js",
python: ".py",
java: ".java",
c: ".c",
cpp: ".cpp",
"c++": ".cpp",
"c#": ".cs",
ruby: ".rb",
php: ".php",
swift: ".swift",
"objective-c": ".m",
kotlin: ".kt",
typescript: ".ts",
go: ".go",
perl: ".pl",
rust: ".rs",
scala: ".scala",
haskell: ".hs",
lua: ".lua",
shell: ".sh",
sql: ".sql",
html: ".html",
css: ".css",
// add more file extensions here, make sure the key is same as language prop in CodeBlock.tsx component
};

View file

@ -1,7 +1,10 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
const plugin = require("tailwindcss/plugin"); import plugin from "tailwindcss/plugin";
module.exports = { module.exports = {
content: ["./src/**/*.{js,ts,tsx,jsx}"], content: [
"./index.html",
"./src/**/*.{js,ts,tsx,jsx}",
],
darkMode: "class", darkMode: "class",
important: true, important: true,
theme: { theme: {
@ -57,6 +60,11 @@ module.exports = {
"font-family": "text-security-disc" "font-family": "text-security-disc"
}, },
'.stop': {
'-webkit-animation-play-state': 'paused',
'-moz-animation-play-state': 'paused',
'animation-play-state': 'paused',
},
'.custom-scroll':{ '.custom-scroll':{
'&::-webkit-scrollbar': { '&::-webkit-scrollbar': {
'width': '8px', 'width': '8px',
@ -73,6 +81,6 @@ module.exports = {
} }
} }
}) })
}),require('@tailwindcss/line-clamp') }),require('@tailwindcss/line-clamp'),require('@tailwindcss/typography'),
], ],
}; };

View file

@ -0,0 +1,34 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
const apiRoutes = [
'/all',
'/predict',
'^/validate/*',
'^/chat/*',
];
const proxyTargets = apiRoutes.reduce((proxyObj, route) => {
proxyObj[route] = {
target: 'http://127.0.0.1:7860',
changeOrigin: true,
secure: false,
ws: true,
};
return proxyObj;
}, {});
export default defineConfig(() => {
return {
build: {
outDir: 'build',
},
plugins: [react()],
server: {
port: 3000,
proxy: {
...proxyTargets
}
},
};
});

View file

@ -14,7 +14,7 @@ from langflow.graph.nodes import (
ToolNode, ToolNode,
WrapperNode, WrapperNode,
) )
from langflow.interface.run import get_result_and_steps from langflow.interface.run import get_result_and_thought
from langflow.utils.payload import get_root_node from langflow.utils.payload import get_root_node
# Test cases for the graph module # Test cases for the graph module
@ -335,7 +335,7 @@ def test_get_result_and_thought(basic_graph):
# now build again and check if FakeListLLM was used # now build again and check if FakeListLLM was used
# Get the result and thought # Get the result and thought
result, thought = get_result_and_steps(langchain_object, message) result, thought = get_result_and_thought(langchain_object, message)
# The result should be a str # The result should be a str
assert isinstance(result, str) assert isinstance(result, str)
# The thought should be a Thought # The thought should be a Thought