Streaming, Chat Markdown, and CacheManager (#282)
Chat now has Markdown helping see code blocks and images. Now models stream their messages if possible (VectorStores don't allow async for now in LangChain, I think) We added a cache_manager object. This allows you to add images (and soon other things too) to the chat. You can also recall the object saved from another PythonFunction node. Closes #241 Closes #162
This commit is contained in:
commit
2b5e3c9b46
75 changed files with 5088 additions and 2680 deletions
|
|
@ -1,12 +1,13 @@
|
|||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
from langchain.callbacks.base import AsyncCallbackHandler
|
||||
from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler
|
||||
|
||||
from langflow.api.schemas import ChatResponse
|
||||
|
||||
|
||||
# https://github.com/hwchase17/chat-langchain/blob/master/callback.py
|
||||
class StreamingLLMCallbackHandler(AsyncCallbackHandler):
|
||||
class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
|
||||
"""Callback handler for streaming LLM responses."""
|
||||
|
||||
def __init__(self, websocket):
|
||||
|
|
@ -15,3 +16,17 @@ class StreamingLLMCallbackHandler(AsyncCallbackHandler):
|
|||
async def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
|
||||
resp = ChatResponse(message=token, type="stream", intermediate_steps="")
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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.utils.logger import logger
|
||||
|
|
@ -12,7 +18,9 @@ async def websocket_endpoint(client_id: str, websocket: WebSocket):
|
|||
"""Websocket endpoint for chat."""
|
||||
try:
|
||||
await chat_manager.handle_websocket(client_id, websocket)
|
||||
except Exception as e:
|
||||
# Log stack trace
|
||||
logger.exception(e)
|
||||
raise e
|
||||
except WebSocketException as exc:
|
||||
logger.error(exc)
|
||||
await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=str(exc))
|
||||
except WebSocketDisconnect as exc:
|
||||
logger.error(exc)
|
||||
await websocket.close(code=status.WS_1000_NORMAL_CLOSURE, reason=str(exc))
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import json
|
|||
from collections import defaultdict
|
||||
from typing import Dict, List
|
||||
|
||||
from fastapi import WebSocket
|
||||
from fastapi import WebSocket, status
|
||||
|
||||
from langflow.api.schemas import ChatMessage, ChatResponse, FileResponse
|
||||
from langflow.cache import cache_manager
|
||||
|
|
@ -47,7 +47,6 @@ class ChatManager:
|
|||
def __init__(self):
|
||||
self.active_connections: Dict[str, WebSocket] = {}
|
||||
self.chat_history = ChatHistory()
|
||||
self.chat_history.attach(self.on_chat_history_update)
|
||||
self.cache_manager = cache_manager
|
||||
self.cache_manager.attach(self.update)
|
||||
|
||||
|
|
@ -91,7 +90,7 @@ class ChatManager:
|
|||
self.active_connections[client_id] = websocket
|
||||
|
||||
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):
|
||||
websocket = self.active_connections[client_id]
|
||||
|
|
@ -109,7 +108,7 @@ class ChatManager:
|
|||
|
||||
graph_data = payload
|
||||
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
|
||||
# Generate result and thought
|
||||
|
|
@ -143,11 +142,12 @@ class ChatManager:
|
|||
break
|
||||
|
||||
response = ChatResponse(
|
||||
message=result or "",
|
||||
message=result,
|
||||
intermediate_steps=intermediate_steps.strip(),
|
||||
type="end",
|
||||
files=file_responses,
|
||||
)
|
||||
await self.send_json(client_id, response)
|
||||
self.chat_history.add_message(client_id, response)
|
||||
|
||||
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):
|
||||
await self.process_message(client_id, payload)
|
||||
|
||||
except Exception as e:
|
||||
# Handle any exceptions that might occur
|
||||
logger.exception(e)
|
||||
# send a message to the client
|
||||
await self.send_message(client_id, str(e))
|
||||
raise e
|
||||
finally:
|
||||
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)
|
||||
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(
|
||||
|
|
@ -203,8 +210,8 @@ async def process_graph(
|
|||
# Generate result and thought
|
||||
try:
|
||||
logger.debug("Generating result and thought")
|
||||
result, intermediate_steps = get_result_and_steps(
|
||||
langchain_object, chat_message.message or ""
|
||||
result, intermediate_steps = await get_result_and_steps(
|
||||
langchain_object, chat_message.message or "", websocket=websocket
|
||||
)
|
||||
logger.debug("Generated result and intermediate_steps")
|
||||
return result, intermediate_steps
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
import logging
|
||||
from typing import Any, Dict
|
||||
|
||||
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 (
|
||||
ExportedFlow,
|
||||
GraphData,
|
||||
PredictRequest,
|
||||
PredictResponse,
|
||||
)
|
||||
from langflow.interface.run import process_graph_cached
|
||||
from langflow.interface.types import build_langchain_types_dict
|
||||
|
||||
# build router
|
||||
router = APIRouter()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Any, Union, Dict, List
|
||||
from typing import Any, Dict, List, Union
|
||||
|
||||
from pydantic import BaseModel, validator
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from abc import ABC
|
||||
from typing import Any, List, Optional
|
||||
|
||||
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 as SQL_FORMAT_INSTRUCTIONS
|
||||
from langchain.base_language import BaseLanguageModel
|
||||
|
||||
from langchain.memory.chat_memory import BaseChatMemory
|
||||
from langchain.sql_database import SQLDatabase
|
||||
from langchain.tools.python.tool import PythonAstREPLTool
|
||||
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"""
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -70,7 +88,7 @@ class JsonAgent(AgentExecutor):
|
|||
return super().run(*args, **kwargs)
|
||||
|
||||
|
||||
class CSVAgent(AgentExecutor):
|
||||
class CSVAgent(CustomAgentExecutor):
|
||||
"""CSV agent"""
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -118,7 +136,7 @@ class CSVAgent(AgentExecutor):
|
|||
return super().run(*args, **kwargs)
|
||||
|
||||
|
||||
class VectorStoreAgent(AgentExecutor):
|
||||
class VectorStoreAgent(CustomAgentExecutor):
|
||||
"""Vector Store agent"""
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -156,7 +174,7 @@ class VectorStoreAgent(AgentExecutor):
|
|||
return super().run(*args, **kwargs)
|
||||
|
||||
|
||||
class SQLAgent(AgentExecutor):
|
||||
class SQLAgent(CustomAgentExecutor):
|
||||
"""SQL agent"""
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -228,7 +246,7 @@ class SQLAgent(AgentExecutor):
|
|||
return super().run(*args, **kwargs)
|
||||
|
||||
|
||||
class VectorStoreRouterAgent(AgentExecutor):
|
||||
class VectorStoreRouterAgent(CustomAgentExecutor):
|
||||
"""Vector Store Router Agent"""
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -267,7 +285,7 @@ class VectorStoreRouterAgent(AgentExecutor):
|
|||
return super().run(*args, **kwargs)
|
||||
|
||||
|
||||
class InitializeAgent(AgentExecutor):
|
||||
class InitializeAgent(CustomAgentExecutor):
|
||||
"""Implementation of initialize_agent function"""
|
||||
|
||||
@staticmethod
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ from typing import Any, Type
|
|||
|
||||
from langchain import PromptTemplate
|
||||
from langchain.agents import Agent
|
||||
from langchain.base_language import BaseLanguageModel
|
||||
from langchain.chains.base import Chain
|
||||
from langchain.chat_models.base import BaseChatModel
|
||||
from langchain.base_language import BaseLanguageModel
|
||||
from langchain.tools import BaseTool
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ from langchain.agents.load_tools import (
|
|||
)
|
||||
from langchain.agents.loading import load_agent_from_config
|
||||
from langchain.agents.tools import Tool
|
||||
from langchain.base_language import BaseLanguageModel
|
||||
from langchain.callbacks.base import BaseCallbackManager
|
||||
from langchain.chains.loading import load_chain_from_config
|
||||
from langchain.base_language import BaseLanguageModel
|
||||
from langchain.llms.loading import load_llm_from_config
|
||||
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:
|
||||
"""Instantiate class from module type and key, and params"""
|
||||
params = convert_params_to_sets(params)
|
||||
|
||||
if node_type in CUSTOM_AGENTS:
|
||||
if custom_agent := CUSTOM_AGENTS.get(node_type):
|
||||
return custom_agent.initialize(**params) # type: ignore
|
||||
params = process_params(params)
|
||||
custom_agent = CUSTOM_AGENTS.get(node_type)
|
||||
if custom_agent:
|
||||
return custom_agent.initialize(**params)
|
||||
|
||||
class_object = import_by_type(_type=base_type, name=node_type)
|
||||
# check if it is a class before using issubclass
|
||||
|
||||
# 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)
|
||||
return instantiate_based_on_type(class_object, base_type, node_type, params)
|
||||
|
||||
|
||||
def process_params(params):
|
||||
"""Process params"""
|
||||
def convert_params_to_sets(params):
|
||||
"""Convert certain params to sets"""
|
||||
if "allowed_special" in params:
|
||||
params["allowed_special"] = set(params["allowed_special"])
|
||||
if "disallowed_special" in params:
|
||||
|
|
@ -119,6 +50,100 @@ def process_params(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(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(node_type, params):
|
||||
if node_type == "ZeroShotPrompt":
|
||||
if "tools" not in params:
|
||||
params["tools"] = []
|
||||
return ZeroShotAgent.create_prompt(**params)
|
||||
return None # Or some other default action
|
||||
|
||||
|
||||
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):
|
||||
# This is done to avoid circular imports
|
||||
from langflow.graph import Graph
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ from typing import Any, Dict
|
|||
|
||||
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.graph.graph import Graph
|
||||
from langflow.interface import loading
|
||||
from langflow.utils.logger import logger
|
||||
|
||||
|
||||
|
|
@ -66,40 +66,6 @@ def build_langchain_object(data_graph):
|
|||
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):
|
||||
"""
|
||||
Process graph by extracting input variables and replacing ZeroShotPrompt
|
||||
|
|
@ -184,8 +150,9 @@ def fix_memory_inputs(langchain_object):
|
|||
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"""
|
||||
|
||||
try:
|
||||
if hasattr(langchain_object, "verbose"):
|
||||
langchain_object.verbose = True
|
||||
|
|
@ -205,17 +172,21 @@ def get_result_and_steps(langchain_object, message: str):
|
|||
# https://github.com/hwchase17/langchain/issues/2068
|
||||
# Deactivating until we have a frontend solution
|
||||
# to display intermediate steps
|
||||
langchain_object.return_intermediate_steps = False
|
||||
langchain_object.return_intermediate_steps = True
|
||||
|
||||
fix_memory_inputs(langchain_object)
|
||||
|
||||
with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer):
|
||||
try:
|
||||
output = langchain_object(chat_input)
|
||||
except ValueError as exc:
|
||||
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)}")
|
||||
output = langchain_object.run(chat_input)
|
||||
sync_callbacks = [StreamingLLMCallbackHandler(**kwargs)]
|
||||
output = langchain_object(chat_input, callbacks=sync_callbacks)
|
||||
|
||||
intermediate_steps = (
|
||||
output.get("intermediate_steps", []) if isinstance(output, dict) else []
|
||||
|
|
|
|||
|
|
@ -4,13 +4,9 @@ import os
|
|||
from io import BytesIO
|
||||
|
||||
import yaml
|
||||
from langchain.callbacks.manager import AsyncCallbackManager
|
||||
from langchain.chat_models import AzureChatOpenAI, ChatOpenAI
|
||||
from langchain.llms import AzureOpenAI, OpenAI
|
||||
from langchain.base_language import BaseLanguageModel
|
||||
from PIL.Image import Image
|
||||
|
||||
from langflow.api.callback import StreamingLLMCallbackHandler
|
||||
|
||||
|
||||
def load_file_into_dict(file_path: str) -> dict:
|
||||
if not os.path.exists(file_path):
|
||||
|
|
@ -48,10 +44,7 @@ def try_setting_streaming_options(langchain_object, websocket):
|
|||
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"))
|
||||
stream_handler = StreamingLLMCallbackHandler(websocket)
|
||||
stream_manager = AsyncCallbackManager([stream_handler])
|
||||
llm.callback_manager = stream_manager
|
||||
|
||||
return langchain_object
|
||||
|
|
|
|||
|
|
@ -123,6 +123,13 @@ class MidJourneyPromptChainNode(FrontendNode):
|
|||
multiline=False,
|
||||
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."
|
||||
|
|
|
|||
1982
src/frontend/package-lock.json
generated
1982
src/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -8,6 +8,7 @@
|
|||
"@headlessui/react": "^1.7.10",
|
||||
"@heroicons/react": "^2.0.15",
|
||||
"@mui/material": "^5.11.9",
|
||||
"@tabler/icons-react": "^2.17.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/line-clamp": "^0.4.4",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
|
|
@ -29,10 +30,15 @@
|
|||
"react-error-boundary": "^4.0.2",
|
||||
"react-icons": "^4.8.0",
|
||||
"react-laag": "^2.0.5",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-router-dom": "^6.8.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"react-tabs": "^6.0.0",
|
||||
"reactflow": "^11.5.5",
|
||||
"rehype-mathjax": "^4.0.2",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-math": "^5.1.1",
|
||||
"tailwindcss": "^3.2.6",
|
||||
"typescript": "^4.9.5",
|
||||
"web-vitals": "^2.1.4"
|
||||
|
|
@ -61,5 +67,8 @@
|
|||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"proxy": "http://127.0.0.1:7860"
|
||||
}
|
||||
"proxy": "http://127.0.0.1:7860",
|
||||
"devDependencies": {
|
||||
"@tailwindcss/typography": "^0.5.9"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,45 +3,45 @@
|
|||
@tailwind utilities;
|
||||
|
||||
.App {
|
||||
text-align: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@font-face{
|
||||
font-family: text-security-disc;
|
||||
src: url("assets/text-security-disc.woff") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: text-security-disc;
|
||||
src: url("assets/text-security-disc.woff") format("woff");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -198,7 +198,9 @@ export default function ParameterComponent({
|
|||
save();
|
||||
}}
|
||||
/>
|
||||
):(<></>)}
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,5 @@
|
|||
import { Cog6ToothIcon, TrashIcon } from "@heroicons/react/24/outline";
|
||||
import {
|
||||
classNames,
|
||||
nodeColors,
|
||||
nodeIcons,
|
||||
toNormalCase,
|
||||
} from "../../utils";
|
||||
import { classNames, nodeColors, nodeIcons, toNormalCase } from "../../utils";
|
||||
import ParameterComponent from "./components/parameterComponent";
|
||||
import { typesContext } from "../../contexts/typesContext";
|
||||
import { useContext, useState, useEffect, useRef } from "react";
|
||||
|
|
@ -16,149 +11,148 @@ import { useCallback } from "react";
|
|||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import { debounce } from "../../utils";
|
||||
export default function GenericNode({
|
||||
data,
|
||||
selected,
|
||||
data,
|
||||
selected,
|
||||
}: {
|
||||
data: NodeDataType;
|
||||
selected: boolean;
|
||||
data: NodeDataType;
|
||||
selected: boolean;
|
||||
}) {
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const showError = useRef(true);
|
||||
const { types, deleteNode } = useContext(typesContext);
|
||||
const { openPopUp } = useContext(PopUpContext);
|
||||
const Icon = nodeIcons[types[data.type]];
|
||||
const [validationStatus, setValidationStatus] = useState("idle");
|
||||
// State for outline color
|
||||
const [isValid, setIsValid] = useState(false);
|
||||
const { save } = useContext(TabsContext);
|
||||
const { reactFlowInstance } = useContext(typesContext);
|
||||
const [params, setParams] = useState([]);
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const showError = useRef(true);
|
||||
const { types, deleteNode } = useContext(typesContext);
|
||||
const { openPopUp } = useContext(PopUpContext);
|
||||
const Icon = nodeIcons[types[data.type]];
|
||||
const [validationStatus, setValidationStatus] = useState("idle");
|
||||
// State for outline color
|
||||
const [isValid, setIsValid] = useState(false);
|
||||
const { save } = useContext(TabsContext);
|
||||
const { reactFlowInstance } = useContext(typesContext);
|
||||
const [params, setParams] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (reactFlowInstance) {
|
||||
setParams(Object.values(reactFlowInstance.toObject()));
|
||||
}
|
||||
}, [save]);
|
||||
|
||||
useEffect(() => {
|
||||
if (reactFlowInstance) {
|
||||
setParams(Object.values(reactFlowInstance.toObject()));
|
||||
}
|
||||
}, [save]);
|
||||
const validateNode = useCallback(
|
||||
debounce(async () => {
|
||||
try {
|
||||
const response = await fetch(`/validate/node/${data.id}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(reactFlowInstance.toObject()),
|
||||
});
|
||||
|
||||
const validateNode = useCallback(
|
||||
debounce(async () => {
|
||||
try {
|
||||
const response = await fetch(`/validate/node/${data.id}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(reactFlowInstance.toObject()),
|
||||
});
|
||||
if (response.status === 200) {
|
||||
setValidationStatus("success");
|
||||
} else if (response.status === 500) {
|
||||
setValidationStatus("error");
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error("Error validating node:", error);
|
||||
setValidationStatus("error");
|
||||
}
|
||||
}, 1000), // Adjust the debounce delay (500ms) as needed
|
||||
[reactFlowInstance, data.id]
|
||||
);
|
||||
useEffect(() => {
|
||||
if (params.length > 0) {
|
||||
validateNode();
|
||||
}
|
||||
}, [params, validateNode]);
|
||||
|
||||
if (response.status === 200) {
|
||||
setValidationStatus("success");
|
||||
} else if (response.status === 500) {
|
||||
setValidationStatus("error");
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error("Error validating node:", error);
|
||||
setValidationStatus("error");
|
||||
}
|
||||
}, 1000), // Adjust the debounce delay (500ms) as needed
|
||||
[reactFlowInstance, data.id]
|
||||
);
|
||||
useEffect(() => {
|
||||
if (params.length > 0) {
|
||||
validateNode();
|
||||
}
|
||||
}, [params, validateNode]);
|
||||
useEffect(() => {
|
||||
if (validationStatus === "success") {
|
||||
setIsValid(true);
|
||||
} else {
|
||||
setIsValid(false);
|
||||
}
|
||||
}, [validationStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
if (validationStatus === "success") {
|
||||
setIsValid(true);
|
||||
} else {
|
||||
setIsValid(false);
|
||||
}
|
||||
}, [validationStatus]);
|
||||
if (!Icon) {
|
||||
if (showError.current) {
|
||||
setErrorData({
|
||||
title: data.type
|
||||
? `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",
|
||||
});
|
||||
showError.current = false;
|
||||
}
|
||||
deleteNode(data.id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Icon) {
|
||||
if (showError.current) {
|
||||
setErrorData({
|
||||
title: data.type
|
||||
? `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",
|
||||
});
|
||||
showError.current = false;
|
||||
}
|
||||
deleteNode(data.id);
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
isValid ? "animate-pulse-green" : "border-red-outline",
|
||||
selected ? "border border-blue-500" : "border dark:border-gray-700",
|
||||
"prompt-node relative bg-white dark:bg-gray-900 w-96 rounded-lg flex flex-col justify-center"
|
||||
)}
|
||||
>
|
||||
<div className="w-full dark:text-white flex items-center justify-between p-4 gap-8 bg-gray-50 rounded-t-lg dark:bg-gray-800 border-b dark:border-b-gray-700 ">
|
||||
<div className="w-full flex items-center truncate gap-4 text-lg">
|
||||
<Icon
|
||||
className="w-10 h-10 p-1 rounded"
|
||||
style={{
|
||||
color: nodeColors[types[data.type]] ?? nodeColors.unknown,
|
||||
}}
|
||||
/>
|
||||
<div className="truncate">{data.type}</div>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
className="relative"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
openPopUp(<NodeModal data={data} />);
|
||||
}}
|
||||
>
|
||||
<div className=" absolute text-red-600 -top-2 -right-1">
|
||||
{Object.keys(data.node.template).some(
|
||||
(t) =>
|
||||
data.node.template[t].advanced &&
|
||||
data.node.template[t].required
|
||||
)
|
||||
? " *"
|
||||
: ""}
|
||||
</div>
|
||||
<Cog6ToothIcon
|
||||
className={classNames(
|
||||
Object.keys(data.node.template).some(
|
||||
(t) =>
|
||||
data.node.template[t].advanced && data.node.template[t].show
|
||||
)
|
||||
? ""
|
||||
: "hidden",
|
||||
"w-6 h-6 dark:text-gray-500 hover:animate-spin"
|
||||
)}
|
||||
></Cog6ToothIcon>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
deleteNode(data.id);
|
||||
}}
|
||||
>
|
||||
<TrashIcon className="w-6 h-6 hover:text-red-500 dark:text-gray-500 dark:hover:text-red-500"></TrashIcon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
isValid ? "animate-pulse-green" : "border-red-outline",
|
||||
selected ? "border border-blue-500" : "border dark:border-gray-700",
|
||||
"prompt-node relative bg-white dark:bg-gray-900 w-96 rounded-lg flex flex-col justify-center"
|
||||
)}
|
||||
>
|
||||
<div className="w-full dark:text-white flex items-center justify-between p-4 gap-8 bg-gray-50 rounded-t-lg dark:bg-gray-800 border-b dark:border-b-gray-700 ">
|
||||
<div className="w-full flex items-center truncate gap-4 text-lg">
|
||||
<Icon
|
||||
className="w-10 h-10 p-1 rounded"
|
||||
style={{
|
||||
color: nodeColors[types[data.type]] ?? nodeColors.unknown,
|
||||
}}
|
||||
/>
|
||||
<div className="truncate">{data.type}</div>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
className="relative"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
openPopUp(<NodeModal data={data} />);
|
||||
}}
|
||||
>
|
||||
<div className=" absolute text-red-600 -top-2 -right-1">
|
||||
{Object.keys(data.node.template).some(
|
||||
(t) =>
|
||||
data.node.template[t].advanced &&
|
||||
data.node.template[t].required
|
||||
)
|
||||
? " *"
|
||||
: ""}
|
||||
</div>
|
||||
<Cog6ToothIcon
|
||||
className={classNames(
|
||||
Object.keys(data.node.template).some(
|
||||
(t) =>
|
||||
data.node.template[t].advanced && data.node.template[t].show
|
||||
)
|
||||
? ""
|
||||
: "hidden",
|
||||
"w-6 h-6 dark:text-gray-500 hover:animate-spin"
|
||||
)}
|
||||
></Cog6ToothIcon>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
deleteNode(data.id);
|
||||
}}
|
||||
>
|
||||
<TrashIcon className="w-6 h-6 hover:text-red-500 dark:text-gray-500 dark:hover:text-red-500"></TrashIcon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full h-full py-5">
|
||||
<div className="w-full text-gray-500 px-5 pb-3 text-sm">
|
||||
{data.node.description}
|
||||
</div>
|
||||
|
||||
<div className="w-full h-full py-5">
|
||||
<div className="w-full text-gray-500 px-5 pb-3 text-sm">
|
||||
{data.node.description}
|
||||
</div>
|
||||
|
||||
<>
|
||||
{Object.keys(data.node.template)
|
||||
.filter((t) => t.charAt(0) !== "_")
|
||||
.map((t: string, idx) => (
|
||||
<div key={idx}>
|
||||
{/* {idx === 0 ? (
|
||||
<>
|
||||
{Object.keys(data.node.template)
|
||||
.filter((t) => t.charAt(0) !== "_")
|
||||
.map((t: string, idx) => (
|
||||
<div key={idx}>
|
||||
{/* {idx === 0 ? (
|
||||
<div
|
||||
className={classNames(
|
||||
"px-5 py-2 mt-2 dark:text-white text-center",
|
||||
|
|
@ -177,59 +171,59 @@ export default function GenericNode({
|
|||
) : (
|
||||
<></>
|
||||
)} */}
|
||||
{data.node.template[t].show &&
|
||||
!data.node.template[t].advanced ? (
|
||||
<ParameterComponent
|
||||
data={data}
|
||||
color={
|
||||
nodeColors[types[data.node.template[t].type]] ??
|
||||
nodeColors.unknown
|
||||
}
|
||||
title={
|
||||
data.node.template[t].display_name
|
||||
? data.node.template[t].display_name
|
||||
: data.node.template[t].name
|
||||
? toNormalCase(data.node.template[t].name)
|
||||
: toNormalCase(t)
|
||||
}
|
||||
name={t}
|
||||
tooltipTitle={
|
||||
"Type: " +
|
||||
data.node.template[t].type +
|
||||
(data.node.template[t].list ? " list" : "")
|
||||
}
|
||||
required={data.node.template[t].required}
|
||||
id={data.node.template[t].type + "|" + t + "|" + data.id}
|
||||
left={true}
|
||||
type={data.node.template[t].type}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<div
|
||||
className={classNames(
|
||||
Object.keys(data.node.template).length < 1 ? "hidden" : "",
|
||||
"w-full flex justify-center"
|
||||
)}
|
||||
>
|
||||
{" "}
|
||||
</div>
|
||||
{/* <div className="px-5 py-2 mt-2 dark:text-white text-center">
|
||||
{data.node.template[t].show &&
|
||||
!data.node.template[t].advanced ? (
|
||||
<ParameterComponent
|
||||
data={data}
|
||||
color={
|
||||
nodeColors[types[data.node.template[t].type]] ??
|
||||
nodeColors.unknown
|
||||
}
|
||||
title={
|
||||
data.node.template[t].display_name
|
||||
? data.node.template[t].display_name
|
||||
: data.node.template[t].name
|
||||
? toNormalCase(data.node.template[t].name)
|
||||
: toNormalCase(t)
|
||||
}
|
||||
name={t}
|
||||
tooltipTitle={
|
||||
"Type: " +
|
||||
data.node.template[t].type +
|
||||
(data.node.template[t].list ? " list" : "")
|
||||
}
|
||||
required={data.node.template[t].required}
|
||||
id={data.node.template[t].type + "|" + t + "|" + data.id}
|
||||
left={true}
|
||||
type={data.node.template[t].type}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<div
|
||||
className={classNames(
|
||||
Object.keys(data.node.template).length < 1 ? "hidden" : "",
|
||||
"w-full flex justify-center"
|
||||
)}
|
||||
>
|
||||
{" "}
|
||||
</div>
|
||||
{/* <div className="px-5 py-2 mt-2 dark:text-white text-center">
|
||||
Output
|
||||
</div> */}
|
||||
<ParameterComponent
|
||||
data={data}
|
||||
color={nodeColors[types[data.type]] ?? nodeColors.unknown}
|
||||
title={data.type}
|
||||
tooltipTitle={`Type: ${data.node.base_classes.join(" | ")}`}
|
||||
id={[data.type, data.id, ...data.node.base_classes].join("|")}
|
||||
type={data.node.base_classes.join("|")}
|
||||
left={false}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<ParameterComponent
|
||||
data={data}
|
||||
color={nodeColors[types[data.type]] ?? nodeColors.unknown}
|
||||
title={data.type}
|
||||
tooltipTitle={`Type: ${data.node.base_classes.join(" | ")}`}
|
||||
id={[data.type, data.id, ...data.node.base_classes].join("|")}
|
||||
type={data.node.base_classes.join("|")}
|
||||
left={false}
|
||||
/>
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import {
|
||||
XCircleIcon,
|
||||
XMarkIcon,
|
||||
InformationCircleIcon,
|
||||
CheckCircleIcon,
|
||||
XCircleIcon,
|
||||
XMarkIcon,
|
||||
InformationCircleIcon,
|
||||
CheckCircleIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Transition } from "@headlessui/react";
|
||||
|
|
@ -10,145 +10,145 @@ import { useState } from "react";
|
|||
import { SingleAlertComponentType } from "../../../../types/alerts";
|
||||
|
||||
export default function SingleAlert({
|
||||
dropItem,
|
||||
removeAlert,
|
||||
dropItem,
|
||||
removeAlert,
|
||||
}: SingleAlertComponentType) {
|
||||
const [show, setShow] = useState(true);
|
||||
const type = dropItem.type;
|
||||
const [show, setShow] = useState(true);
|
||||
const type = dropItem.type;
|
||||
|
||||
return (
|
||||
<Transition
|
||||
className="relative"
|
||||
show={show}
|
||||
appear={true}
|
||||
enter="transition-transform duration-500 ease-out"
|
||||
enterFrom={"transform translate-x-[-100%]"}
|
||||
enterTo={"transform translate-x-0"}
|
||||
leave="transition-transform duration-500 ease-in"
|
||||
leaveFrom={"transform translate-x-0"}
|
||||
leaveTo={"transform translate-x-[-100%]"}
|
||||
>
|
||||
{type === "error" ? (
|
||||
<div
|
||||
className="flex bg-red-50 rounded-md p-3 mb-2 mx-2"
|
||||
key={dropItem.id}
|
||||
>
|
||||
<div className="flex-shrink-0">
|
||||
<XCircleIcon className="h-5 w-5 text-red-400" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h3 className="text-sm font-medium text-red-800">
|
||||
{dropItem.title}
|
||||
</h3>
|
||||
{dropItem.list ? (
|
||||
<div className="mt-2 text-sm text-red-700">
|
||||
<ul className="list-disc space-y-1 pl-5">
|
||||
{dropItem.list.map((item, idx) => (
|
||||
<li key={idx}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
<div className="ml-auto pl-3">
|
||||
<div className="-mx-1.5 -my-1.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShow(false);
|
||||
setTimeout(() => {
|
||||
removeAlert(dropItem.id);
|
||||
}, 500);
|
||||
}}
|
||||
className="inline-flex rounded-md bg-red-50 p-1.5 text-red-500"
|
||||
>
|
||||
<span className="sr-only">Dismiss</span>
|
||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : type === "notice" ? (
|
||||
<div
|
||||
className="flex rounded-md bg-blue-50 p-3 mb-2 mx-2"
|
||||
key={dropItem.id}
|
||||
>
|
||||
<div className="flex-shrink-0">
|
||||
<InformationCircleIcon
|
||||
className="h-5 w-5 text-blue-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3 flex-1 md:flex md:justify-between">
|
||||
<p className="text-sm text-blue-700">{dropItem.title}</p>
|
||||
<p className="mt-3 text-sm md:mt-0 md:ml-6">
|
||||
{dropItem.link ? (
|
||||
<Link
|
||||
to={dropItem.link}
|
||||
className="whitespace-nowrap font-medium text-blue-700 hover:text-blue-600"
|
||||
>
|
||||
Details
|
||||
</Link>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="ml-auto pl-3">
|
||||
<div className="-mx-1.5 -my-1.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShow(false);
|
||||
setTimeout(() => {
|
||||
removeAlert(dropItem.id);
|
||||
}, 500);
|
||||
}}
|
||||
className="inline-flex rounded-md bg-blue-50 p-1.5 text-blue-500"
|
||||
>
|
||||
<span className="sr-only">Dismiss</span>
|
||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="flex bg-green-50 p-3 mb-2 mx-2 rounded-md"
|
||||
key={dropItem.id}
|
||||
>
|
||||
<div className="flex-shrink-0">
|
||||
<CheckCircleIcon
|
||||
className="h-5 w-5 text-green-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<p className="text-sm font-medium text-green-800">
|
||||
{dropItem.title}
|
||||
</p>
|
||||
</div>
|
||||
<div className="ml-auto pl-3">
|
||||
<div className="-mx-1.5 -my-1.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShow(false);
|
||||
setTimeout(() => {
|
||||
removeAlert(dropItem.id);
|
||||
}, 500);
|
||||
}}
|
||||
className="inline-flex rounded-md bg-green-50 p-1.5 text-green-500"
|
||||
>
|
||||
<span className="sr-only">Dismiss</span>
|
||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Transition>
|
||||
);
|
||||
return (
|
||||
<Transition
|
||||
className="relative"
|
||||
show={show}
|
||||
appear={true}
|
||||
enter="transition-transform duration-500 ease-out"
|
||||
enterFrom={"transform translate-x-[-100%]"}
|
||||
enterTo={"transform translate-x-0"}
|
||||
leave="transition-transform duration-500 ease-in"
|
||||
leaveFrom={"transform translate-x-0"}
|
||||
leaveTo={"transform translate-x-[-100%]"}
|
||||
>
|
||||
{type === "error" ? (
|
||||
<div
|
||||
className="flex bg-red-50 rounded-md p-3 mb-2 mx-2"
|
||||
key={dropItem.id}
|
||||
>
|
||||
<div className="flex-shrink-0">
|
||||
<XCircleIcon className="h-5 w-5 text-red-400" aria-hidden="true" />
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h3 className="text-sm font-medium text-red-800">
|
||||
{dropItem.title}
|
||||
</h3>
|
||||
{dropItem.list ? (
|
||||
<div className="mt-2 text-sm text-red-700">
|
||||
<ul className="list-disc space-y-1 pl-5">
|
||||
{dropItem.list.map((item, idx) => (
|
||||
<li key={idx}>{item}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
<div className="ml-auto pl-3">
|
||||
<div className="-mx-1.5 -my-1.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShow(false);
|
||||
setTimeout(() => {
|
||||
removeAlert(dropItem.id);
|
||||
}, 500);
|
||||
}}
|
||||
className="inline-flex rounded-md bg-red-50 p-1.5 text-red-500"
|
||||
>
|
||||
<span className="sr-only">Dismiss</span>
|
||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : type === "notice" ? (
|
||||
<div
|
||||
className="flex rounded-md bg-blue-50 p-3 mb-2 mx-2"
|
||||
key={dropItem.id}
|
||||
>
|
||||
<div className="flex-shrink-0">
|
||||
<InformationCircleIcon
|
||||
className="h-5 w-5 text-blue-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3 flex-1 md:flex md:justify-between">
|
||||
<p className="text-sm text-blue-700">{dropItem.title}</p>
|
||||
<p className="mt-3 text-sm md:mt-0 md:ml-6">
|
||||
{dropItem.link ? (
|
||||
<Link
|
||||
to={dropItem.link}
|
||||
className="whitespace-nowrap font-medium text-blue-700 hover:text-blue-600"
|
||||
>
|
||||
Details
|
||||
</Link>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="ml-auto pl-3">
|
||||
<div className="-mx-1.5 -my-1.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShow(false);
|
||||
setTimeout(() => {
|
||||
removeAlert(dropItem.id);
|
||||
}, 500);
|
||||
}}
|
||||
className="inline-flex rounded-md bg-blue-50 p-1.5 text-blue-500"
|
||||
>
|
||||
<span className="sr-only">Dismiss</span>
|
||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="flex bg-green-50 p-3 mb-2 mx-2 rounded-md"
|
||||
key={dropItem.id}
|
||||
>
|
||||
<div className="flex-shrink-0">
|
||||
<CheckCircleIcon
|
||||
className="h-5 w-5 text-green-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<p className="text-sm font-medium text-green-800">
|
||||
{dropItem.title}
|
||||
</p>
|
||||
</div>
|
||||
<div className="ml-auto pl-3">
|
||||
<div className="-mx-1.5 -my-1.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setShow(false);
|
||||
setTimeout(() => {
|
||||
removeAlert(dropItem.id);
|
||||
}, 500);
|
||||
}}
|
||||
className="inline-flex rounded-md bg-green-50 p-1.5 text-green-500"
|
||||
>
|
||||
<span className="sr-only">Dismiss</span>
|
||||
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,60 +7,60 @@ import { AlertDropdownType } from "../../types/alerts";
|
|||
import { PopUpContext } from "../../contexts/popUpContext";
|
||||
import { useOnClickOutside } from "../hooks/useOnClickOutside";
|
||||
export default function AlertDropdown({}: AlertDropdownType) {
|
||||
const { closePopUp } = useContext(PopUpContext);
|
||||
const componentRef = useRef<HTMLDivElement>(null);
|
||||
const { closePopUp } = useContext(PopUpContext);
|
||||
const componentRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Use the custom hook
|
||||
useOnClickOutside(componentRef, () => {
|
||||
closePopUp();
|
||||
});
|
||||
// Use the custom hook
|
||||
useOnClickOutside(componentRef, () => {
|
||||
closePopUp();
|
||||
});
|
||||
|
||||
const {
|
||||
notificationList,
|
||||
clearNotificationList,
|
||||
removeFromNotificationList,
|
||||
} = useContext(alertContext);
|
||||
const {
|
||||
notificationList,
|
||||
clearNotificationList,
|
||||
removeFromNotificationList,
|
||||
} = useContext(alertContext);
|
||||
|
||||
return (
|
||||
<div
|
||||
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"
|
||||
>
|
||||
<div className="flex pl-3 flex-row justify-between text-md font-medium text-gray-800 dark:text-gray-200">
|
||||
Notifications
|
||||
<div className="flex gap-2 pr-3 ">
|
||||
<button
|
||||
className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500"
|
||||
onClick={() => {
|
||||
closePopUp();
|
||||
setTimeout(clearNotificationList, 100);
|
||||
}}
|
||||
>
|
||||
<TrashIcon className="w-[1.1rem] h-[1.1rem]" />
|
||||
</button>
|
||||
<button
|
||||
className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500"
|
||||
onClick={closePopUp}
|
||||
>
|
||||
<XMarkIcon className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 flex flex-col overflow-y-scroll w-full h-full scrollbar-hide text-gray-900 dark:text-gray-300">
|
||||
{notificationList.length !== 0 ? (
|
||||
notificationList.map((alertItem, index) => (
|
||||
<SingleAlert
|
||||
key={alertItem.id}
|
||||
dropItem={alertItem}
|
||||
removeAlert={removeFromNotificationList}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="h-full w-full pb-16 text-gray-500 dark:text-gray-500 flex justify-center items-center">
|
||||
No new notifications
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
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"
|
||||
>
|
||||
<div className="flex pl-3 flex-row justify-between text-md font-medium text-gray-800 dark:text-gray-200">
|
||||
Notifications
|
||||
<div className="flex gap-2 pr-3 ">
|
||||
<button
|
||||
className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500"
|
||||
onClick={() => {
|
||||
closePopUp();
|
||||
setTimeout(clearNotificationList, 100);
|
||||
}}
|
||||
>
|
||||
<TrashIcon className="w-[1.1rem] h-[1.1rem]" />
|
||||
</button>
|
||||
<button
|
||||
className="text-gray-800 hover:text-red-500 dark:text-gray-200 dark:hover:text-red-500"
|
||||
onClick={closePopUp}
|
||||
>
|
||||
<XMarkIcon className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 flex flex-col overflow-y-scroll w-full h-full scrollbar-hide text-gray-900 dark:text-gray-300">
|
||||
{notificationList.length !== 0 ? (
|
||||
notificationList.map((alertItem, index) => (
|
||||
<SingleAlert
|
||||
key={alertItem.id}
|
||||
dropItem={alertItem}
|
||||
removeAlert={removeFromNotificationList}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="h-full w-full pb-16 text-gray-500 dark:text-gray-500 flex justify-center items-center">
|
||||
No new notifications
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,33 @@
|
|||
import { useEffect } from "react";
|
||||
|
||||
export function useOnClickOutside(ref, handler) {
|
||||
useEffect(() => {
|
||||
const listener = (event) => {
|
||||
// Do nothing if clicking ref's element or its children
|
||||
if (!ref.current || ref.current.contains(event.target)) {
|
||||
return;
|
||||
}
|
||||
useEffect(() => {
|
||||
const listener = (event) => {
|
||||
// Do nothing if clicking ref's element or its children
|
||||
if (!ref.current || ref.current.contains(event.target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
handler(event);
|
||||
};
|
||||
handler(event);
|
||||
};
|
||||
|
||||
// Attach the listener to the document
|
||||
document.addEventListener("mousedown", listener, { passive: true });
|
||||
// Attach the listener to the document
|
||||
document.addEventListener("mousedown", listener, { passive: true });
|
||||
|
||||
// Attach the listener to the react-flow instance
|
||||
const reactFlowContainer = document.querySelector(".react-flow");
|
||||
if (reactFlowContainer) {
|
||||
reactFlowContainer.addEventListener("mousedown", listener, {
|
||||
passive: true,
|
||||
});
|
||||
}
|
||||
// Attach the listener to the react-flow instance
|
||||
const reactFlowContainer = document.querySelector(".react-flow");
|
||||
if (reactFlowContainer) {
|
||||
reactFlowContainer.addEventListener("mousedown", listener, {
|
||||
passive: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Clean up the listener when the component is unmounted
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", listener);
|
||||
if (reactFlowContainer) {
|
||||
reactFlowContainer.removeEventListener("mousedown", listener);
|
||||
}
|
||||
};
|
||||
}, [ref, handler]); // Rerun only if ref or handler changes
|
||||
// Clean up the listener when the component is unmounted
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", listener);
|
||||
if (reactFlowContainer) {
|
||||
reactFlowContainer.removeEventListener("mousedown", listener);
|
||||
}
|
||||
};
|
||||
}, [ref, handler]); // Rerun only if ref or handler changes
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@ export default function CrashErrorComponent({ error, resetErrorBoundary }) {
|
|||
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="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">
|
||||
Please click the 'Reset Application' button
|
||||
to restore the application's state. If the error persists, please
|
||||
create an issue on our GitHub page. We apologize for any inconvenience
|
||||
this may have caused.
|
||||
Please click the 'Reset Application' button to restore the
|
||||
application's state. If the error persists, please create an issue on
|
||||
our GitHub page. We apologize for any inconvenience this may have
|
||||
caused.
|
||||
</p>
|
||||
<div className="flex justify-center">
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -6,119 +6,119 @@ import { classNames } from "../../utils";
|
|||
import { locationContext } from "../../contexts/locationContext";
|
||||
|
||||
export default function ExtraSidebar() {
|
||||
const {
|
||||
current,
|
||||
isStackedOpen,
|
||||
setIsStackedOpen,
|
||||
extraNavigation,
|
||||
extraComponent,
|
||||
} = useContext(locationContext);
|
||||
return (
|
||||
<>
|
||||
<aside
|
||||
className={` ${
|
||||
isStackedOpen ? "w-52" : "w-0 "
|
||||
} flex-shrink-0 flex overflow-hidden flex-col border-r dark:border-r-gray-700 transition-all duration-500`}
|
||||
>
|
||||
<div className="w-52 dark:bg-gray-800 border dark:border-gray-700 overflow-y-auto scrollbar-hide h-full flex flex-col items-start">
|
||||
<div className="flex pt-1 px-4 justify-between align-middle w-full">
|
||||
<span className="text-gray-900 dark:text-white py-[2px] font-medium ">
|
||||
{extraNavigation.title}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-grow flex-col w-full">
|
||||
{extraNavigation.options ? (
|
||||
<div className="p-4">
|
||||
<nav className="flex-1 space-y-1">
|
||||
{extraNavigation.options.map((item) =>
|
||||
!item.children ? (
|
||||
<div key={item.name}>
|
||||
<Link
|
||||
to={item.href}
|
||||
className={classNames(
|
||||
item.href.split("/")[2] === current[4]
|
||||
? "bg-gray-100 text-gray-900"
|
||||
: "bg-white text-gray-600 hover:bg-gray-50 hover:text-gray-900",
|
||||
"group w-full flex items-center pl-2 py-2 text-sm font-medium rounded-md"
|
||||
)}
|
||||
>
|
||||
<item.icon
|
||||
className={classNames(
|
||||
item.href.split("/")[2] === current[4]
|
||||
? "text-gray-500"
|
||||
: "text-gray-400 group-hover:text-gray-500",
|
||||
"mr-3 flex-shrink-0 h-6 w-6"
|
||||
)}
|
||||
/>
|
||||
{item.name}
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<Disclosure
|
||||
as="div"
|
||||
key={item.name}
|
||||
className="space-y-1"
|
||||
>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button
|
||||
className={classNames(
|
||||
item.href.split("/")[2] === current[4]
|
||||
? "bg-gray-100 text-gray-900"
|
||||
: "bg-white text-gray-600 hover:bg-gray-50 hover:text-gray-900",
|
||||
"group w-full flex items-center pl-2 pr-1 py-2 text-left text-sm font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
)}
|
||||
>
|
||||
<item.icon
|
||||
className="mr-3 h-6 w-6 flex-shrink-0 text-gray-400 group-hover:text-gray-500"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="flex-1">{item.name}</span>
|
||||
<svg
|
||||
className={classNames(
|
||||
open
|
||||
? "text-gray-400 rotate-90"
|
||||
: "text-gray-300",
|
||||
"ml-3 h-5 w-5 flex-shrink-0 transition-rotate duration-150 ease-in-out group-hover:text-gray-400"
|
||||
)}
|
||||
viewBox="0 0 20 20"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M6 6L14 10L6 14V6Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</Disclosure.Button>
|
||||
<Disclosure.Panel className="space-y-1">
|
||||
{item.children.map((subItem) => (
|
||||
<Link
|
||||
key={subItem.name}
|
||||
to={subItem.href}
|
||||
className={classNames(
|
||||
subItem.href.split("/")[3] === current[5]
|
||||
? "bg-gray-100 text-gray-900"
|
||||
: "bg-white text-gray-600 hover:bg-gray-50 hover:text-gray-900",
|
||||
"group flex w-full items-center rounded-md py-2 pl-11 pr-2 text-sm font-medium"
|
||||
)}
|
||||
>
|
||||
{subItem.name}
|
||||
</Link>
|
||||
))}
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
)
|
||||
)}
|
||||
</nav>
|
||||
</div>
|
||||
) : (
|
||||
extraComponent
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
const {
|
||||
current,
|
||||
isStackedOpen,
|
||||
setIsStackedOpen,
|
||||
extraNavigation,
|
||||
extraComponent,
|
||||
} = useContext(locationContext);
|
||||
return (
|
||||
<>
|
||||
<aside
|
||||
className={` ${
|
||||
isStackedOpen ? "w-52" : "w-0 "
|
||||
} flex-shrink-0 flex overflow-hidden flex-col border-r dark:border-r-gray-700 transition-all duration-500`}
|
||||
>
|
||||
<div className="w-52 dark:bg-gray-800 border dark:border-gray-700 overflow-y-auto scrollbar-hide h-full flex flex-col items-start">
|
||||
<div className="flex pt-1 px-4 justify-between align-middle w-full">
|
||||
<span className="text-gray-900 dark:text-white py-[2px] font-medium ">
|
||||
{extraNavigation.title}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-grow flex-col w-full">
|
||||
{extraNavigation.options ? (
|
||||
<div className="p-4">
|
||||
<nav className="flex-1 space-y-1">
|
||||
{extraNavigation.options.map((item) =>
|
||||
!item.children ? (
|
||||
<div key={item.name}>
|
||||
<Link
|
||||
to={item.href}
|
||||
className={classNames(
|
||||
item.href.split("/")[2] === current[4]
|
||||
? "bg-gray-100 text-gray-900"
|
||||
: "bg-white text-gray-600 hover:bg-gray-50 hover:text-gray-900",
|
||||
"group w-full flex items-center pl-2 py-2 text-sm font-medium rounded-md"
|
||||
)}
|
||||
>
|
||||
<item.icon
|
||||
className={classNames(
|
||||
item.href.split("/")[2] === current[4]
|
||||
? "text-gray-500"
|
||||
: "text-gray-400 group-hover:text-gray-500",
|
||||
"mr-3 flex-shrink-0 h-6 w-6"
|
||||
)}
|
||||
/>
|
||||
{item.name}
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
<Disclosure
|
||||
as="div"
|
||||
key={item.name}
|
||||
className="space-y-1"
|
||||
>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Disclosure.Button
|
||||
className={classNames(
|
||||
item.href.split("/")[2] === current[4]
|
||||
? "bg-gray-100 text-gray-900"
|
||||
: "bg-white text-gray-600 hover:bg-gray-50 hover:text-gray-900",
|
||||
"group w-full flex items-center pl-2 pr-1 py-2 text-left text-sm font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
)}
|
||||
>
|
||||
<item.icon
|
||||
className="mr-3 h-6 w-6 flex-shrink-0 text-gray-400 group-hover:text-gray-500"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="flex-1">{item.name}</span>
|
||||
<svg
|
||||
className={classNames(
|
||||
open
|
||||
? "text-gray-400 rotate-90"
|
||||
: "text-gray-300",
|
||||
"ml-3 h-5 w-5 flex-shrink-0 transition-rotate duration-150 ease-in-out group-hover:text-gray-400"
|
||||
)}
|
||||
viewBox="0 0 20 20"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
d="M6 6L14 10L6 14V6Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</Disclosure.Button>
|
||||
<Disclosure.Panel className="space-y-1">
|
||||
{item.children.map((subItem) => (
|
||||
<Link
|
||||
key={subItem.name}
|
||||
to={subItem.href}
|
||||
className={classNames(
|
||||
subItem.href.split("/")[3] === current[5]
|
||||
? "bg-gray-100 text-gray-900"
|
||||
: "bg-white text-gray-600 hover:bg-gray-50 hover:text-gray-900",
|
||||
"group flex w-full items-center rounded-md py-2 pl-11 pr-2 text-sm font-medium"
|
||||
)}
|
||||
>
|
||||
{subItem.name}
|
||||
</Link>
|
||||
))}
|
||||
</Disclosure.Panel>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
)
|
||||
)}
|
||||
</nav>
|
||||
</div>
|
||||
) : (
|
||||
extraComponent
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import { styled } from '@mui/material/styles';
|
||||
import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip';
|
||||
import { styled } from "@mui/material/styles";
|
||||
import Tooltip, { TooltipProps, tooltipClasses } from "@mui/material/Tooltip";
|
||||
|
||||
export const LightTooltip = styled(({ className, ...props }: TooltipProps) => (
|
||||
<Tooltip {...props} classes={{ popper: className }} />
|
||||
<Tooltip {...props} classes={{ popper: className }} />
|
||||
))(({ theme }) => ({
|
||||
[`& .${tooltipClasses.tooltip}`]: {
|
||||
backgroundColor: theme.palette.common.white,
|
||||
color: 'rgba(0, 0, 0, 0.87)',
|
||||
boxShadow: theme.shadows[2],
|
||||
fontSize: 14,
|
||||
},
|
||||
[`& .${tooltipClasses.arrow}:before`]: {
|
||||
color: theme.palette.common.white,
|
||||
boxShadow: theme.shadows[1],
|
||||
},
|
||||
}));
|
||||
[`& .${tooltipClasses.tooltip}`]: {
|
||||
backgroundColor: theme.palette.common.white,
|
||||
color: "rgba(0, 0, 0, 0.87)",
|
||||
boxShadow: theme.shadows[2],
|
||||
fontSize: 14,
|
||||
},
|
||||
[`& .${tooltipClasses.arrow}:before`]: {
|
||||
color: theme.palette.common.white,
|
||||
boxShadow: theme.shadows[1],
|
||||
},
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -2,6 +2,14 @@ import { ReactElement } from "react";
|
|||
import { LightTooltip } from "../LightTooltipComponent";
|
||||
import { TooltipComponentType } from "../../types/components";
|
||||
|
||||
export default function Tooltip({ children, title,placement }:TooltipComponentType) {
|
||||
return <LightTooltip placement={placement} title={title} arrow>{children}</LightTooltip>;
|
||||
export default function Tooltip({
|
||||
children,
|
||||
title,
|
||||
placement,
|
||||
}: TooltipComponentType) {
|
||||
return (
|
||||
<LightTooltip placement={placement} title={title} arrow>
|
||||
{children}
|
||||
</LightTooltip>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import { ChatBubbleLeftEllipsisIcon, ChatBubbleOvalLeftEllipsisIcon, PlusSmallIcon } from "@heroicons/react/24/outline";
|
||||
import {
|
||||
ChatBubbleLeftEllipsisIcon,
|
||||
ChatBubbleOvalLeftEllipsisIcon,
|
||||
PlusSmallIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { useState } from "react";
|
||||
import { ChatMessageType } from "../../../types/chat";
|
||||
import { nodeColors } from "../../../utils";
|
||||
var Convert = require('ansi-to-html');
|
||||
var convert = new Convert({newline:true});
|
||||
var Convert = require("ansi-to-html");
|
||||
var convert = new Convert({ newline: true });
|
||||
|
||||
export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
|
||||
const [hidden, setHidden] = useState(true);
|
||||
|
|
@ -29,13 +33,16 @@ export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
|
|||
style={{ backgroundColor: nodeColors["thought"] }}
|
||||
className=" text-start inline-block w-full pb-3 pt-3 px-5 cursor-pointer"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: convert.toHtml(chat.thought)
|
||||
__html: convert.toHtml(chat.thought),
|
||||
}}
|
||||
></div>
|
||||
)}
|
||||
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
|
||||
<div className="w-full rounded-b-md px-4 pb-3 pt-3 pr-8" style={{ backgroundColor: nodeColors["chat"] }}>
|
||||
{chat.message}
|
||||
<div
|
||||
className="w-full rounded-b-md px-4 pb-3 pt-3 pr-8"
|
||||
style={{ backgroundColor: nodeColors["chat"] }}
|
||||
>
|
||||
{chat.message}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,37 +1,45 @@
|
|||
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 { PopUpContext } from "../../../contexts/popUpContext";
|
||||
import { useContext } from "react";
|
||||
import ChatModal from "../../../modals/chatModal";
|
||||
|
||||
export default function ChatTrigger({open, setOpen,flow}){
|
||||
const {openPopUp} = useContext(PopUpContext)
|
||||
return(<Transition
|
||||
show={!open}
|
||||
appear={true}
|
||||
enter="transition ease-out duration-300"
|
||||
enterFrom="translate-y-96"
|
||||
enterTo="translate-y-0"
|
||||
leave="transition ease-in duration-300"
|
||||
leaveFrom="translate-y-0"
|
||||
leaveTo="translate-y-96"
|
||||
>
|
||||
<div className="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">
|
||||
<button
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<ChatBubbleBottomCenterTextIcon
|
||||
className="h-6 w-6 mt-1"
|
||||
style={{ color: "white" }}
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>)
|
||||
}
|
||||
export default function ChatTrigger({ open, setOpen }) {
|
||||
const { openPopUp } = useContext(PopUpContext);
|
||||
return (
|
||||
<Transition
|
||||
show={!open}
|
||||
appear={true}
|
||||
enter="transition ease-out duration-300"
|
||||
enterFrom="translate-y-96"
|
||||
enterTo="translate-y-0"
|
||||
leave="transition ease-in duration-300"
|
||||
leaveFrom="translate-y-0"
|
||||
leaveTo="translate-y-96"
|
||||
>
|
||||
<div className="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"
|
||||
>
|
||||
<button
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
<div className="flex gap-3 items-center">
|
||||
<ChatBubbleBottomCenterTextIcon
|
||||
className="h-6 w-6 mt-1"
|
||||
style={{ color: "white" }}
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,26 +7,26 @@ import ChatModal from "../../modals/chatModal";
|
|||
const _ = require("lodash");
|
||||
|
||||
export default function Chat({ flow }: ChatType) {
|
||||
const [open, setOpen] = useState(false);
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (
|
||||
(event.key === "K" || event.key === "k") &&
|
||||
(event.metaKey || event.ctrlKey)
|
||||
) {
|
||||
event.preventDefault();
|
||||
setOpen((oldState) => !oldState);
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
<ChatModal key={flow.id} flow={flow} open={open} setOpen={setOpen} />
|
||||
<ChatTrigger open={open} setOpen={setOpen} flow={flow} />
|
||||
</>
|
||||
);
|
||||
const [open, setOpen] = useState(false);
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (
|
||||
(event.key === "K" || event.key === "k") &&
|
||||
(event.metaKey || event.ctrlKey)
|
||||
) {
|
||||
event.preventDefault();
|
||||
setOpen((oldState) => !oldState);
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
<ChatModal key={flow.id} flow={flow} open={open} setOpen={setOpen} />
|
||||
<ChatTrigger open={open} setOpen={setOpen} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,90 +5,90 @@ import { DropDownComponentType } from "../../types/components";
|
|||
import { classNames } from "../../utils";
|
||||
|
||||
export default function Dropdown({
|
||||
value,
|
||||
options,
|
||||
onSelect,
|
||||
value,
|
||||
options,
|
||||
onSelect,
|
||||
}: DropDownComponentType) {
|
||||
let [internalValue, setInternalValue] = useState(
|
||||
value === "" || !value ? "Choose an option" : value
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Listbox
|
||||
value={internalValue}
|
||||
onChange={(value) => {
|
||||
setInternalValue(value);
|
||||
onSelect(value);
|
||||
}}
|
||||
>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<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">
|
||||
<span className="block w-max truncate">{internalValue}</span>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<ChevronUpDownIcon
|
||||
className="h-5 w-5 text-gray-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
</Listbox.Button>
|
||||
let [internalValue, setInternalValue] = useState(
|
||||
value === "" || !value ? "Choose an option" : value
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Listbox
|
||||
value={internalValue}
|
||||
onChange={(value) => {
|
||||
setInternalValue(value);
|
||||
onSelect(value);
|
||||
}}
|
||||
>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<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">
|
||||
<span className="block w-max truncate">{internalValue}</span>
|
||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||
<ChevronUpDownIcon
|
||||
className="h-5 w-5 text-gray-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
</Listbox.Button>
|
||||
|
||||
<Transition
|
||||
show={open}
|
||||
as={Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full 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) => (
|
||||
<Listbox.Option
|
||||
key={id}
|
||||
className={({ active }) =>
|
||||
classNames(
|
||||
active
|
||||
? "text-white bg-indigo-600 dark:bg-indigo-500"
|
||||
: "text-gray-900",
|
||||
"relative cursor-default select-none py-2 pl-3 pr-9 dark:text-gray-300 dark:bg-gray-800"
|
||||
)
|
||||
}
|
||||
value={option}
|
||||
>
|
||||
{({ selected, active }) => (
|
||||
<>
|
||||
<span
|
||||
className={classNames(
|
||||
selected ? "font-semibold" : "font-normal",
|
||||
"block truncate"
|
||||
)}
|
||||
>
|
||||
{option}
|
||||
</span>
|
||||
<Transition
|
||||
show={open}
|
||||
as={Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options className="absolute z-10 mt-1 max-h-60 w-full 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) => (
|
||||
<Listbox.Option
|
||||
key={id}
|
||||
className={({ active }) =>
|
||||
classNames(
|
||||
active
|
||||
? "text-white bg-indigo-600 dark:bg-indigo-500"
|
||||
: "text-gray-900",
|
||||
"relative cursor-default select-none py-2 pl-3 pr-9 dark:text-gray-300 dark:bg-gray-800"
|
||||
)
|
||||
}
|
||||
value={option}
|
||||
>
|
||||
{({ selected, active }) => (
|
||||
<>
|
||||
<span
|
||||
className={classNames(
|
||||
selected ? "font-semibold" : "font-normal",
|
||||
"block truncate"
|
||||
)}
|
||||
>
|
||||
{option}
|
||||
</span>
|
||||
|
||||
{selected ? (
|
||||
<span
|
||||
className={classNames(
|
||||
active ? "text-white" : "text-indigo-600",
|
||||
"absolute inset-y-0 right-0 flex items-center pr-4"
|
||||
)}
|
||||
>
|
||||
<CheckIcon
|
||||
className="h-5 w-5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Listbox>
|
||||
</>
|
||||
);
|
||||
{selected ? (
|
||||
<span
|
||||
className={classNames(
|
||||
active ? "text-white" : "text-indigo-600",
|
||||
"absolute inset-y-0 right-0 flex items-center pr-4"
|
||||
)}
|
||||
>
|
||||
<CheckIcon
|
||||
className="h-5 w-5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Listbox>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,33 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { FloatComponentType } from "../../types/components";
|
||||
|
||||
export default function FloatComponent({value, onChange, disabled}: FloatComponentType){
|
||||
const [myValue, setMyValue] = useState(value ?? "");
|
||||
useEffect(()=> {
|
||||
if(disabled){
|
||||
setMyValue("");
|
||||
onChange("");
|
||||
}
|
||||
}, [disabled, onChange])
|
||||
return (
|
||||
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
|
||||
<input
|
||||
type="number"
|
||||
value={myValue}
|
||||
className={"block w-full form-input dark:bg-gray-900 arrow-hide dark:border-gray-600 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" + (disabled ? " bg-gray-200 dark:bg-gray-700" : "")}
|
||||
placeholder="Type a number from zero to one"
|
||||
onChange={(e) => {
|
||||
setMyValue(e.target.value);
|
||||
onChange(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default function FloatComponent({
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
}: FloatComponentType) {
|
||||
const [myValue, setMyValue] = useState(value ?? "");
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
setMyValue("");
|
||||
onChange("");
|
||||
}
|
||||
}, [disabled, onChange]);
|
||||
return (
|
||||
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
|
||||
<input
|
||||
type="number"
|
||||
value={myValue}
|
||||
className={
|
||||
"block w-full form-input dark:bg-gray-900 arrow-hide dark:border-gray-600 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" +
|
||||
(disabled ? " bg-gray-200 dark:bg-gray-700" : "")
|
||||
}
|
||||
placeholder="Type a number from zero to one"
|
||||
onChange={(e) => {
|
||||
setMyValue(e.target.value);
|
||||
onChange(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,84 +3,84 @@ import { InputComponentType } from "../../types/components";
|
|||
import { classNames } from "../../utils";
|
||||
|
||||
export default function InputComponent({
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
password,
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
password,
|
||||
}: InputComponentType) {
|
||||
const [myValue, setMyValue] = useState(value ?? "");
|
||||
const [pwdVisible, setPwdVisible] = useState(false);
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
setMyValue("");
|
||||
onChange("");
|
||||
}
|
||||
}, [disabled, onChange]);
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
disabled
|
||||
? "relative pointer-events-none cursor-not-allowed"
|
||||
: "relative"
|
||||
}
|
||||
>
|
||||
<input
|
||||
value={myValue}
|
||||
className={classNames(
|
||||
"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" : ""
|
||||
)}
|
||||
placeholder="Type a text"
|
||||
onChange={(e) => {
|
||||
setMyValue(e.target.value);
|
||||
onChange(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
className="absolute inset-y-0 right-0 items-center px-4 text-gray-600"
|
||||
onClick={() => {
|
||||
setPwdVisible(!pwdVisible);
|
||||
}}
|
||||
>
|
||||
{password &&
|
||||
(pwdVisible ? (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
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"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
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"
|
||||
/>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
/>
|
||||
</svg>
|
||||
))}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
const [myValue, setMyValue] = useState(value ?? "");
|
||||
const [pwdVisible, setPwdVisible] = useState(false);
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
setMyValue("");
|
||||
onChange("");
|
||||
}
|
||||
}, [disabled, onChange]);
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
disabled
|
||||
? "relative pointer-events-none cursor-not-allowed"
|
||||
: "relative"
|
||||
}
|
||||
>
|
||||
<input
|
||||
value={myValue}
|
||||
className={classNames(
|
||||
"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" : ""
|
||||
)}
|
||||
placeholder="Type a text"
|
||||
onChange={(e) => {
|
||||
setMyValue(e.target.value);
|
||||
onChange(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
className="absolute inset-y-0 right-0 items-center px-4 text-gray-600"
|
||||
onClick={() => {
|
||||
setPwdVisible(!pwdVisible);
|
||||
}}
|
||||
>
|
||||
{password &&
|
||||
(pwdVisible ? (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
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"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
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"
|
||||
/>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
/>
|
||||
</svg>
|
||||
))}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export default function InputFileComponent({
|
|||
disabled,
|
||||
suffixes,
|
||||
fileTypes,
|
||||
onFileChange
|
||||
onFileChange,
|
||||
}: FileComponentType) {
|
||||
const [myValue, setMyValue] = useState(value);
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
|
|
@ -17,23 +17,23 @@ export default function InputFileComponent({
|
|||
if (disabled) {
|
||||
setMyValue("");
|
||||
onChange("");
|
||||
onFileChange("")
|
||||
onFileChange("");
|
||||
}
|
||||
}, [disabled, onChange]);
|
||||
|
||||
function attachFile(fileReadEvent: ProgressEvent<FileReader>) {
|
||||
fileReadEvent.preventDefault();
|
||||
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++) {
|
||||
if(fileName.endsWith(suffixes[index])){
|
||||
return true
|
||||
if (fileName.endsWith(suffixes[index])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
const handleButtonClick = () => {
|
||||
|
|
@ -69,7 +69,7 @@ export default function InputFileComponent({
|
|||
>
|
||||
<div className="w-full flex items-center gap-3">
|
||||
<span
|
||||
onClick={handleButtonClick}
|
||||
onClick={handleButtonClick}
|
||||
className={
|
||||
"truncate block w-full text-gray-500 px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" +
|
||||
(disabled ? " bg-gray-200" : "")
|
||||
|
|
|
|||
|
|
@ -4,51 +4,73 @@ import { InputListComponentType } from "../../types/components";
|
|||
|
||||
var _ = require("lodash");
|
||||
|
||||
export default function InputListComponent({ value, onChange, disabled}:InputListComponentType) {
|
||||
const [inputList, setInputList] = useState(value ?? [""]);
|
||||
useEffect(()=> {
|
||||
if(disabled){
|
||||
setInputList([""]);
|
||||
onChange([""]);
|
||||
}
|
||||
}, [disabled, onChange])
|
||||
return (
|
||||
<div className={(disabled ? "pointer-events-none cursor-not-allowed" : "") + "flex flex-col gap-3"}>
|
||||
{inputList.map((i, idx) => (
|
||||
<div key={idx} className="w-full flex gap-3">
|
||||
<input
|
||||
type="text"
|
||||
value={i}
|
||||
className={"block w-full form-input rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" + (disabled ? " bg-gray-200" : "")}
|
||||
placeholder="Type a text"
|
||||
onChange={(e) => {
|
||||
setInputList((old) => {
|
||||
let newInputList = _.cloneDeep(old);
|
||||
newInputList[idx] = e.target.value;
|
||||
return newInputList;
|
||||
});
|
||||
onChange(inputList);
|
||||
}}
|
||||
/>
|
||||
{idx === inputList.length - 1 ?
|
||||
<button onClick={() => {setInputList((old) => {
|
||||
let newInputList = _.cloneDeep(old);
|
||||
newInputList.push('');
|
||||
return newInputList;
|
||||
});
|
||||
onChange(inputList);}}>
|
||||
<PlusIcon className="w-4 h-4 hover:text-blue-600" />
|
||||
</button>
|
||||
: <button onClick={() => {setInputList((old) => {
|
||||
let newInputList = _.cloneDeep(old);
|
||||
newInputList.splice(idx, 1);
|
||||
return newInputList;
|
||||
});
|
||||
onChange(inputList);}}>
|
||||
<XMarkIcon className="w-4 h-4 hover:text-red-600" />
|
||||
</button>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
export default function InputListComponent({
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
}: InputListComponentType) {
|
||||
const [inputList, setInputList] = useState(value ?? [""]);
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
setInputList([""]);
|
||||
onChange([""]);
|
||||
}
|
||||
}, [disabled, onChange]);
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
(disabled ? "pointer-events-none cursor-not-allowed" : "") +
|
||||
"flex flex-col gap-3"
|
||||
}
|
||||
>
|
||||
{inputList.map((i, idx) => (
|
||||
<div key={idx} className="w-full flex gap-3">
|
||||
<input
|
||||
type="text"
|
||||
value={i}
|
||||
className={
|
||||
"block w-full form-input rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" +
|
||||
(disabled ? " bg-gray-200" : "")
|
||||
}
|
||||
placeholder="Type a text"
|
||||
onChange={(e) => {
|
||||
setInputList((old) => {
|
||||
let newInputList = _.cloneDeep(old);
|
||||
newInputList[idx] = e.target.value;
|
||||
return newInputList;
|
||||
});
|
||||
onChange(inputList);
|
||||
}}
|
||||
/>
|
||||
{idx === inputList.length - 1 ? (
|
||||
<button
|
||||
onClick={() => {
|
||||
setInputList((old) => {
|
||||
let newInputList = _.cloneDeep(old);
|
||||
newInputList.push("");
|
||||
return newInputList;
|
||||
});
|
||||
onChange(inputList);
|
||||
}}
|
||||
>
|
||||
<PlusIcon className="w-4 h-4 hover:text-blue-600" />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => {
|
||||
setInputList((old) => {
|
||||
let newInputList = _.cloneDeep(old);
|
||||
newInputList.splice(idx, 1);
|
||||
return newInputList;
|
||||
});
|
||||
onChange(inputList);
|
||||
}}
|
||||
>
|
||||
<XMarkIcon className="w-4 h-4 hover:text-red-600" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,12 +14,23 @@ export default function IntComponent({
|
|||
}
|
||||
}, [disabled, onChange]);
|
||||
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
|
||||
onKeyDown={(event) => {
|
||||
if (event.key !== 'Backspace' && event.key !== 'Enter' && event.key !== 'Delete' && event.key !== 'ArrowLeft' && event.key !== 'ArrowRight' && !/^[-]?\d*$/.test(event.key)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
if (
|
||||
event.key !== "Backspace" &&
|
||||
event.key !== "Enter" &&
|
||||
event.key !== "Delete" &&
|
||||
event.key !== "ArrowLeft" &&
|
||||
event.key !== "ArrowRight" &&
|
||||
!/^[-]?\d*$/.test(event.key)
|
||||
) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}}
|
||||
type="number"
|
||||
value={myValue}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,28 @@
|
|||
type LoadingComponentProps={
|
||||
remSize:number
|
||||
type LoadingComponentProps = {
|
||||
remSize: number;
|
||||
};
|
||||
|
||||
export default function LoadingComponent({ remSize }: LoadingComponentProps) {
|
||||
return (
|
||||
<div role="status" className="w-min m-auto">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className={`w-${remSize} h-${remSize} mr-2 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600`}
|
||||
viewBox="0 0 100 101"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
||||
fill="currentFill"
|
||||
/>
|
||||
</svg>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
|
@ -4,30 +4,57 @@ import { PopUpContext } from "../../contexts/popUpContext";
|
|||
import TextAreaModal from "../../modals/textAreaModal";
|
||||
import { TextAreaComponentType } from "../../types/components";
|
||||
|
||||
export default function TextAreaComponent({ value, onChange, disabled }:TextAreaComponentType) {
|
||||
const [myValue, setMyValue] = useState(value);
|
||||
const { openPopUp } = useContext(PopUpContext);
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
setMyValue("");
|
||||
onChange("");
|
||||
}
|
||||
}, [disabled, onChange]);
|
||||
return (
|
||||
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
|
||||
<div className="w-full flex items-center gap-3">
|
||||
<span onClick={()=>{openPopUp(<TextAreaModal value={myValue} setValue={(t:string) => {setMyValue(t); onChange(t);}}/>)}}
|
||||
className={
|
||||
"truncate block w-full text-gray-500 px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" +
|
||||
(disabled ? " bg-gray-200" : "")
|
||||
}
|
||||
>
|
||||
{myValue !== "" ? myValue : 'Text empty'}
|
||||
</span>
|
||||
<button onClick={()=>{openPopUp(<TextAreaModal value={myValue} setValue={(t:string) => {setMyValue(t); onChange(t);}}/>)}}>
|
||||
<ArrowTopRightOnSquareIcon className="w-6 h-6 hover:text-blue-600" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
export default function TextAreaComponent({
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
}: TextAreaComponentType) {
|
||||
const [myValue, setMyValue] = useState(value);
|
||||
const { openPopUp } = useContext(PopUpContext);
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
setMyValue("");
|
||||
onChange("");
|
||||
}
|
||||
}, [disabled, onChange]);
|
||||
return (
|
||||
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
|
||||
<div className="w-full flex items-center gap-3">
|
||||
<span
|
||||
onClick={() => {
|
||||
openPopUp(
|
||||
<TextAreaModal
|
||||
value={myValue}
|
||||
setValue={(t: string) => {
|
||||
setMyValue(t);
|
||||
onChange(t);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
className={
|
||||
"truncate block w-full text-gray-500 px-3 py-2 rounded-md border border-gray-300 dark:border-gray-700 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm" +
|
||||
(disabled ? " bg-gray-200" : "")
|
||||
}
|
||||
>
|
||||
{myValue !== "" ? myValue : "Text empty"}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => {
|
||||
openPopUp(
|
||||
<TextAreaModal
|
||||
value={myValue}
|
||||
setValue={(t: string) => {
|
||||
setMyValue(t);
|
||||
onChange(t);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
>
|
||||
<ArrowTopRightOnSquareIcon className="w-6 h-6 hover:text-blue-600" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,73 +3,80 @@ import { classNames } from "../../utils";
|
|||
import { useEffect } from "react";
|
||||
import { ToggleComponentType } from "../../types/components";
|
||||
|
||||
export default function ToggleComponent({ enabled, setEnabled, disabled }:ToggleComponentType) {
|
||||
useEffect(()=> {
|
||||
if(disabled){
|
||||
setEnabled(false);
|
||||
}
|
||||
}, [disabled, setEnabled])
|
||||
return (
|
||||
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
|
||||
<Switch
|
||||
checked={enabled}
|
||||
onChange={(x:boolean) => {
|
||||
setEnabled(x);
|
||||
}}
|
||||
className={classNames(
|
||||
enabled ? "bg-indigo-600" : "bg-gray-200 dark:bg-gray-600",
|
||||
"relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out "
|
||||
)}
|
||||
>
|
||||
<span className="sr-only">Use setting</span>
|
||||
<span
|
||||
className={classNames(
|
||||
enabled ? "translate-x-5" : "translate-x-0",
|
||||
"pointer-events-none relative inline-block h-5 w-5 transform rounded-full shadow ring-0 transition duration-200 ease-in-out", disabled ? "bg-gray-200 dark:bg-gray-600" : "bg-white dark:bg-gray-800"
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={classNames(
|
||||
enabled
|
||||
? "opacity-0 ease-out duration-100"
|
||||
: "opacity-100 ease-in duration-200",
|
||||
"absolute inset-0 flex h-full w-full items-center justify-center transition-opacity"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
className="h-3 w-3 text-gray-400"
|
||||
fill="none"
|
||||
viewBox="0 0 12 12"
|
||||
>
|
||||
<path
|
||||
d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
className={classNames(
|
||||
enabled
|
||||
? "opacity-100 ease-in duration-200"
|
||||
: "opacity-0 ease-out duration-100",
|
||||
"absolute inset-0 flex h-full w-full items-center justify-center transition-opacity"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
className="h-3 w-3 text-indigo-600"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 12 12"
|
||||
>
|
||||
<path d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z" />
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
export default function ToggleComponent({
|
||||
enabled,
|
||||
setEnabled,
|
||||
disabled,
|
||||
}: ToggleComponentType) {
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
setEnabled(false);
|
||||
}
|
||||
}, [disabled, setEnabled]);
|
||||
return (
|
||||
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
|
||||
<Switch
|
||||
checked={enabled}
|
||||
onChange={(x: boolean) => {
|
||||
setEnabled(x);
|
||||
}}
|
||||
className={classNames(
|
||||
enabled ? "bg-indigo-600" : "bg-gray-200 dark:bg-gray-600",
|
||||
"relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out "
|
||||
)}
|
||||
>
|
||||
<span className="sr-only">Use setting</span>
|
||||
<span
|
||||
className={classNames(
|
||||
enabled ? "translate-x-5" : "translate-x-0",
|
||||
"pointer-events-none relative inline-block h-5 w-5 transform rounded-full shadow ring-0 transition duration-200 ease-in-out",
|
||||
disabled
|
||||
? "bg-gray-200 dark:bg-gray-600"
|
||||
: "bg-white dark:bg-gray-800"
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={classNames(
|
||||
enabled
|
||||
? "opacity-0 ease-out duration-100"
|
||||
: "opacity-100 ease-in duration-200",
|
||||
"absolute inset-0 flex h-full w-full items-center justify-center transition-opacity"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
className="h-3 w-3 text-gray-400"
|
||||
fill="none"
|
||||
viewBox="0 0 12 12"
|
||||
>
|
||||
<path
|
||||
d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2"
|
||||
stroke="currentColor"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
className={classNames(
|
||||
enabled
|
||||
? "opacity-100 ease-in duration-200"
|
||||
: "opacity-0 ease-out duration-100",
|
||||
"absolute inset-0 flex h-full w-full items-center justify-center transition-opacity"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
className="h-3 w-3 text-indigo-600"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 12 12"
|
||||
>
|
||||
<path d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z" />
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,13 +20,13 @@ type alertContextType = {
|
|||
notificationCenter: boolean;
|
||||
setNotificationCenter: (newState: boolean) => void;
|
||||
notificationList: Array<AlertItemType>;
|
||||
pushNotificationList: (Object:AlertItemType) => void;
|
||||
pushNotificationList: (Object: AlertItemType) => void;
|
||||
clearNotificationList: () => void;
|
||||
removeFromNotificationList: (index: string) => void;
|
||||
};
|
||||
|
||||
//initial values to alertContextType
|
||||
const initialValue:alertContextType = {
|
||||
const initialValue: alertContextType = {
|
||||
errorData: { title: "", list: [] },
|
||||
setErrorData: () => {},
|
||||
errorOpen: false,
|
||||
|
|
@ -49,7 +49,7 @@ const initialValue:alertContextType = {
|
|||
|
||||
export const alertContext = createContext<alertContextType>(initialValue);
|
||||
|
||||
export function AlertProvider({ children }:{children:ReactNode}) {
|
||||
export function AlertProvider({ children }: { children: ReactNode }) {
|
||||
const [errorData, setErrorDataState] = useState<{
|
||||
title: string;
|
||||
list?: Array<string>;
|
||||
|
|
@ -73,68 +73,69 @@ export function AlertProvider({ children }:{children:ReactNode}) {
|
|||
return newNotificationList;
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Sets the error data state, opens the error dialog and pushes the new error notification to the notification list
|
||||
* @param newState An object containing the new error data, including title and optional list of error messages
|
||||
*/
|
||||
function setErrorData(newState: { title: string; list?: Array<string> }) {
|
||||
setErrorDataState(newState);
|
||||
setErrorOpen(true);
|
||||
if (newState.title) {
|
||||
setNotificationCenter(true);
|
||||
pushNotificationList({
|
||||
type: "error",
|
||||
title: newState.title,
|
||||
list: newState.list,
|
||||
id: _.uniqueId(),
|
||||
});
|
||||
/**
|
||||
* Sets the error data state, opens the error dialog and pushes the new error notification to the notification list
|
||||
* @param newState An object containing the new error data, including title and optional list of error messages
|
||||
*/
|
||||
function setErrorData(newState: { title: string; list?: Array<string> }) {
|
||||
setErrorDataState(newState);
|
||||
setErrorOpen(true);
|
||||
if (newState.title) {
|
||||
setNotificationCenter(true);
|
||||
pushNotificationList({
|
||||
type: "error",
|
||||
title: newState.title,
|
||||
list: newState.list,
|
||||
id: _.uniqueId(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Sets the state of the notice data and opens the notice modal, also adds a new notice to the notification center if the title is defined.
|
||||
* @param newState An object containing the title of the notice and optionally a link.
|
||||
*/
|
||||
function setNoticeData(newState: { title: string; link?: string }) {
|
||||
setNoticeDataState(newState);
|
||||
setNoticeOpen(true);
|
||||
if (newState.title) {
|
||||
// Add new notice to notification center
|
||||
setNotificationCenter(true);
|
||||
pushNotificationList({
|
||||
type: "notice",
|
||||
title: newState.title,
|
||||
link: newState.link,
|
||||
id: _.uniqueId(),
|
||||
});
|
||||
/**
|
||||
* Sets the state of the notice data and opens the notice modal, also adds a new notice to the notification center if the title is defined.
|
||||
* @param newState An object containing the title of the notice and optionally a link.
|
||||
*/
|
||||
function setNoticeData(newState: { title: string; link?: string }) {
|
||||
setNoticeDataState(newState);
|
||||
setNoticeOpen(true);
|
||||
if (newState.title) {
|
||||
// Add new notice to notification center
|
||||
setNotificationCenter(true);
|
||||
pushNotificationList({
|
||||
type: "notice",
|
||||
title: newState.title,
|
||||
link: newState.link,
|
||||
id: _.uniqueId(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Update the success data state and show a success alert notification.
|
||||
* @param newState - A state object with a "title" property to set in the success data state.
|
||||
*/
|
||||
function setSuccessData(newState: { title: string }) {
|
||||
setSuccessDataState(newState); // update the success data state with the provided new state
|
||||
setSuccessOpen(true); // open the success alert
|
||||
/**
|
||||
* Update the success data state and show a success alert notification.
|
||||
* @param newState - A state object with a "title" property to set in the success data state.
|
||||
*/
|
||||
function setSuccessData(newState: { title: string }) {
|
||||
setSuccessDataState(newState); // update the success data state with the provided new state
|
||||
setSuccessOpen(true); // open the success alert
|
||||
|
||||
// If the new state has a "title" property, add a new success notification to the list
|
||||
if (newState.title) {
|
||||
setNotificationCenter(true); // show the notification center
|
||||
pushNotificationList({ // add the new notification to the list
|
||||
type: "success",
|
||||
title: newState.title,
|
||||
id: _.uniqueId(),
|
||||
});
|
||||
// If the new state has a "title" property, add a new success notification to the list
|
||||
if (newState.title) {
|
||||
setNotificationCenter(true); // show the notification center
|
||||
pushNotificationList({
|
||||
// add the new notification to the list
|
||||
type: "success",
|
||||
title: newState.title,
|
||||
id: _.uniqueId(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
function clearNotificationList() {
|
||||
setNotificationList([]);
|
||||
}
|
||||
function removeFromNotificationList(index: string) {
|
||||
// set the notification list to a new array that filters out the alert with the matching id
|
||||
setNotificationList((prevAlertsList) =>
|
||||
prevAlertsList.filter((alert) => alert.id !== index)
|
||||
prevAlertsList.filter((alert) => alert.id !== index)
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<alertContext.Provider
|
||||
value={{
|
||||
|
|
|
|||
|
|
@ -14,13 +14,13 @@ export const darkContext = createContext<darkContextType>(initialValue);
|
|||
|
||||
export function DarkProvider({ children }) {
|
||||
const [dark, setDark] = useState(false);
|
||||
useEffect(()=>{
|
||||
if(dark){
|
||||
useEffect(() => {
|
||||
if (dark) {
|
||||
document.getElementById("body").classList.add("dark");
|
||||
} else {
|
||||
document.getElementById("body").classList.remove("dark");
|
||||
}
|
||||
}, [dark])
|
||||
}, [dark]);
|
||||
return (
|
||||
<darkContext.Provider
|
||||
value={{
|
||||
|
|
@ -31,4 +31,4 @@ export function DarkProvider({ children }) {
|
|||
{children}
|
||||
</darkContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ type locationContextType = {
|
|||
|
||||
//initial value for location context
|
||||
const initialValue = {
|
||||
//actual
|
||||
//actual
|
||||
current: window.location.pathname.replace(/\/$/g, "").split("/"),
|
||||
isStackedOpen:
|
||||
window.innerWidth > 1024 && window.location.pathname.split("/")[1]
|
||||
|
|
@ -50,7 +50,7 @@ const 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 [isStackedOpen, setIsStackedOpen] = useState(
|
||||
initialValue.isStackedOpen
|
||||
|
|
|
|||
|
|
@ -3,31 +3,31 @@ import React, { useState } from "react";
|
|||
|
||||
// context to set JSX element on the DOM
|
||||
export const PopUpContext = createContext({
|
||||
openPopUp: (popUpElement: JSX.Element) => {},
|
||||
closePopUp: () => {},
|
||||
openPopUp: (popUpElement: JSX.Element) => {},
|
||||
closePopUp: () => {},
|
||||
});
|
||||
|
||||
interface PopUpProviderProps {
|
||||
children: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const PopUpProvider = ({ children }: PopUpProviderProps) => {
|
||||
const [popUpElements, setPopUpElements] = useState<JSX.Element[]>([]);
|
||||
const [popUpElements, setPopUpElements] = useState<JSX.Element[]>([]);
|
||||
|
||||
const openPopUp = (element: JSX.Element) => {
|
||||
setPopUpElements(prevPopUps => [element, ...prevPopUps]);
|
||||
};
|
||||
const openPopUp = (element: JSX.Element) => {
|
||||
setPopUpElements((prevPopUps) => [element, ...prevPopUps]);
|
||||
};
|
||||
|
||||
const closePopUp = () => {
|
||||
setPopUpElements(prevPopUps => prevPopUps.slice(1));
|
||||
};
|
||||
const closePopUp = () => {
|
||||
setPopUpElements((prevPopUps) => prevPopUps.slice(1));
|
||||
};
|
||||
|
||||
return (
|
||||
<PopUpContext.Provider value={{ openPopUp, closePopUp }}>
|
||||
{children}
|
||||
{popUpElements[0]}
|
||||
</PopUpContext.Provider>
|
||||
);
|
||||
return (
|
||||
<PopUpContext.Provider value={{ openPopUp, closePopUp }}>
|
||||
{children}
|
||||
{popUpElements[0]}
|
||||
</PopUpContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default PopUpProvider;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import {
|
||||
createContext,
|
||||
useEffect,
|
||||
useState,
|
||||
useRef,
|
||||
ReactNode,
|
||||
useContext,
|
||||
createContext,
|
||||
useEffect,
|
||||
useState,
|
||||
useRef,
|
||||
ReactNode,
|
||||
useContext,
|
||||
} from "react";
|
||||
import { FlowType } from "../types/flow";
|
||||
import { LangFlowState, TabsContextType } from "../types/tabs";
|
||||
|
|
@ -15,221 +15,221 @@ import { APITemplateType, TemplateVariableType } from "../types/api";
|
|||
const { v4: uuidv4 } = require("uuid");
|
||||
|
||||
const TabsContextInitialValue: TabsContextType = {
|
||||
save: () => {},
|
||||
tabIndex: 0,
|
||||
setTabIndex: (index: number) => {},
|
||||
flows: [],
|
||||
removeFlow: (id: string) => {},
|
||||
addFlow: (flowData?: any) => {},
|
||||
updateFlow: (newFlow: FlowType) => {},
|
||||
incrementNodeId: () => 0,
|
||||
downloadFlow: (flow: FlowType) => {},
|
||||
uploadFlow: () => {},
|
||||
hardReset: () => {},
|
||||
save: () => {},
|
||||
tabIndex: 0,
|
||||
setTabIndex: (index: number) => {},
|
||||
flows: [],
|
||||
removeFlow: (id: string) => {},
|
||||
addFlow: (flowData?: any) => {},
|
||||
updateFlow: (newFlow: FlowType) => {},
|
||||
incrementNodeId: () => 0,
|
||||
downloadFlow: (flow: FlowType) => {},
|
||||
uploadFlow: () => {},
|
||||
hardReset: () => {},
|
||||
};
|
||||
|
||||
export const TabsContext = createContext<TabsContextType>(
|
||||
TabsContextInitialValue
|
||||
TabsContextInitialValue
|
||||
);
|
||||
|
||||
export function TabsProvider({ children }: { children: ReactNode }) {
|
||||
const { setNoticeData } = useContext(alertContext);
|
||||
const [tabIndex, setTabIndex] = useState(0);
|
||||
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
||||
const [id, setId] = useState("");
|
||||
const { templates } = useContext(typesContext);
|
||||
const { setNoticeData } = useContext(alertContext);
|
||||
const [tabIndex, setTabIndex] = useState(0);
|
||||
const [flows, setFlows] = useState<Array<FlowType>>([]);
|
||||
const [id, setId] = useState("");
|
||||
const { templates } = useContext(typesContext);
|
||||
|
||||
const newNodeId = useRef(0);
|
||||
function incrementNodeId() {
|
||||
newNodeId.current = newNodeId.current + 1;
|
||||
return newNodeId.current;
|
||||
}
|
||||
function save() {
|
||||
if (flows.length !== 0)
|
||||
window.localStorage.setItem(
|
||||
"tabsData",
|
||||
JSON.stringify({ tabIndex, flows, id, nodeId: newNodeId.current })
|
||||
);
|
||||
}
|
||||
useEffect(() => {
|
||||
//save tabs locally
|
||||
save();
|
||||
}, [flows, id, tabIndex, newNodeId]);
|
||||
const newNodeId = useRef(0);
|
||||
function incrementNodeId() {
|
||||
newNodeId.current = newNodeId.current + 1;
|
||||
return newNodeId.current;
|
||||
}
|
||||
function save() {
|
||||
if (flows.length !== 0)
|
||||
window.localStorage.setItem(
|
||||
"tabsData",
|
||||
JSON.stringify({ tabIndex, flows, id, nodeId: newNodeId.current })
|
||||
);
|
||||
}
|
||||
useEffect(() => {
|
||||
//save tabs locally
|
||||
save();
|
||||
}, [flows, id, tabIndex, newNodeId]);
|
||||
|
||||
useEffect(() => {
|
||||
//get tabs locally saved
|
||||
let cookie = window.localStorage.getItem("tabsData");
|
||||
if (cookie && Object.keys(templates).length > 0) {
|
||||
let cookieObject: LangFlowState = JSON.parse(cookie);
|
||||
cookieObject.flows.forEach((flow) => {
|
||||
flow.data.nodes.forEach((node) => {
|
||||
if (Object.keys(templates[node.data.type]["template"]).length > 0) {
|
||||
node.data.node.template = updateTemplate(
|
||||
templates[node.data.type][
|
||||
"template"
|
||||
] as unknown as APITemplateType,
|
||||
useEffect(() => {
|
||||
//get tabs locally saved
|
||||
let cookie = window.localStorage.getItem("tabsData");
|
||||
if (cookie && Object.keys(templates).length > 0) {
|
||||
let cookieObject: LangFlowState = JSON.parse(cookie);
|
||||
cookieObject.flows.forEach((flow) => {
|
||||
flow.data.nodes.forEach((node) => {
|
||||
if (Object.keys(templates[node.data.type]["template"]).length > 0) {
|
||||
node.data.node.template = updateTemplate(
|
||||
templates[node.data.type][
|
||||
"template"
|
||||
] as unknown as APITemplateType,
|
||||
|
||||
node.data.node.template as APITemplateType
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
setTabIndex(cookieObject.tabIndex);
|
||||
setFlows(cookieObject.flows);
|
||||
setId(cookieObject.id);
|
||||
newNodeId.current = cookieObject.nodeId;
|
||||
}
|
||||
}, [templates]);
|
||||
function hardReset() {
|
||||
newNodeId.current = 0;
|
||||
setTabIndex(0);
|
||||
setFlows([]);
|
||||
setId("");
|
||||
}
|
||||
node.data.node.template as APITemplateType
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
setTabIndex(cookieObject.tabIndex);
|
||||
setFlows(cookieObject.flows);
|
||||
setId(cookieObject.id);
|
||||
newNodeId.current = cookieObject.nodeId;
|
||||
}
|
||||
}, [templates]);
|
||||
function hardReset() {
|
||||
newNodeId.current = 0;
|
||||
setTabIndex(0);
|
||||
setFlows([]);
|
||||
setId("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the current flow as a JSON file
|
||||
*/
|
||||
function downloadFlow(flow: FlowType) {
|
||||
// create a data URI with the current flow data
|
||||
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
|
||||
JSON.stringify(flow)
|
||||
)}`;
|
||||
/**
|
||||
* Downloads the current flow as a JSON file
|
||||
*/
|
||||
function downloadFlow(flow: FlowType) {
|
||||
// create a data URI with the current flow data
|
||||
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
|
||||
JSON.stringify(flow)
|
||||
)}`;
|
||||
|
||||
// create a link element and set its properties
|
||||
const link = document.createElement("a");
|
||||
link.href = jsonString;
|
||||
link.download = `${flows[tabIndex].name}.json`;
|
||||
// create a link element and set its properties
|
||||
const link = document.createElement("a");
|
||||
link.href = jsonString;
|
||||
link.download = `${flows[tabIndex].name}.json`;
|
||||
|
||||
// simulate a click on the link element to trigger the download
|
||||
link.click();
|
||||
setNoticeData({
|
||||
title: "Warning: Critical data,JSON file may including API keys.",
|
||||
});
|
||||
}
|
||||
// simulate a click on the link element to trigger the download
|
||||
link.click();
|
||||
setNoticeData({
|
||||
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.
|
||||
* If the file type is application/json, the file is read and parsed into a JSON object.
|
||||
* The resulting JSON object is passed to the addFlow function.
|
||||
*/
|
||||
function uploadFlow() {
|
||||
// create a file input
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
// add a change event listener to the file input
|
||||
input.onchange = (e: Event) => {
|
||||
// check if the file type is application/json
|
||||
if ((e.target as HTMLInputElement).files[0].type === "application/json") {
|
||||
// get the file from the file input
|
||||
const file = (e.target as HTMLInputElement).files[0];
|
||||
// read the file as text
|
||||
file.text().then((text) => {
|
||||
// parse the text into a JSON object
|
||||
let flow: FlowType = JSON.parse(text);
|
||||
/**
|
||||
* Creates a file input and listens to a change event to upload a JSON flow file.
|
||||
* If the file type is application/json, the file is read and parsed into a JSON object.
|
||||
* The resulting JSON object is passed to the addFlow function.
|
||||
*/
|
||||
function uploadFlow() {
|
||||
// create a file input
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
// add a change event listener to the file input
|
||||
input.onchange = (e: Event) => {
|
||||
// check if the file type is application/json
|
||||
if ((e.target as HTMLInputElement).files[0].type === "application/json") {
|
||||
// get the file from the file input
|
||||
const file = (e.target as HTMLInputElement).files[0];
|
||||
// read the file as text
|
||||
file.text().then((text) => {
|
||||
// parse the text into a JSON object
|
||||
let flow: FlowType = JSON.parse(text);
|
||||
|
||||
addFlow(flow);
|
||||
});
|
||||
}
|
||||
};
|
||||
// trigger the file input click event to open the file dialog
|
||||
input.click();
|
||||
}
|
||||
/**
|
||||
* Removes a flow from an array of flows based on its id.
|
||||
* Updates the state of flows and tabIndex using setFlows and setTabIndex hooks.
|
||||
* @param {string} id - The id of the flow to remove.
|
||||
*/
|
||||
function removeFlow(id: string) {
|
||||
setFlows((prevState) => {
|
||||
const newFlows = [...prevState];
|
||||
const index = newFlows.findIndex((flow) => flow.id === id);
|
||||
if (index >= 0) {
|
||||
if (index === tabIndex) {
|
||||
setTabIndex(flows.length - 2);
|
||||
newFlows.splice(index, 1);
|
||||
} else {
|
||||
let flowId = flows[tabIndex].id;
|
||||
newFlows.splice(index, 1);
|
||||
setTabIndex(newFlows.findIndex((flow) => flow.id === flowId));
|
||||
}
|
||||
}
|
||||
return newFlows;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Add a new flow to the list of flows.
|
||||
* @param flow Optional flow to add.
|
||||
*/
|
||||
function addFlow(flow?: FlowType) {
|
||||
// Get data from the flow or set it to null if there's no flow provided.
|
||||
const data = flow?.data ? flow.data : null;
|
||||
const description = flow?.description ? flow.description : "";
|
||||
addFlow(flow);
|
||||
});
|
||||
}
|
||||
};
|
||||
// trigger the file input click event to open the file dialog
|
||||
input.click();
|
||||
}
|
||||
/**
|
||||
* Removes a flow from an array of flows based on its id.
|
||||
* Updates the state of flows and tabIndex using setFlows and setTabIndex hooks.
|
||||
* @param {string} id - The id of the flow to remove.
|
||||
*/
|
||||
function removeFlow(id: string) {
|
||||
setFlows((prevState) => {
|
||||
const newFlows = [...prevState];
|
||||
const index = newFlows.findIndex((flow) => flow.id === id);
|
||||
if (index >= 0) {
|
||||
if (index === tabIndex) {
|
||||
setTabIndex(flows.length - 2);
|
||||
newFlows.splice(index, 1);
|
||||
} else {
|
||||
let flowId = flows[tabIndex].id;
|
||||
newFlows.splice(index, 1);
|
||||
setTabIndex(newFlows.findIndex((flow) => flow.id === flowId));
|
||||
}
|
||||
}
|
||||
return newFlows;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Add a new flow to the list of flows.
|
||||
* @param flow Optional flow to add.
|
||||
*/
|
||||
function addFlow(flow?: FlowType) {
|
||||
// Get data from the flow or set it to null if there's no flow provided.
|
||||
const data = flow?.data ? flow.data : null;
|
||||
const description = flow?.description ? flow.description : "";
|
||||
|
||||
if (data) {
|
||||
data.nodes.forEach((node) => {
|
||||
if (Object.keys(templates[node.data.type]["template"]).length > 0) {
|
||||
node.data.node.template = updateTemplate(
|
||||
templates[node.data.type]["template"] as unknown as APITemplateType,
|
||||
node.data.node.template as APITemplateType
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Create a new flow with a default name if no flow is provided.
|
||||
let newFlow: FlowType = {
|
||||
description,
|
||||
name: flow?.name ?? "New Flow",
|
||||
id: id.toString(),
|
||||
data,
|
||||
};
|
||||
if (data) {
|
||||
data.nodes.forEach((node) => {
|
||||
if (Object.keys(templates[node.data.type]["template"]).length > 0) {
|
||||
node.data.node.template = updateTemplate(
|
||||
templates[node.data.type]["template"] as unknown as APITemplateType,
|
||||
node.data.node.template as APITemplateType
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Create a new flow with a default name if no flow is provided.
|
||||
let newFlow: FlowType = {
|
||||
description,
|
||||
name: flow?.name ?? "New Flow",
|
||||
id: id.toString(),
|
||||
data,
|
||||
};
|
||||
|
||||
// Increment the ID counter.
|
||||
setId(uuidv4());
|
||||
// Increment the ID counter.
|
||||
setId(uuidv4());
|
||||
|
||||
// Add the new flow to the list of flows.
|
||||
setFlows((prevState) => {
|
||||
const newFlows = [...prevState, newFlow];
|
||||
return newFlows;
|
||||
});
|
||||
// Add the new flow to the list of flows.
|
||||
setFlows((prevState) => {
|
||||
const newFlows = [...prevState, newFlow];
|
||||
return newFlows;
|
||||
});
|
||||
|
||||
// Set the tab index to the new flow.
|
||||
setTabIndex(flows.length);
|
||||
}
|
||||
/**
|
||||
* Updates an existing flow with new data
|
||||
* @param newFlow - The new flow object containing the updated data
|
||||
*/
|
||||
function updateFlow(newFlow: FlowType) {
|
||||
setFlows((prevState) => {
|
||||
const newFlows = [...prevState];
|
||||
const index = newFlows.findIndex((flow) => flow.id === newFlow.id);
|
||||
if (index !== -1) {
|
||||
newFlows[index].description = newFlow.description ?? "";
|
||||
newFlows[index].data = newFlow.data;
|
||||
newFlows[index].name = newFlow.name;
|
||||
}
|
||||
return newFlows;
|
||||
});
|
||||
}
|
||||
// Set the tab index to the new flow.
|
||||
setTabIndex(flows.length);
|
||||
}
|
||||
/**
|
||||
* Updates an existing flow with new data
|
||||
* @param newFlow - The new flow object containing the updated data
|
||||
*/
|
||||
function updateFlow(newFlow: FlowType) {
|
||||
setFlows((prevState) => {
|
||||
const newFlows = [...prevState];
|
||||
const index = newFlows.findIndex((flow) => flow.id === newFlow.id);
|
||||
if (index !== -1) {
|
||||
newFlows[index].description = newFlow.description ?? "";
|
||||
newFlows[index].data = newFlow.data;
|
||||
newFlows[index].name = newFlow.name;
|
||||
}
|
||||
return newFlows;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<TabsContext.Provider
|
||||
value={{
|
||||
save,
|
||||
hardReset,
|
||||
tabIndex,
|
||||
setTabIndex,
|
||||
flows,
|
||||
incrementNodeId,
|
||||
removeFlow,
|
||||
addFlow,
|
||||
updateFlow,
|
||||
downloadFlow,
|
||||
uploadFlow,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</TabsContext.Provider>
|
||||
);
|
||||
return (
|
||||
<TabsContext.Provider
|
||||
value={{
|
||||
save,
|
||||
hardReset,
|
||||
tabIndex,
|
||||
setTabIndex,
|
||||
flows,
|
||||
incrementNodeId,
|
||||
removeFlow,
|
||||
addFlow,
|
||||
updateFlow,
|
||||
downloadFlow,
|
||||
uploadFlow,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</TabsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { createContext, ReactNode, useEffect, useState } from "react";
|
||||
import { Node} from "reactflow";
|
||||
import { Node } from "reactflow";
|
||||
import { typesContextType } from "../types/typesContext";
|
||||
import { getAll } from "../controllers/API";
|
||||
import { APIKindType } from "../types/api";
|
||||
|
||||
//context to share types adn functions from nodes to flow
|
||||
|
||||
const initialValue:typesContextType = {
|
||||
const initialValue: typesContextType = {
|
||||
reactFlowInstance: null,
|
||||
setReactFlowInstance: () => {},
|
||||
deleteNode: () => {},
|
||||
|
|
@ -14,13 +14,13 @@ const initialValue:typesContextType = {
|
|||
setTypes: () => {},
|
||||
templates: {},
|
||||
setTemplates: () => {},
|
||||
data:{},
|
||||
setData:()=>{}
|
||||
data: {},
|
||||
setData: () => {},
|
||||
};
|
||||
|
||||
export const typesContext = createContext<typesContextType>(initialValue);
|
||||
|
||||
export function TypesProvider({ children }:{children:ReactNode}) {
|
||||
export function TypesProvider({ children }: { children: ReactNode }) {
|
||||
const [types, setTypes] = useState({});
|
||||
const [reactFlowInstance, setReactFlowInstance] = useState(null);
|
||||
const [templates, setTemplates] = useState({});
|
||||
|
|
@ -35,11 +35,11 @@ export function TypesProvider({ children }:{children:ReactNode}) {
|
|||
setData(result.data);
|
||||
setTemplates(
|
||||
Object.keys(result.data).reduce((acc, curr) => {
|
||||
Object.keys(result.data[curr]).forEach((c: keyof APIKindType)=>{
|
||||
acc[c] = result.data[curr][c]
|
||||
})
|
||||
Object.keys(result.data[curr]).forEach((c: keyof APIKindType) => {
|
||||
acc[c] = result.data[curr][c];
|
||||
});
|
||||
return acc;
|
||||
},{})
|
||||
}, {})
|
||||
);
|
||||
// Set the types by reducing over the keys of the result data and updating the accumulator.
|
||||
setTypes(
|
||||
|
|
@ -59,11 +59,15 @@ export function TypesProvider({ children }:{children:ReactNode}) {
|
|||
getTypes();
|
||||
}, [setTypes]);
|
||||
|
||||
function deleteNode(idx:string) {
|
||||
function deleteNode(idx: string) {
|
||||
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 (
|
||||
<typesContext.Provider
|
||||
|
|
@ -76,7 +80,7 @@ export function TypesProvider({ children }:{children:ReactNode}) {
|
|||
setTemplates,
|
||||
templates,
|
||||
data,
|
||||
setData
|
||||
setData,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -4,40 +4,40 @@ import axios, { AxiosResponse } from "axios";
|
|||
import { FlowType } from "../../types/flow";
|
||||
|
||||
export async function getAll(): Promise<AxiosResponse<APIObjectType>> {
|
||||
return await axios.get(`/all`);
|
||||
return await axios.get(`/all`);
|
||||
}
|
||||
|
||||
export async function sendAll(data: sendAllProps) {
|
||||
return await axios.post(`/predict`, data);
|
||||
return await axios.post(`/predict`, data);
|
||||
}
|
||||
|
||||
export async function checkCode(
|
||||
code: string
|
||||
code: string
|
||||
): Promise<AxiosResponse<errorsTypeAPI>> {
|
||||
return await axios.post("/validate/code", { code });
|
||||
return await axios.post("/validate/code", { code });
|
||||
}
|
||||
|
||||
export async function checkPrompt(
|
||||
template: string
|
||||
template: string
|
||||
): Promise<AxiosResponse<PromptTypeAPI>> {
|
||||
return await axios.post("/validate/prompt", { template });
|
||||
return await axios.post("/validate/prompt", { template });
|
||||
}
|
||||
|
||||
export async function getExamples(): Promise<FlowType[]> {
|
||||
const url =
|
||||
"https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples";
|
||||
const response = await axios.get(url);
|
||||
const url =
|
||||
"https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples";
|
||||
const response = await axios.get(url);
|
||||
|
||||
const jsonFiles = response.data.filter((file: any) => {
|
||||
return file.name.endsWith(".json");
|
||||
});
|
||||
const jsonFiles = response.data.filter((file: any) => {
|
||||
return file.name.endsWith(".json");
|
||||
});
|
||||
|
||||
const contentsPromises = jsonFiles.map(async (file: any) => {
|
||||
const contentResponse = await axios.get(file.download_url);
|
||||
return contentResponse.data;
|
||||
});
|
||||
const contentsPromises = jsonFiles.map(async (file: any) => {
|
||||
const contentResponse = await axios.get(file.download_url);
|
||||
return contentResponse.data;
|
||||
});
|
||||
|
||||
const contents = await Promise.all(contentsPromises);
|
||||
const contents = await Promise.all(contentsPromises);
|
||||
|
||||
return contents;
|
||||
return contents;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
|
||||
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
||||
monospace;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,7 @@ const root = ReactDOM.createRoot(
|
|||
root.render(
|
||||
<ContextWrapper>
|
||||
<BrowserRouter>
|
||||
|
||||
<App />
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</ContextWrapper>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -87,37 +87,38 @@ export default function NodeModal({ data }: { data: NodeDataType }) {
|
|||
<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="flex flex-col h-full gap-5">
|
||||
{
|
||||
Object.keys(data.node.template)
|
||||
.filter((t) => t.charAt(0) !== "_"&& data.node.template[t].advanced && data.node.template[t].show)
|
||||
.map((t: string, idx) => {
|
||||
return (
|
||||
<ModalField
|
||||
{Object.keys(data.node.template)
|
||||
.filter(
|
||||
(t) =>
|
||||
t.charAt(0) !== "_" &&
|
||||
data.node.template[t].advanced &&
|
||||
data.node.template[t].show
|
||||
)
|
||||
.map((t: string, idx) => {
|
||||
return (
|
||||
<ModalField
|
||||
key={idx}
|
||||
data={data}
|
||||
title={
|
||||
data.node.template[t].display_name
|
||||
? data.node.template[t].display_name
|
||||
: data.node.template[t].name
|
||||
? toNormalCase(
|
||||
data.node.template[t].name
|
||||
)
|
||||
: toNormalCase(t)
|
||||
}
|
||||
required={data.node.template[t].required}
|
||||
id={
|
||||
data.node.template[t].type +
|
||||
"|" +
|
||||
t +
|
||||
"|" +
|
||||
data.id
|
||||
}
|
||||
name={t}
|
||||
type={data.node.template[t].type}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
data={data}
|
||||
title={
|
||||
data.node.template[t].display_name
|
||||
? data.node.template[t].display_name
|
||||
: data.node.template[t].name
|
||||
? toNormalCase(data.node.template[t].name)
|
||||
: toNormalCase(t)
|
||||
}
|
||||
required={data.node.template[t].required}
|
||||
id={
|
||||
data.node.template[t].type +
|
||||
"|" +
|
||||
t +
|
||||
"|" +
|
||||
data.id
|
||||
}
|
||||
name={t}
|
||||
type={data.node.template[t].type}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ export default function ChatInput({
|
|||
}}
|
||||
ref={inputRef}
|
||||
disabled={lockChat}
|
||||
style={{resize: "none" }}
|
||||
style={{ resize: "none" }}
|
||||
value={lockChat ? "Thinking..." : chatValue}
|
||||
onChange={(e) => {
|
||||
onChange={(e) => {
|
||||
setChatValue(e.target.value);
|
||||
}}
|
||||
className={classNames(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
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 relative 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
|
||||
language={language}
|
||||
style={oneDark}
|
||||
customStyle={{ margin: 0 }}
|
||||
>
|
||||
{value}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
CodeBlock.displayName = "CodeBlock";
|
||||
|
|
@ -1,82 +1,140 @@
|
|||
import { ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ChatMessageType } from "../../../types/chat";
|
||||
import { classNames } from "../../../utils";
|
||||
import AiIcon from "../../../assets/Gooey Ring-5s-271px.svg";
|
||||
import { UserIcon } from "@heroicons/react/24/solid";
|
||||
import FileCard from "../fileComponent";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import rehypeMathjax from "rehype-mathjax";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkMath from "remark-math";
|
||||
import { CodeBlock } from "./codeBlock";
|
||||
var Convert = require("ansi-to-html");
|
||||
var convert = new Convert({ newline: true });
|
||||
|
||||
export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
|
||||
const [hidden, setHidden] = useState(true);
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
"w-full py-2 pl-2 flex",
|
||||
chat.isSend ? "bg-white dark:bg-gray-800 " : "bg-gray-200 dark:bg-gray-700"
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
"rounded-full w-8 h-8 flex items-center my-3 justify-center",
|
||||
chat.isSend ? "bg-gray-900" : "bg-gray-200"
|
||||
)}
|
||||
>
|
||||
{!chat.isSend && <img className="scale-150" src={AiIcon} />}
|
||||
{chat.isSend && <UserIcon className="w-6 h-6 -mb-1 text-gray-200" />}
|
||||
</div>
|
||||
{!chat.isSend ? (
|
||||
<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 !== "" && (
|
||||
<div
|
||||
onClick={() => setHidden((prev) => !prev)}
|
||||
className="absolute -top-1 -left-2 cursor-pointer"
|
||||
>
|
||||
<ChatBubbleOvalLeftEllipsisIcon className="w-5 h-5 animate-bounce dark:text-white" />
|
||||
</div>
|
||||
)}
|
||||
{chat.thought && chat.thought !== "" && !hidden && (
|
||||
<div
|
||||
onClick={() => setHidden((prev) => !prev)}
|
||||
className=" text-start inline-block rounded-md h-full border border-gray-300
|
||||
const [message, setMessage] = useState("");
|
||||
useEffect(() => {
|
||||
setMessage(chat.message);
|
||||
}, [chat.message]);
|
||||
const [hidden, setHidden] = useState(true);
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
"w-full py-2 pl-2 flex",
|
||||
chat.isSend
|
||||
? "bg-white dark:bg-gray-800 "
|
||||
: "bg-gray-200 dark:bg-gray-700"
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
"rounded-full w-8 h-8 flex items-center my-3 justify-center",
|
||||
chat.isSend ? "bg-gray-900" : "bg-gray-200"
|
||||
)}
|
||||
>
|
||||
{!chat.isSend && <img className="scale-150" src={AiIcon} />}
|
||||
{chat.isSend && <UserIcon className="w-6 h-6 -mb-1 text-gray-200" />}
|
||||
</div>
|
||||
{!chat.isSend ? (
|
||||
<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 !== "" && (
|
||||
<div
|
||||
onClick={() => setHidden((prev) => !prev)}
|
||||
className="absolute -top-1 -left-2 cursor-pointer"
|
||||
>
|
||||
<ChatBubbleOvalLeftEllipsisIcon className="w-5 h-5 animate-bounce dark:text-white" />
|
||||
</div>
|
||||
)}
|
||||
{chat.thought && chat.thought !== "" && !hidden && (
|
||||
<div
|
||||
onClick={() => setHidden((prev) => !prev)}
|
||||
className=" text-start inline-block rounded-md h-full border border-gray-300
|
||||
bg-gray-100 w-[95%] pb-3 pt-3 px-2 ml-3 cursor-pointer scrollbar-hide overflow-scroll"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: convert.toHtml(chat.thought),
|
||||
}}
|
||||
></div>
|
||||
)}
|
||||
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
|
||||
<div className="w-full px-4 pb-3 pt-3 pr-8">
|
||||
<span className="dark:text-white">
|
||||
{chat.message}
|
||||
{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>
|
||||
)}
|
||||
</span>
|
||||
</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">
|
||||
{chat.message}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: convert.toHtml(chat.thought),
|
||||
}}
|
||||
></div>
|
||||
)}
|
||||
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
|
||||
<div className="w-full px-4 pb-3 pt-3 pr-8">
|
||||
<div className="dark:text-white">
|
||||
<div>
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[rehypeMathjax]}
|
||||
className="markdown prose"
|
||||
components={{
|
||||
code({ node, inline, className, children, ...props }) {
|
||||
if (children.length) {
|
||||
if (children[0] == "▍") {
|
||||
return (
|
||||
<span className="animate-pulse cursor-default mt-1">
|
||||
▍
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
children[0] = (children[0] as string).replace(
|
||||
"`▍`",
|
||||
"▍"
|
||||
);
|
||||
}
|
||||
|
||||
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"
|
||||
>
|
||||
{message}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,55 @@
|
|||
import { CloudArrowDownIcon, DocumentIcon } from "@heroicons/react/24/outline";
|
||||
import * as base64js from 'base64-js';
|
||||
import { CloudArrowDownIcon, DocumentIcon } from "@heroicons/react/24/outline";
|
||||
import * as base64js from "base64-js";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function FileCard({ fileName, content, fileType }) {
|
||||
const handleDownload = () => {
|
||||
const byteArray = new Uint8Array(base64js.toByteArray(content));
|
||||
const blob = new Blob([byteArray], { type: 'application/octet-stream' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = fileName+".png";
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
const handleDownload = () => {
|
||||
const byteArray = new Uint8Array(base64js.toByteArray(content));
|
||||
const blob = new Blob([byteArray], { type: "application/octet-stream" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = fileName + ".png";
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
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 (
|
||||
<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">
|
||||
{" "}
|
||||
<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="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>
|
||||
<CloudArrowDownIcon className="w-6 h-6 text-current ml-auto" />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,7 @@
|
|||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import {
|
||||
ChatBubbleOvalLeftEllipsisIcon,
|
||||
LockClosedIcon,
|
||||
PaperAirplaneIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline";
|
||||
import { Fragment, useContext, useEffect, useRef, useState } from "react";
|
||||
import { PopUpContext } from "../../contexts/popUpContext";
|
||||
import { FlowType, NodeType } from "../../types/flow";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { toNormalCase } from "../../utils";
|
||||
import { typesContext } from "../../contexts/typesContext";
|
||||
|
|
@ -32,8 +26,15 @@ export default function ChatModal({
|
|||
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
|
||||
const { reactFlowInstance } = useContext(typesContext);
|
||||
const { setErrorData, setNoticeData } = useContext(alertContext);
|
||||
const [ws, setWs] = useState<WebSocket | null>(null);
|
||||
const ws = useRef<WebSocket | null>(null);
|
||||
const [lockChat, setLockChat] = useState(false);
|
||||
const isOpen = useRef(open);
|
||||
|
||||
useEffect(() => {
|
||||
isOpen.current = open;
|
||||
}, [open]);
|
||||
var isStream = false;
|
||||
|
||||
const addChatHistory = (
|
||||
message: string,
|
||||
isSend: boolean,
|
||||
|
|
@ -53,135 +54,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() {
|
||||
console.log("conectou");
|
||||
try {
|
||||
const urlWs =
|
||||
process.env.NODE_ENV === "development"
|
||||
? `ws://localhost:7860/chat/${flow.id}`
|
||||
: `${window.location.protocol === "https:" ? "wss" : "ws"}://${
|
||||
window.location.host
|
||||
}/chat/${flow.id}`;
|
||||
: `${window.location.protocol === "https:" ? "wss" : "ws"}://${window.location.host
|
||||
}/chat/${flow.id}`;
|
||||
|
||||
const newWs = new WebSocket(urlWs);
|
||||
newWs.onopen = () => {
|
||||
console.log("WebSocket connection established!");
|
||||
};
|
||||
console.log(flow.id);
|
||||
newWs.onmessage = (event) => {
|
||||
try {
|
||||
setLockChat(false);
|
||||
const data = JSON.parse(event.data);
|
||||
console.log("Received data:", data);
|
||||
//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();
|
||||
}
|
||||
}
|
||||
const data = JSON.parse(event.data);
|
||||
console.log("Received data:", data);
|
||||
handleWsMessage(data);
|
||||
//get chat history
|
||||
};
|
||||
newWs.onclose = (_) => {
|
||||
if (open) {
|
||||
setLockChat(false);
|
||||
setTimeout(() => {
|
||||
connectWS();
|
||||
}, 1000);
|
||||
}
|
||||
newWs.onclose = (event) => {
|
||||
handleOnClose(event);
|
||||
};
|
||||
newWs.onerror = (ev) => {
|
||||
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);
|
||||
|
||||
return newWs;
|
||||
ws.current = newWs;
|
||||
} catch {
|
||||
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",
|
||||
"Refresh the page",
|
||||
"Use a new flow tab",
|
||||
"Check if the backend is up",
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (ws && (ws.readyState === ws.CLOSED || ws.readyState === ws.CLOSING)) {
|
||||
let newWs = connectWS();
|
||||
return () => {
|
||||
console.log("trigger");
|
||||
newWs.close();
|
||||
};
|
||||
}
|
||||
}, [lockChat]);
|
||||
|
||||
useEffect(() => {
|
||||
let newWs = connectWS();
|
||||
connectWS();
|
||||
return () => {
|
||||
console.log("trigger");
|
||||
newWs.close();
|
||||
console.log("unmount");
|
||||
console.log(ws);
|
||||
if (ws) {
|
||||
ws.current.close();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
async function sendAll(data: sendAllProps) {
|
||||
try {
|
||||
if (ws) {
|
||||
ws.send(JSON.stringify(data));
|
||||
ws.current.send(JSON.stringify(data));
|
||||
}
|
||||
} catch (error) {
|
||||
setErrorData({
|
||||
title: "There was an erro sending the message",
|
||||
title: "There was an error sending the message",
|
||||
list: [error.message],
|
||||
});
|
||||
setChatValue(data.message);
|
||||
|
|
@ -193,12 +235,6 @@ export default function ChatModal({
|
|||
if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" });
|
||||
}, [chatHistory]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ws && ws.readyState === ws.CLOSED) {
|
||||
setLockChat(false);
|
||||
}
|
||||
}, [lockChat]);
|
||||
|
||||
function validateNode(n: NodeType): Array<string> {
|
||||
if (!n.data?.node?.template || !Object.keys(n.data.node.template)) {
|
||||
setNoticeData({
|
||||
|
|
@ -227,12 +263,11 @@ export default function ChatModal({
|
|||
e.targetHandle.split("|")[2] === n.id
|
||||
)
|
||||
? [
|
||||
`${type} is missing ${
|
||||
template.display_name
|
||||
? template.display_name
|
||||
: toNormalCase(template[t].name)
|
||||
}.`,
|
||||
]
|
||||
`${type} is missing ${template.display_name
|
||||
? template.display_name
|
||||
: toNormalCase(template[t].name)
|
||||
}.`,
|
||||
]
|
||||
: []
|
||||
),
|
||||
[] as string[]
|
||||
|
|
@ -255,7 +290,6 @@ export default function ChatModal({
|
|||
let message = chatValue;
|
||||
setChatValue("");
|
||||
addChatHistory(message, true);
|
||||
|
||||
sendAll({
|
||||
...reactFlowInstance.toObject(),
|
||||
message,
|
||||
|
|
@ -278,20 +312,14 @@ export default function ChatModal({
|
|||
}
|
||||
function clearChat() {
|
||||
setChatHistory([]);
|
||||
ws.send(JSON.stringify({ clear_history: true }));
|
||||
ws.current.send(JSON.stringify({ clear_history: true }));
|
||||
}
|
||||
|
||||
const { closePopUp } = useContext(PopUpContext);
|
||||
function setModalOpen(x: boolean) {
|
||||
setOpen(x);
|
||||
if (x === false) {
|
||||
setTimeout(() => {
|
||||
closePopUp();
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Transition.Root show={open} appear={true} as={Fragment}>
|
||||
<Transition.Root show={open} appear={open} as={Fragment}>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-10"
|
||||
|
|
@ -321,11 +349,11 @@ export default function ChatModal({
|
|||
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=" 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]">
|
||||
<div className="relative w-full">
|
||||
<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 p-4">
|
||||
<button
|
||||
onClick={() => clearChat()}
|
||||
className="absolute top-2 right-2 hover:text-red-500"
|
||||
className="absolute top-2 right-3 hover:text-red-500 z-30"
|
||||
>
|
||||
<FaEraser className="w-4 h-4" />
|
||||
</button>
|
||||
|
|
@ -334,7 +362,7 @@ export default function ChatModal({
|
|||
{chatHistory.length > 0 ? (
|
||||
chatHistory.map((c, i) => <ChatMessage 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 className="text-gray-600 text-lg">
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ export default function CodeAreaModal({
|
|||
title: "Code is ready to run",
|
||||
});
|
||||
setModalOpen(false);
|
||||
setValue(code)
|
||||
setValue(code);
|
||||
} else {
|
||||
if (funcErrors.length !== 0) {
|
||||
setErrorData({
|
||||
|
|
@ -142,7 +142,7 @@ export default function CodeAreaModal({
|
|||
list: funcErrors,
|
||||
});
|
||||
}
|
||||
if(importsErrors.length!==0){
|
||||
if (importsErrors.length !== 0) {
|
||||
setErrorData({
|
||||
title: "There is an error in your imports",
|
||||
list: importsErrors,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import {
|
||||
XMarkIcon,
|
||||
ArrowDownTrayIcon,
|
||||
DocumentDuplicateIcon,
|
||||
ComputerDesktopIcon,
|
||||
XMarkIcon,
|
||||
ArrowDownTrayIcon,
|
||||
DocumentDuplicateIcon,
|
||||
ComputerDesktopIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { Fragment, useContext, useRef, useState } from "react";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
|
|
@ -12,169 +12,169 @@ import { TabsContext } from "../../contexts/tabsContext";
|
|||
import { removeApiKeys } from "../../utils";
|
||||
|
||||
export default function ExportModal() {
|
||||
const [open, setOpen] = useState(true);
|
||||
const { closePopUp } = useContext(PopUpContext);
|
||||
const ref = useRef();
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const { flows, tabIndex, updateFlow, downloadFlow } = useContext(TabsContext);
|
||||
function setModalOpen(x: boolean) {
|
||||
setOpen(x);
|
||||
if (x === false) {
|
||||
setTimeout(() => {
|
||||
closePopUp();
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
const [checked, setChecked] = useState(true);
|
||||
const [name, setName] = useState(flows[tabIndex].name);
|
||||
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>
|
||||
const [open, setOpen] = useState(true);
|
||||
const { closePopUp } = useContext(PopUpContext);
|
||||
const ref = useRef();
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const { flows, tabIndex, updateFlow, downloadFlow } = useContext(TabsContext);
|
||||
function setModalOpen(x: boolean) {
|
||||
setOpen(x);
|
||||
if (x === false) {
|
||||
setTimeout(() => {
|
||||
closePopUp();
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
const [checked, setChecked] = useState(true);
|
||||
const [name, setName] = useState(flows[tabIndex].name);
|
||||
return (
|
||||
<Transition.Root show={open} appear={true} as={Fragment}>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-10"
|
||||
onClose={setModalOpen}
|
||||
initialFocus={ref}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative flex flex-col justify-between transform h-[600px] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[700px]">
|
||||
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Close</span>
|
||||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="h-full w-full flex flex-col justify-center items-center">
|
||||
<div className="flex w-full pb-4 z-10 justify-center shadow-sm">
|
||||
<div className="mx-auto mt-4 flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 dark:bg-gray-900 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<ArrowDownTrayIcon
|
||||
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"
|
||||
>
|
||||
Export as
|
||||
</Dialog.Title>
|
||||
</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="w-full">
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="block mb-2 font-medium text-gray-700 dark:text-white"
|
||||
>
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
onChange={(event) => {
|
||||
if (event.target.value != "") {
|
||||
let newFlow = flows[tabIndex];
|
||||
newFlow.name = event.target.value;
|
||||
setName(event.target.value);
|
||||
updateFlow(newFlow);
|
||||
} else {
|
||||
setName(event.target.value);
|
||||
}
|
||||
}}
|
||||
type="text"
|
||||
name="name"
|
||||
value={name ?? null}
|
||||
placeholder="File 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"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<label
|
||||
htmlFor="description"
|
||||
className="block mb-2 font-medium text-gray-700 dark:text-white"
|
||||
>
|
||||
Description{" "}
|
||||
<span className="text-gray-400 text-sm">
|
||||
{" "}
|
||||
(optional)
|
||||
</span>
|
||||
</label>
|
||||
<textarea
|
||||
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 className="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative flex flex-col justify-between transform h-[600px] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[700px]">
|
||||
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Close</span>
|
||||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="h-full w-full flex flex-col justify-center items-center">
|
||||
<div className="flex w-full pb-4 z-10 justify-center shadow-sm">
|
||||
<div className="mx-auto mt-4 flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 dark:bg-gray-900 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<ArrowDownTrayIcon
|
||||
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"
|
||||
>
|
||||
Export as
|
||||
</Dialog.Title>
|
||||
</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="w-full">
|
||||
<label
|
||||
htmlFor="name"
|
||||
className="block mb-2 font-medium text-gray-700 dark:text-white"
|
||||
>
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
onChange={(event) => {
|
||||
if (event.target.value != "") {
|
||||
let newFlow = flows[tabIndex];
|
||||
newFlow.name = event.target.value;
|
||||
setName(event.target.value);
|
||||
updateFlow(newFlow);
|
||||
} else {
|
||||
setName(event.target.value);
|
||||
}
|
||||
}}
|
||||
type="text"
|
||||
name="name"
|
||||
value={name ?? null}
|
||||
placeholder="File 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"
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<label
|
||||
htmlFor="description"
|
||||
className="block mb-2 font-medium text-gray-700 dark:text-white"
|
||||
>
|
||||
Description{" "}
|
||||
<span className="text-gray-400 text-sm">
|
||||
{" "}
|
||||
(optional)
|
||||
</span>
|
||||
</label>
|
||||
<textarea
|
||||
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>
|
||||
<label htmlFor="checkbox" className="flex items-center">
|
||||
<input
|
||||
onChange={(event) => {
|
||||
setChecked(event.target.checked);
|
||||
}}
|
||||
checked={checked}
|
||||
id="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"
|
||||
/>
|
||||
<span className="ml-2 font-medium text-gray-700 dark:text-white">
|
||||
Save with my API keys
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="w-full flex justify-end">
|
||||
<button
|
||||
onClick={() => {
|
||||
if (checked) downloadFlow(flows[tabIndex]);
|
||||
else downloadFlow(removeApiKeys(flows[tabIndex]));
|
||||
}}
|
||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
||||
>
|
||||
Download Flow
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
<div>
|
||||
<label htmlFor="checkbox" className="flex items-center">
|
||||
<input
|
||||
onChange={(event) => {
|
||||
setChecked(event.target.checked);
|
||||
}}
|
||||
checked={checked}
|
||||
id="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"
|
||||
/>
|
||||
<span className="ml-2 font-medium text-gray-700 dark:text-white">
|
||||
Save with my API keys
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="w-full flex justify-end">
|
||||
<button
|
||||
onClick={() => {
|
||||
if (checked) downloadFlow(flows[tabIndex]);
|
||||
else downloadFlow(removeApiKeys(flows[tabIndex]));
|
||||
}}
|
||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
|
||||
>
|
||||
Download Flow
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import {
|
||||
XMarkIcon,
|
||||
ArrowDownTrayIcon,
|
||||
DocumentDuplicateIcon,
|
||||
ComputerDesktopIcon,
|
||||
ArrowUpTrayIcon,
|
||||
ArrowLeftIcon,
|
||||
XMarkIcon,
|
||||
ArrowDownTrayIcon,
|
||||
DocumentDuplicateIcon,
|
||||
ComputerDesktopIcon,
|
||||
ArrowUpTrayIcon,
|
||||
ArrowLeftIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { Fragment, useContext, useRef, useState } from "react";
|
||||
import { PopUpContext } from "../../contexts/popUpContext";
|
||||
|
|
@ -19,214 +19,214 @@ import { FlowType } from "../../types/flow";
|
|||
import { classNames, toNormalCase } from "../../utils";
|
||||
|
||||
export default function ImportModal() {
|
||||
const [open, setOpen] = useState(true);
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const { closePopUp } = useContext(PopUpContext);
|
||||
const ref = useRef();
|
||||
const [showExamples, setShowExamples] = useState(false);
|
||||
const [loadingExamples, setLoadingExamples] = useState(false);
|
||||
const [examples, setExamples] = useState<FlowType[]>([]);
|
||||
const { uploadFlow, addFlow } = useContext(TabsContext);
|
||||
function setModalOpen(x: boolean) {
|
||||
setOpen(x);
|
||||
if (x === false) {
|
||||
setTimeout(() => {
|
||||
closePopUp();
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
const [open, setOpen] = useState(true);
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const { closePopUp } = useContext(PopUpContext);
|
||||
const ref = useRef();
|
||||
const [showExamples, setShowExamples] = useState(false);
|
||||
const [loadingExamples, setLoadingExamples] = useState(false);
|
||||
const [examples, setExamples] = useState<FlowType[]>([]);
|
||||
const { uploadFlow, addFlow } = useContext(TabsContext);
|
||||
function setModalOpen(x: boolean) {
|
||||
setOpen(x);
|
||||
if (x === false) {
|
||||
setTimeout(() => {
|
||||
closePopUp();
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
function handleExamples() {
|
||||
setLoadingExamples(true);
|
||||
getExamples()
|
||||
.then((result) => {
|
||||
setLoadingExamples(false);
|
||||
setExamples(result);
|
||||
})
|
||||
.catch((error) =>
|
||||
setErrorData({
|
||||
title: "there was an error loading examples, please try again",
|
||||
list: [error.message],
|
||||
})
|
||||
);
|
||||
}
|
||||
function handleExamples() {
|
||||
setLoadingExamples(true);
|
||||
getExamples()
|
||||
.then((result) => {
|
||||
setLoadingExamples(false);
|
||||
setExamples(result);
|
||||
})
|
||||
.catch((error) =>
|
||||
setErrorData({
|
||||
title: "there was an error loading examples, please try again",
|
||||
list: [error.message],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
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>
|
||||
return (
|
||||
<Transition.Root show={open} appear={true} as={Fragment}>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-10"
|
||||
onClose={setModalOpen}
|
||||
initialFocus={ref}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative flex flex-col justify-between transform h-[600px] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[700px]">
|
||||
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Close</span>
|
||||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
{showExamples && (
|
||||
<>
|
||||
<div className="z-50 absolute top-2 left-0 hidden pt-4 pl-4 sm:block">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
onClick={() => {
|
||||
setShowExamples(false);
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Close</span>
|
||||
<ArrowLeftIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</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"
|
||||
target="_blank"
|
||||
className="flex items-center justify-center"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
viewBox="0 0 98 96"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
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"
|
||||
fill="#24292f"
|
||||
/>
|
||||
</svg>
|
||||
<span className="ml-2">LangFlow Examples</span>
|
||||
</a>
|
||||
</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">
|
||||
<ArrowUpTrayIcon
|
||||
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"
|
||||
>
|
||||
{showExamples ? "Select an example" : "Import from"}
|
||||
</Dialog.Title>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
"h-full w-full bg-gray-200 dark:bg-gray-900 gap-4",
|
||||
showExamples && !loadingExamples
|
||||
? "flex flex-row start justify-start items-start p-9 flex-wrap overflow-auto"
|
||||
: "flex flex-row justify-center items-center p-4"
|
||||
)}
|
||||
>
|
||||
{!showExamples && (
|
||||
<div className="flex h-full w-full justify-evenly items-center">
|
||||
<ButtonBox
|
||||
size="big"
|
||||
bgColor="bg-emerald-500"
|
||||
description="Prebuilt Examples"
|
||||
icon={
|
||||
<DocumentDuplicateIcon className="h-10 w-10 flex-shrink-0" />
|
||||
}
|
||||
onClick={() => {
|
||||
setShowExamples(true);
|
||||
handleExamples();
|
||||
}}
|
||||
textColor="text-emerald-400"
|
||||
title="Examples"
|
||||
></ButtonBox>
|
||||
<ButtonBox
|
||||
size="big"
|
||||
bgColor="bg-blue-500"
|
||||
description="Import from Local"
|
||||
icon={
|
||||
<ComputerDesktopIcon className="h-10 w-10 flex-shrink-0" />
|
||||
}
|
||||
onClick={() => {
|
||||
uploadFlow();
|
||||
setModalOpen(false);
|
||||
}}
|
||||
textColor="text-blue-500"
|
||||
title="Local file"
|
||||
></ButtonBox>
|
||||
</div>
|
||||
)}
|
||||
{showExamples && loadingExamples && (
|
||||
<div className="flex align-middle justify-center items-center">
|
||||
<LoadingComponent remSize={30} />
|
||||
</div>
|
||||
)}
|
||||
{showExamples &&
|
||||
!loadingExamples &&
|
||||
examples.map((example, index) => {
|
||||
return (
|
||||
<div id="index">
|
||||
{" "}
|
||||
<ButtonBox
|
||||
size="small"
|
||||
bgColor="bg-emerald-500"
|
||||
description={
|
||||
example.description ?? "Prebuilt Examples"
|
||||
}
|
||||
icon={
|
||||
<DocumentDuplicateIcon className="h-6 w-6 flex-shrink-0" />
|
||||
}
|
||||
onClick={() => {
|
||||
addFlow(example);
|
||||
setModalOpen(false);
|
||||
}}
|
||||
textColor="text-emerald-400"
|
||||
title={toNormalCase(example.name)}
|
||||
></ButtonBox>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative flex flex-col justify-between transform h-[600px] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[700px]">
|
||||
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Close</span>
|
||||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
{showExamples && (
|
||||
<>
|
||||
<div className="z-50 absolute top-2 left-0 hidden pt-4 pl-4 sm:block">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
onClick={() => {
|
||||
setShowExamples(false);
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Close</span>
|
||||
<ArrowLeftIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</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"
|
||||
target="_blank"
|
||||
className="flex items-center justify-center"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
viewBox="0 0 98 96"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
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"
|
||||
fill="#24292f"
|
||||
/>
|
||||
</svg>
|
||||
<span className="ml-2">LangFlow Examples</span>
|
||||
</a>
|
||||
</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">
|
||||
<ArrowUpTrayIcon
|
||||
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"
|
||||
>
|
||||
{showExamples ? "Select an example" : "Import from"}
|
||||
</Dialog.Title>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
"h-full w-full bg-gray-200 dark:bg-gray-900 gap-4",
|
||||
showExamples && !loadingExamples
|
||||
? "flex flex-row start justify-start items-start p-9 flex-wrap overflow-auto"
|
||||
: "flex flex-row justify-center items-center p-4"
|
||||
)}
|
||||
>
|
||||
{!showExamples && (
|
||||
<div className="flex h-full w-full justify-evenly items-center">
|
||||
<ButtonBox
|
||||
size="big"
|
||||
bgColor="bg-emerald-500"
|
||||
description="Prebuilt Examples"
|
||||
icon={
|
||||
<DocumentDuplicateIcon className="h-10 w-10 flex-shrink-0" />
|
||||
}
|
||||
onClick={() => {
|
||||
setShowExamples(true);
|
||||
handleExamples();
|
||||
}}
|
||||
textColor="text-emerald-400"
|
||||
title="Examples"
|
||||
></ButtonBox>
|
||||
<ButtonBox
|
||||
size="big"
|
||||
bgColor="bg-blue-500"
|
||||
description="Import from Local"
|
||||
icon={
|
||||
<ComputerDesktopIcon className="h-10 w-10 flex-shrink-0" />
|
||||
}
|
||||
onClick={() => {
|
||||
uploadFlow();
|
||||
setModalOpen(false);
|
||||
}}
|
||||
textColor="text-blue-500"
|
||||
title="Local file"
|
||||
></ButtonBox>
|
||||
</div>
|
||||
)}
|
||||
{showExamples && loadingExamples && (
|
||||
<div className="flex align-middle justify-center items-center">
|
||||
<LoadingComponent remSize={30} />
|
||||
</div>
|
||||
)}
|
||||
{showExamples &&
|
||||
!loadingExamples &&
|
||||
examples.map((example, index) => {
|
||||
return (
|
||||
<div id="index">
|
||||
{" "}
|
||||
<ButtonBox
|
||||
size="small"
|
||||
bgColor="bg-emerald-500"
|
||||
description={
|
||||
example.description ?? "Prebuilt Examples"
|
||||
}
|
||||
icon={
|
||||
<DocumentDuplicateIcon className="h-6 w-6 flex-shrink-0" />
|
||||
}
|
||||
onClick={() => {
|
||||
addFlow(example);
|
||||
setModalOpen(false);
|
||||
}}
|
||||
textColor="text-emerald-400"
|
||||
title={toNormalCase(example.name)}
|
||||
></ButtonBox>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,7 +135,8 @@ export default function PromptAreaModal({
|
|||
return setErrorData({
|
||||
title:
|
||||
"There is something wrong with this prompt, please review it",
|
||||
list:[error.response.data.detail]});
|
||||
list: [error.response.data.detail],
|
||||
});
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,104 +1,123 @@
|
|||
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 { PopUpContext } from "../../contexts/popUpContext";
|
||||
|
||||
export default function TextAreaModal({value, setValue}:{setValue:(value:string)=>void,value:string|string[]}){
|
||||
const [open, setOpen] = useState(true);
|
||||
const [myValue, setMyValue] = useState(value);
|
||||
const { closePopUp } = useContext(PopUpContext);
|
||||
const ref = useRef();
|
||||
function setModalOpen(x:boolean){
|
||||
setOpen(x);
|
||||
if(x === false){
|
||||
setTimeout(() => {closePopUp()}, 300);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Transition.Root show={open} appear={true} as={Fragment}>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-10"
|
||||
onClose={setModalOpen}
|
||||
initialFocus={ref}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
|
||||
</Transition.Child>
|
||||
export default function TextAreaModal({
|
||||
value,
|
||||
setValue,
|
||||
}: {
|
||||
setValue: (value: string) => void;
|
||||
value: string | string[];
|
||||
}) {
|
||||
const [open, setOpen] = useState(true);
|
||||
const [myValue, setMyValue] = useState(value);
|
||||
const { closePopUp } = useContext(PopUpContext);
|
||||
const ref = useRef();
|
||||
function setModalOpen(x: boolean) {
|
||||
setOpen(x);
|
||||
if (x === false) {
|
||||
setTimeout(() => {
|
||||
closePopUp();
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Transition.Root show={open} appear={true} as={Fragment}>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-10"
|
||||
onClose={setModalOpen}
|
||||
initialFocus={ref}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed inset-0 bg-gray-500 dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
|
||||
</Transition.Child>
|
||||
|
||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative flex flex-col justify-between transform h-[600px] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[700px]">
|
||||
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Close</span>
|
||||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="h-full w-full flex flex-col justify-center items-center">
|
||||
<div className="flex w-full pb-4 z-10 justify-center shadow-sm">
|
||||
<div className="mx-auto mt-4 flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 dark:bg-gray-900 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<ClipboardDocumentListIcon
|
||||
className="h-6 w-6 text-blue-600"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 text-center sm:ml-4 sm:text-left">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium dark:text-white leading-10 text-gray-900"
|
||||
>
|
||||
Edit text
|
||||
</Dialog.Title>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-full w-full bg-gray-200 dark:bg-gray-900 p-4 gap-4 flex flex-row justify-center items-center">
|
||||
<div className="flex h-full w-full">
|
||||
<div className="overflow-hidden px-4 py-5 sm:p-6 w-full rounded-lg bg-white dark:bg-gray-800 shadow">
|
||||
<textarea ref={ref} className="form-input h-full w-full rounded-lg border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-white" value={myValue} onChange={(e) => {setMyValue(e.target.value); setValue(e.target.value)}}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-200 dark:bg-gray-900 w-full pb-3 flex flex-row-reverse px-4">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm"
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
>
|
||||
Finish editing
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
)
|
||||
}
|
||||
<div className="fixed inset-0 z-10 overflow-y-auto">
|
||||
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative flex flex-col justify-between transform h-[600px] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[700px]">
|
||||
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Close</span>
|
||||
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="h-full w-full flex flex-col justify-center items-center">
|
||||
<div className="flex w-full pb-4 z-10 justify-center shadow-sm">
|
||||
<div className="mx-auto mt-4 flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 dark:bg-gray-900 sm:mx-0 sm:h-10 sm:w-10">
|
||||
<ClipboardDocumentListIcon
|
||||
className="h-6 w-6 text-blue-600"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 text-center sm:ml-4 sm:text-left">
|
||||
<Dialog.Title
|
||||
as="h3"
|
||||
className="text-lg font-medium dark:text-white leading-10 text-gray-900"
|
||||
>
|
||||
Edit text
|
||||
</Dialog.Title>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-full w-full bg-gray-200 dark:bg-gray-900 p-4 gap-4 flex flex-row justify-center items-center">
|
||||
<div className="flex h-full w-full">
|
||||
<div className="overflow-hidden px-4 py-5 sm:p-6 w-full rounded-lg bg-white dark:bg-gray-800 shadow">
|
||||
<textarea
|
||||
ref={ref}
|
||||
className="form-input h-full w-full rounded-lg border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-white"
|
||||
value={myValue}
|
||||
onChange={(e) => {
|
||||
setMyValue(e.target.value);
|
||||
setValue(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-200 dark:bg-gray-900 w-full pb-3 flex flex-row-reverse px-4">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm"
|
||||
onClick={() => {
|
||||
setModalOpen(false);
|
||||
}}
|
||||
>
|
||||
Finish editing
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
import { ConnectionLineComponentProps } from 'reactflow';
|
||||
|
||||
|
||||
import { ConnectionLineComponentProps } from "reactflow";
|
||||
|
||||
const ConnectionLineComponent = ({
|
||||
fromX,
|
||||
fromY,
|
||||
toX,
|
||||
toY,
|
||||
connectionLineStyle = {} // provide a default value for connectionLineStyle
|
||||
}:ConnectionLineComponentProps) => {
|
||||
connectionLineStyle = {}, // provide a default value for connectionLineStyle
|
||||
}: ConnectionLineComponentProps) => {
|
||||
return (
|
||||
<g>
|
||||
<path
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
import {
|
||||
ChevronRightIcon,
|
||||
} from "@heroicons/react/24/solid";
|
||||
import { ChevronRightIcon } from "@heroicons/react/24/solid";
|
||||
import { Disclosure } from "@headlessui/react";
|
||||
import { DisclosureComponentType } from "../../../../types/components";
|
||||
|
||||
export default function DisclosureComponent({
|
||||
button: { title, Icon, buttons = [] },
|
||||
children,
|
||||
}: DisclosureComponentType
|
||||
) {
|
||||
}: DisclosureComponentType) {
|
||||
return (
|
||||
<Disclosure as="div" key={title}>
|
||||
{({ open }) => (
|
||||
|
|
|
|||
|
|
@ -3,13 +3,10 @@ import DisclosureComponent from "../DisclosureComponent";
|
|||
import { nodeColors, nodeIcons, nodeNames } from "../../../../utils";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { typesContext } from "../../../../contexts/typesContext";
|
||||
import {
|
||||
APIClassType,
|
||||
APIObjectType,
|
||||
} from "../../../../types/api";
|
||||
import { APIClassType, APIObjectType } from "../../../../types/api";
|
||||
|
||||
export default function ExtraSidebar() {
|
||||
const {data} = useContext(typesContext)
|
||||
const { data } = useContext(typesContext);
|
||||
|
||||
function onDragStart(
|
||||
event: React.DragEvent<any>,
|
||||
|
|
@ -22,45 +19,49 @@ export default function ExtraSidebar() {
|
|||
|
||||
return (
|
||||
<div className="mt-1 w-full">
|
||||
{Object.keys(data).sort().map((d: keyof APIObjectType, i) => (
|
||||
<DisclosureComponent
|
||||
key={i}
|
||||
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
|
||||
draggable
|
||||
className={" cursor-grab border-l-8 rounded-l-md"}
|
||||
style={{
|
||||
borderLeftColor: nodeColors[d] ?? nodeColors.unknown,
|
||||
}}
|
||||
onDragStart={(event) =>
|
||||
onDragStart(event, {
|
||||
type: t,
|
||||
node: data[d][t],
|
||||
})
|
||||
}
|
||||
>
|
||||
<div className="flex w-full justify-between text-sm px-3 py-1 items-center border-dashed border-gray-400 dark:border-gray-600 border-l-0 rounded-md rounded-l-none border">
|
||||
<span className="text-black dark:text-white w-36 truncate text-xs">
|
||||
{t}
|
||||
</span>
|
||||
<Bars2Icon className="w-4 h-6 text-gray-400 dark:text-gray-600" />
|
||||
{Object.keys(data)
|
||||
.sort()
|
||||
.map((d: keyof APIObjectType, i) => (
|
||||
<DisclosureComponent
|
||||
key={i}
|
||||
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
|
||||
draggable
|
||||
className={" cursor-grab border-l-8 rounded-l-md"}
|
||||
style={{
|
||||
borderLeftColor: nodeColors[d] ?? nodeColors.unknown,
|
||||
}}
|
||||
onDragStart={(event) =>
|
||||
onDragStart(event, {
|
||||
type: t,
|
||||
node: data[d][t],
|
||||
})
|
||||
}
|
||||
>
|
||||
<div className="flex w-full justify-between text-sm px-3 py-1 items-center border-dashed border-gray-400 dark:border-gray-600 border-l-0 rounded-md rounded-l-none border">
|
||||
<span className="text-black dark:text-white w-36 truncate text-xs">
|
||||
{t}
|
||||
</span>
|
||||
<Bars2Icon className="w-4 h-6 text-gray-400 dark:text-gray-600" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{Object.keys(data[d]).length === 0 && (
|
||||
<div className="text-gray-400 text-center">Coming soon</div>
|
||||
)}
|
||||
</div>
|
||||
</DisclosureComponent>
|
||||
))}
|
||||
))}
|
||||
{Object.keys(data[d]).length === 0 && (
|
||||
<div className="text-gray-400 text-center">Coming soon</div>
|
||||
)}
|
||||
</div>
|
||||
</DisclosureComponent>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,16 @@ import { FlowType } from "../../../../types/flow";
|
|||
|
||||
var _ = require("lodash");
|
||||
|
||||
export default function TabComponent({ selected, flow, onClick }:{flow:FlowType,selected:boolean,onClick:()=>void}) {
|
||||
const { removeFlow, updateFlow, flows } =
|
||||
useContext(TabsContext);
|
||||
export default function TabComponent({
|
||||
selected,
|
||||
flow,
|
||||
onClick,
|
||||
}: {
|
||||
flow: FlowType;
|
||||
selected: boolean;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
const { removeFlow, updateFlow, flows } = useContext(TabsContext);
|
||||
const [isRename, setIsRename] = useState(false);
|
||||
const [value, setValue] = useState("");
|
||||
return (
|
||||
|
|
@ -19,7 +26,7 @@ export default function TabComponent({ selected, flow, onClick }:{flow:FlowType,
|
|||
onClick={onClick}
|
||||
>
|
||||
<span className="w-32 truncate text-left">{flow.name}</span>
|
||||
|
||||
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
|
@ -85,4 +92,3 @@ export default function TabComponent({ selected, flow, onClick }:{flow:FlowType,
|
|||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -94,9 +94,7 @@ export default function TabsManagerComponent() {
|
|||
>
|
||||
<AlertDropdown />
|
||||
</div>
|
||||
<div className="h-screen w-screen fixed top-0 left-0">
|
||||
|
||||
</div>
|
||||
<div className="h-screen w-screen fixed top-0 left-0"></div>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,21 @@
|
|||
import { useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||
import ReactFlow, {
|
||||
Background,
|
||||
Controls,
|
||||
addEdge,
|
||||
useEdgesState,
|
||||
useNodesState,
|
||||
useReactFlow,
|
||||
updateEdge,
|
||||
EdgeChange,
|
||||
Connection,
|
||||
Edge,
|
||||
useKeyPress,
|
||||
useOnSelectionChange,
|
||||
NodeDragHandler,
|
||||
OnEdgesDelete,
|
||||
OnNodesDelete,
|
||||
SelectionDragHandler,
|
||||
Background,
|
||||
Controls,
|
||||
addEdge,
|
||||
useEdgesState,
|
||||
useNodesState,
|
||||
useReactFlow,
|
||||
updateEdge,
|
||||
EdgeChange,
|
||||
Connection,
|
||||
Edge,
|
||||
useKeyPress,
|
||||
useOnSelectionChange,
|
||||
NodeDragHandler,
|
||||
OnEdgesDelete,
|
||||
OnNodesDelete,
|
||||
SelectionDragHandler,
|
||||
} from "reactflow";
|
||||
import { locationContext } from "../../contexts/locationContext";
|
||||
import ExtraSidebar from "./components/extraSidebarComponent";
|
||||
|
|
@ -31,270 +31,270 @@ import { isValidConnection } from "../../utils";
|
|||
import useUndoRedo from "./hooks/useUndoRedo";
|
||||
|
||||
const nodeTypes = {
|
||||
genericNode: GenericNode,
|
||||
genericNode: GenericNode,
|
||||
};
|
||||
|
||||
var _ = require("lodash");
|
||||
|
||||
export default function FlowPage({ flow }:{flow:FlowType}) {
|
||||
let { updateFlow, incrementNodeId} =
|
||||
export default function FlowPage({ flow }: { flow: FlowType }) {
|
||||
let { updateFlow, incrementNodeId } =
|
||||
useContext(TabsContext);
|
||||
const { types, reactFlowInstance, setReactFlowInstance, templates } =
|
||||
useContext(typesContext);
|
||||
const reactFlowWrapper = useRef(null);
|
||||
|
||||
const { undo, redo, canUndo, canRedo, takeSnapshot } = useUndoRedo();
|
||||
const { undo, redo, canUndo, canRedo, takeSnapshot } = useUndoRedo();
|
||||
|
||||
const onKeyDown = (event : React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if ((event.ctrlKey || event.metaKey) && (event.key === 'c') && lastSelection){
|
||||
event.preventDefault();
|
||||
setLastCopiedSelection(lastSelection);
|
||||
}
|
||||
if ((event.ctrlKey || event.metaKey) && (event.key === 'v') && lastCopiedSelection){
|
||||
event.preventDefault();
|
||||
paste();
|
||||
}
|
||||
}
|
||||
const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if ((event.ctrlKey || event.metaKey) && (event.key === 'c') && lastSelection) {
|
||||
event.preventDefault();
|
||||
setLastCopiedSelection(lastSelection);
|
||||
}
|
||||
if ((event.ctrlKey || event.metaKey) && (event.key === 'v') && lastCopiedSelection) {
|
||||
event.preventDefault();
|
||||
paste();
|
||||
}
|
||||
}
|
||||
|
||||
const [lastSelection, setLastSelection] = useState(null);
|
||||
const [lastCopiedSelection, setLastCopiedSelection] = useState(null);
|
||||
const [lastSelection, setLastSelection] = 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) => {
|
||||
setPosition({ x: event.clientX, y: event.clientY });
|
||||
};
|
||||
const handleMouseMove = (event) => {
|
||||
setPosition({ x: event.clientX, y: event.clientY });
|
||||
};
|
||||
|
||||
useOnSelectionChange({
|
||||
onChange: (flow) => {setLastSelection(flow);},
|
||||
})
|
||||
useOnSelectionChange({
|
||||
onChange: (flow) => { setLastSelection(flow); },
|
||||
})
|
||||
|
||||
let paste = () => {
|
||||
let minimumX = Infinity;
|
||||
let minimumY = Infinity;
|
||||
let idsMap = {};
|
||||
lastCopiedSelection.nodes.forEach((n) => {
|
||||
if(n.position.y < minimumY){
|
||||
minimumY = n.position.y
|
||||
}
|
||||
if(n.position.x < minimumX){
|
||||
minimumX = n.position.x;
|
||||
}
|
||||
});
|
||||
let paste = () => {
|
||||
let minimumX = Infinity;
|
||||
let minimumY = Infinity;
|
||||
let idsMap = {};
|
||||
lastCopiedSelection.nodes.forEach((n) => {
|
||||
if (n.position.y < minimumY) {
|
||||
minimumY = n.position.y
|
||||
}
|
||||
if (n.position.x < minimumX) {
|
||||
minimumX = n.position.x;
|
||||
}
|
||||
});
|
||||
|
||||
const bounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||
const insidePosition = reactFlowInstance.project({
|
||||
x: position.x - bounds.left,
|
||||
y: position.y - bounds.top
|
||||
});
|
||||
const bounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||
const insidePosition = reactFlowInstance.project({
|
||||
x: position.x - bounds.left,
|
||||
y: position.y - bounds.top
|
||||
});
|
||||
|
||||
lastCopiedSelection.nodes.forEach((n) => {
|
||||
lastCopiedSelection.nodes.forEach((n) => {
|
||||
|
||||
// Generate a unique node ID
|
||||
let newId = getId();
|
||||
idsMap[n.id] = newId;
|
||||
// Generate a unique node ID
|
||||
let newId = getId();
|
||||
idsMap[n.id] = newId;
|
||||
|
||||
// Create a new node object
|
||||
const newNode: NodeType = {
|
||||
id: newId,
|
||||
type: "genericNode",
|
||||
position: {
|
||||
x: insidePosition.x + n.position.x - minimumX,
|
||||
y: insidePosition.y + n.position.y - minimumY,
|
||||
},
|
||||
data: {
|
||||
...n.data,
|
||||
id: newId,
|
||||
},
|
||||
};
|
||||
// Create a new node object
|
||||
const newNode: NodeType = {
|
||||
id: newId,
|
||||
type: "genericNode",
|
||||
position: {
|
||||
x: insidePosition.x + n.position.x - minimumX,
|
||||
y: insidePosition.y + n.position.y - minimumY,
|
||||
},
|
||||
data: {
|
||||
...n.data,
|
||||
id: newId,
|
||||
},
|
||||
};
|
||||
|
||||
// Add the new node to the list of nodes in state
|
||||
setNodes((nds) => nds.map((e) => ({...e, selected: false})).concat({...newNode, selected: false}));
|
||||
})
|
||||
// Add the new node to the list of nodes in state
|
||||
setNodes((nds) => nds.map((e) => ({ ...e, selected: false })).concat({ ...newNode, selected: false }));
|
||||
})
|
||||
|
||||
lastCopiedSelection.edges.forEach((e) => {
|
||||
let source = idsMap[e.source];
|
||||
let target = idsMap[e.target];
|
||||
let sourceHandleSplitted = e.sourceHandle.split('|');
|
||||
let sourceHandle = sourceHandleSplitted[0] + '|' + source + '|' + sourceHandleSplitted.slice(2).join('|');
|
||||
let targetHandleSplitted = e.targetHandle.split('|');
|
||||
let targetHandle = targetHandleSplitted.slice(0, -1).join('|') + '|' + target;
|
||||
let id = "reactflow__edge-" + source + sourceHandle + "-" + target + targetHandle;
|
||||
setEdges((eds) =>
|
||||
addEdge({ source, target, sourceHandle, targetHandle, id, className: "animate-pulse", selected: false }, eds.map((e) => ({...e, selected: false})))
|
||||
);
|
||||
})
|
||||
}
|
||||
lastCopiedSelection.edges.forEach((e) => {
|
||||
let source = idsMap[e.source];
|
||||
let target = idsMap[e.target];
|
||||
let sourceHandleSplitted = e.sourceHandle.split('|');
|
||||
let sourceHandle = sourceHandleSplitted[0] + '|' + source + '|' + sourceHandleSplitted.slice(2).join('|');
|
||||
let targetHandleSplitted = e.targetHandle.split('|');
|
||||
let targetHandle = targetHandleSplitted.slice(0, -1).join('|') + '|' + target;
|
||||
let id = "reactflow__edge-" + source + sourceHandle + "-" + target + targetHandle;
|
||||
setEdges((eds) =>
|
||||
addEdge({ source, target, sourceHandle, targetHandle, id, className: "animate-pulse", selected: false }, eds.map((e) => ({ ...e, selected: false })))
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const { setExtraComponent, setExtraNavigation } = useContext(locationContext);
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(
|
||||
flow.data?.nodes ?? []
|
||||
);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(
|
||||
flow.data?.edges ?? []
|
||||
);
|
||||
const { setViewport } = useReactFlow();
|
||||
const edgeUpdateSuccessful = useRef(true);
|
||||
const { setExtraComponent, setExtraNavigation } = useContext(locationContext);
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(
|
||||
flow.data?.nodes ?? []
|
||||
);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(
|
||||
flow.data?.edges ?? []
|
||||
);
|
||||
const { setViewport } = useReactFlow();
|
||||
const edgeUpdateSuccessful = useRef(true);
|
||||
|
||||
function getId() {
|
||||
return `dndnode_` + incrementNodeId();
|
||||
}
|
||||
function getId() {
|
||||
return `dndnode_` + incrementNodeId();
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (reactFlowInstance && flow) {
|
||||
flow.data = reactFlowInstance.toObject();
|
||||
updateFlow(flow);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [nodes, edges]);
|
||||
//update flow when tabs change
|
||||
useEffect(() => {
|
||||
setNodes(flow?.data?.nodes ?? []);
|
||||
setEdges(flow?.data?.edges ?? []);
|
||||
if (reactFlowInstance) {
|
||||
setViewport(flow?.data?.viewport ?? { x: 1, y: 0, zoom: 0.5 });
|
||||
}
|
||||
}, [flow, reactFlowInstance, setEdges, setNodes, setViewport]);
|
||||
//set extra sidebar
|
||||
useEffect(() => {
|
||||
setExtraComponent(<ExtraSidebar />);
|
||||
setExtraNavigation({ title: "Components" });
|
||||
}, [setExtraComponent, setExtraNavigation]);
|
||||
useEffect(() => {
|
||||
if (reactFlowInstance && flow) {
|
||||
flow.data = reactFlowInstance.toObject();
|
||||
updateFlow(flow);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [nodes, edges]);
|
||||
//update flow when tabs change
|
||||
useEffect(() => {
|
||||
setNodes(flow?.data?.nodes ?? []);
|
||||
setEdges(flow?.data?.edges ?? []);
|
||||
if (reactFlowInstance) {
|
||||
setViewport(flow?.data?.viewport ?? { x: 1, y: 0, zoom: 0.5 });
|
||||
}
|
||||
}, [flow, reactFlowInstance, setEdges, setNodes, setViewport]);
|
||||
//set extra sidebar
|
||||
useEffect(() => {
|
||||
setExtraComponent(<ExtraSidebar />);
|
||||
setExtraNavigation({ title: "Components" });
|
||||
}, [setExtraComponent, setExtraNavigation]);
|
||||
|
||||
const onEdgesChangeMod = useCallback(
|
||||
(s: EdgeChange[]) => {
|
||||
onEdgesChange(s);
|
||||
setNodes((x) => {
|
||||
let newX = _.cloneDeep(x);
|
||||
return newX;
|
||||
});
|
||||
},
|
||||
[onEdgesChange, setNodes]
|
||||
);
|
||||
const onEdgesChangeMod = useCallback(
|
||||
(s: EdgeChange[]) => {
|
||||
onEdgesChange(s);
|
||||
setNodes((x) => {
|
||||
let newX = _.cloneDeep(x);
|
||||
return newX;
|
||||
});
|
||||
},
|
||||
[onEdgesChange, setNodes]
|
||||
);
|
||||
|
||||
const onConnect = useCallback(
|
||||
(params: Connection) => {
|
||||
takeSnapshot();
|
||||
setEdges((eds) =>
|
||||
addEdge({ ...params, className: "animate-pulse" }, eds)
|
||||
);
|
||||
setNodes((x) => {
|
||||
let newX = _.cloneDeep(x);
|
||||
return newX;
|
||||
});
|
||||
},
|
||||
[setEdges, setNodes, takeSnapshot]
|
||||
);
|
||||
const onConnect = useCallback(
|
||||
(params: Connection) => {
|
||||
takeSnapshot();
|
||||
setEdges((eds) =>
|
||||
addEdge({ ...params, className: "animate-pulse" }, eds)
|
||||
);
|
||||
setNodes((x) => {
|
||||
let newX = _.cloneDeep(x);
|
||||
return newX;
|
||||
});
|
||||
},
|
||||
[setEdges, setNodes, takeSnapshot]
|
||||
);
|
||||
|
||||
const onNodeDragStart: NodeDragHandler = useCallback(() => {
|
||||
// 👇 make dragging a node undoable
|
||||
takeSnapshot();
|
||||
// 👉 you can place your event handlers here
|
||||
}, [takeSnapshot]);
|
||||
const onNodeDragStart: NodeDragHandler = useCallback(() => {
|
||||
// 👇 make dragging a node undoable
|
||||
takeSnapshot();
|
||||
// 👉 you can place your event handlers here
|
||||
}, [takeSnapshot]);
|
||||
|
||||
const onSelectionDragStart: SelectionDragHandler = useCallback(() => {
|
||||
// 👇 make dragging a selection undoable
|
||||
takeSnapshot();
|
||||
}, [takeSnapshot]);
|
||||
const onSelectionDragStart: SelectionDragHandler = useCallback(() => {
|
||||
// 👇 make dragging a selection undoable
|
||||
takeSnapshot();
|
||||
}, [takeSnapshot]);
|
||||
|
||||
|
||||
const onEdgesDelete: OnEdgesDelete = useCallback(() => {
|
||||
// 👇 make deleting edges undoable
|
||||
takeSnapshot();
|
||||
}, [takeSnapshot]);
|
||||
const onEdgesDelete: OnEdgesDelete = useCallback(() => {
|
||||
// 👇 make deleting edges undoable
|
||||
takeSnapshot();
|
||||
}, [takeSnapshot]);
|
||||
|
||||
const onDragOver = useCallback((event: React.DragEvent) => {
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = "move";
|
||||
}, []);
|
||||
const onDragOver = useCallback((event: React.DragEvent) => {
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = "move";
|
||||
}, []);
|
||||
|
||||
const onDrop = useCallback(
|
||||
(event: React.DragEvent) => {
|
||||
event.preventDefault();
|
||||
takeSnapshot();
|
||||
const onDrop = useCallback(
|
||||
(event: React.DragEvent) => {
|
||||
event.preventDefault();
|
||||
takeSnapshot();
|
||||
|
||||
// Get the current bounds of the ReactFlow wrapper element
|
||||
const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||
// Get the current bounds of the ReactFlow wrapper element
|
||||
const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||
|
||||
// Extract the data from the drag event and parse it as a JSON object
|
||||
let data: { type: string; node?: APIClassType } = JSON.parse(
|
||||
event.dataTransfer.getData("json")
|
||||
);
|
||||
// Extract the data from the drag event and parse it as a JSON object
|
||||
let data: { type: string; node?: APIClassType } = JSON.parse(
|
||||
event.dataTransfer.getData("json")
|
||||
);
|
||||
|
||||
// If data type is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node
|
||||
if (
|
||||
data.type !== "chatInput" ||
|
||||
(data.type === "chatInput" &&
|
||||
!reactFlowInstance.getNodes().some((n) => n.type === "chatInputNode"))
|
||||
) {
|
||||
// Calculate the position where the node should be created
|
||||
const position = reactFlowInstance.project({
|
||||
x: event.clientX - reactflowBounds.left,
|
||||
y: event.clientY - reactflowBounds.top,
|
||||
});
|
||||
// If data type is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node
|
||||
if (
|
||||
data.type !== "chatInput" ||
|
||||
(data.type === "chatInput" &&
|
||||
!reactFlowInstance.getNodes().some((n) => n.type === "chatInputNode"))
|
||||
) {
|
||||
// Calculate the position where the node should be created
|
||||
const position = reactFlowInstance.project({
|
||||
x: event.clientX - reactflowBounds.left,
|
||||
y: event.clientY - reactflowBounds.top,
|
||||
});
|
||||
|
||||
// Generate a unique node ID
|
||||
let newId = getId();
|
||||
// Generate a unique node ID
|
||||
let newId = getId();
|
||||
|
||||
// Create a new node object
|
||||
const newNode: NodeType = {
|
||||
id: newId,
|
||||
type: "genericNode",
|
||||
position,
|
||||
data: {
|
||||
...data,
|
||||
id: newId,
|
||||
value: null,
|
||||
},
|
||||
};
|
||||
// Create a new node object
|
||||
const newNode: NodeType = {
|
||||
id: newId,
|
||||
type: "genericNode",
|
||||
position,
|
||||
data: {
|
||||
...data,
|
||||
id: newId,
|
||||
value: null,
|
||||
},
|
||||
};
|
||||
|
||||
// Add the new node to the list of nodes in state
|
||||
setNodes((nds) => nds.concat(newNode));
|
||||
} else {
|
||||
// If a chat input node already exists, set an error message
|
||||
setErrorData({
|
||||
title: "Error creating node",
|
||||
list: ["There can't be more than one chat input."],
|
||||
});
|
||||
}
|
||||
},
|
||||
// Specify dependencies for useCallback
|
||||
[incrementNodeId, reactFlowInstance, setErrorData, setNodes, takeSnapshot]
|
||||
);
|
||||
// Add the new node to the list of nodes in state
|
||||
setNodes((nds) => nds.concat(newNode));
|
||||
} else {
|
||||
// If a chat input node already exists, set an error message
|
||||
setErrorData({
|
||||
title: "Error creating node",
|
||||
list: ["There can't be more than one chat input."],
|
||||
});
|
||||
}
|
||||
},
|
||||
// Specify dependencies for useCallback
|
||||
[incrementNodeId, reactFlowInstance, setErrorData, setNodes, takeSnapshot]
|
||||
);
|
||||
|
||||
const onDelete = useCallback((mynodes) => {
|
||||
takeSnapshot();
|
||||
setEdges(
|
||||
edges.filter(
|
||||
(ns) => !mynodes.some((n) => ns.source === n.id || ns.target === n.id)
|
||||
)
|
||||
);
|
||||
}, [takeSnapshot, edges, setEdges]);
|
||||
const onDelete = useCallback((mynodes) => {
|
||||
takeSnapshot();
|
||||
setEdges(
|
||||
edges.filter(
|
||||
(ns) => !mynodes.some((n) => ns.source === n.id || ns.target === n.id)
|
||||
)
|
||||
);
|
||||
}, [takeSnapshot, edges, setEdges]);
|
||||
|
||||
const onEdgeUpdateStart = useCallback(() => {
|
||||
edgeUpdateSuccessful.current = false;
|
||||
}, []);
|
||||
const onEdgeUpdateStart = useCallback(() => {
|
||||
edgeUpdateSuccessful.current = false;
|
||||
}, []);
|
||||
|
||||
const onEdgeUpdate = useCallback(
|
||||
(oldEdge: Edge, newConnection: Connection) => {
|
||||
if (isValidConnection(newConnection, reactFlowInstance)) {
|
||||
edgeUpdateSuccessful.current = true;
|
||||
setEdges((els) => updateEdge(oldEdge, newConnection, els));
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
const onEdgeUpdate = useCallback(
|
||||
(oldEdge: Edge, newConnection: Connection) => {
|
||||
if (isValidConnection(newConnection, reactFlowInstance)) {
|
||||
edgeUpdateSuccessful.current = true;
|
||||
setEdges((els) => updateEdge(oldEdge, newConnection, els));
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const onEdgeUpdateEnd = useCallback((_, edge) => {
|
||||
if (!edgeUpdateSuccessful.current) {
|
||||
setEdges((eds) => eds.filter((e) => e.id !== edge.id));
|
||||
setEdges((eds) => eds.filter((e) => e.id !== edge.id));
|
||||
}
|
||||
|
||||
|
||||
edgeUpdateSuccessful.current = true;
|
||||
}, []);
|
||||
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="w-full h-full" onMouseMove={handleMouseMove} ref={reactFlowWrapper}>
|
||||
{Object.keys(templates).length > 0 && Object.keys(types).length > 0 ? (
|
||||
|
|
@ -307,7 +307,7 @@ export default function FlowPage({ flow }:{flow:FlowType}) {
|
|||
edges={edges}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChangeMod}
|
||||
onKeyDown={(e) => onKeyDown(e)}
|
||||
onKeyDown={(e) => onKeyDown(e)}
|
||||
onConnect={onConnect}
|
||||
onLoad={setReactFlowInstance}
|
||||
onInit={setReactFlowInstance}
|
||||
|
|
@ -315,16 +315,16 @@ export default function FlowPage({ flow }:{flow:FlowType}) {
|
|||
onEdgeUpdate={onEdgeUpdate}
|
||||
onEdgeUpdateStart={onEdgeUpdateStart}
|
||||
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
||||
onNodeDragStart={onNodeDragStart}
|
||||
onSelectionDragStart={onSelectionDragStart}
|
||||
onEdgesDelete={onEdgesDelete}
|
||||
onNodeDragStart={onNodeDragStart}
|
||||
onSelectionDragStart={onSelectionDragStart}
|
||||
onEdgesDelete={onEdgesDelete}
|
||||
connectionLineComponent={ConnectionLineComponent}
|
||||
onDragOver={onDragOver}
|
||||
onDrop={onDrop}
|
||||
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>
|
||||
</ReactFlow>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import { ReportHandler } from "web-vitals";
|
||||
|
||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
||||
|
|
|
|||
4
src/frontend/src/svg.d.ts
vendored
4
src/frontend/src/svg.d.ts
vendored
|
|
@ -1,4 +1,4 @@
|
|||
declare module "*.svg" {
|
||||
const content: any;
|
||||
export default content;
|
||||
const content: any;
|
||||
export default content;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,29 @@
|
|||
export type ErrorAlertType = {
|
||||
title: string;
|
||||
list: Array<string>;
|
||||
id: string;
|
||||
removeAlert: (id: string) => void;
|
||||
title: string;
|
||||
list: Array<string>;
|
||||
id: string;
|
||||
removeAlert: (id: string) => void;
|
||||
};
|
||||
export type NoticeAlertType = {
|
||||
title: string;
|
||||
link: string;
|
||||
id: string;
|
||||
removeAlert: (id: string) => void;
|
||||
title: string;
|
||||
link: string;
|
||||
id: string;
|
||||
removeAlert: (id: string) => void;
|
||||
};
|
||||
export type SuccessAlertType = {
|
||||
title: string;
|
||||
id: string;
|
||||
removeAlert: (id: string) => void;
|
||||
title: string;
|
||||
id: string;
|
||||
removeAlert: (id: string) => void;
|
||||
};
|
||||
export type SingleAlertComponentType = {
|
||||
dropItem: AlertItemType;
|
||||
removeAlert: (index: string) => void;
|
||||
dropItem: AlertItemType;
|
||||
removeAlert: (index: string) => void;
|
||||
};
|
||||
export type AlertDropdownType = {};
|
||||
export type AlertItemType = {
|
||||
type: "notice" | "error" | "success";
|
||||
title: string;
|
||||
link?: string;
|
||||
list?: Array<string>;
|
||||
id: string;
|
||||
type: "notice" | "error" | "success";
|
||||
title: string;
|
||||
link?: string;
|
||||
list?: Array<string>;
|
||||
id: string;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,37 +4,37 @@ import { Node, Edge, Viewport } from "reactflow";
|
|||
export type APIObjectType = { kind: APIKindType; [key: string]: APIKindType };
|
||||
export type APIKindType = { class: APIClassType; [key: string]: APIClassType };
|
||||
export type APITemplateType = {
|
||||
variable: TemplateVariableType;
|
||||
[key: string]: TemplateVariableType;
|
||||
variable: TemplateVariableType;
|
||||
[key: string]: TemplateVariableType;
|
||||
};
|
||||
export type APIClassType = {
|
||||
base_classes: Array<string>;
|
||||
description: string;
|
||||
template: APITemplateType;
|
||||
[key: string]: Array<string> | string | APITemplateType;
|
||||
base_classes: Array<string>;
|
||||
description: string;
|
||||
template: APITemplateType;
|
||||
[key: string]: Array<string> | string | APITemplateType;
|
||||
};
|
||||
export type TemplateVariableType = {
|
||||
type: string;
|
||||
required: boolean;
|
||||
placeholder?: string;
|
||||
list: boolean;
|
||||
show: boolean;
|
||||
multiline?: boolean;
|
||||
value?: any;
|
||||
[key: string]: any;
|
||||
type: string;
|
||||
required: boolean;
|
||||
placeholder?: string;
|
||||
list: boolean;
|
||||
show: boolean;
|
||||
multiline?: boolean;
|
||||
value?: any;
|
||||
[key: string]: any;
|
||||
};
|
||||
export type sendAllProps = {
|
||||
nodes: Node[];
|
||||
edges: Edge[];
|
||||
name: string;
|
||||
description: string;
|
||||
viewport: Viewport;
|
||||
message: string;
|
||||
nodes: Node[];
|
||||
edges: Edge[];
|
||||
name: string;
|
||||
description: string;
|
||||
viewport: Viewport;
|
||||
message: string;
|
||||
|
||||
chatHistory: { message: string; isSend: boolean }[];
|
||||
chatHistory: { message: string; isSend: boolean }[];
|
||||
};
|
||||
export type errorsTypeAPI = {
|
||||
function: { errors: Array<string> };
|
||||
imports: { errors: Array<string> };
|
||||
function: { errors: Array<string> };
|
||||
imports: { errors: Array<string> };
|
||||
};
|
||||
export type PromptTypeAPI = { input_variables: Array<string> };
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import { FlowType } from "../flow";
|
|||
|
||||
export type ChatType = { flow: FlowType; reactFlowInstance: ReactFlowInstance };
|
||||
export type ChatMessageType = {
|
||||
message: string;
|
||||
isSend: boolean;
|
||||
thought?: string;
|
||||
files?: Array<{ data: string; type: string; data_type: string }>;
|
||||
message: string;
|
||||
isSend: boolean;
|
||||
thought?: string;
|
||||
files?: Array<{ data: string; type: string; data_type: string }>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,85 +1,85 @@
|
|||
import { ForwardRefExoticComponent, ReactElement, ReactNode } from "react";
|
||||
import { NodeDataType } from "../flow/index";
|
||||
export type InputComponentType = {
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
onChange: (value: string) => void;
|
||||
password: boolean;
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
onChange: (value: string) => void;
|
||||
password: boolean;
|
||||
};
|
||||
export type ToggleComponentType = {
|
||||
enabled: boolean;
|
||||
setEnabled: (state: boolean) => void;
|
||||
disabled: boolean;
|
||||
enabled: boolean;
|
||||
setEnabled: (state: boolean) => void;
|
||||
disabled: boolean;
|
||||
};
|
||||
export type DropDownComponentType = {
|
||||
value: string;
|
||||
options: string[];
|
||||
onSelect: (value: string) => void;
|
||||
value: string;
|
||||
options: string[];
|
||||
onSelect: (value: string) => void;
|
||||
};
|
||||
export type ParameterComponentType = {
|
||||
data: NodeDataType;
|
||||
title: string;
|
||||
id: string;
|
||||
color: string;
|
||||
left: boolean;
|
||||
type: string;
|
||||
required?: boolean;
|
||||
name?: string;
|
||||
tooltipTitle: string;
|
||||
data: NodeDataType;
|
||||
title: string;
|
||||
id: string;
|
||||
color: string;
|
||||
left: boolean;
|
||||
type: string;
|
||||
required?: boolean;
|
||||
name?: string;
|
||||
tooltipTitle: string;
|
||||
};
|
||||
export type InputListComponentType = {
|
||||
value: string[];
|
||||
onChange: (value: string[]) => void;
|
||||
disabled: boolean;
|
||||
value: string[];
|
||||
onChange: (value: string[]) => void;
|
||||
disabled: boolean;
|
||||
};
|
||||
|
||||
export type TextAreaComponentType = {
|
||||
disabled: boolean;
|
||||
onChange: (value: string[] | string) => void;
|
||||
value: string;
|
||||
disabled: boolean;
|
||||
onChange: (value: string[] | string) => void;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type FileComponentType = {
|
||||
disabled: boolean;
|
||||
onChange: (value: string[] | string) => void;
|
||||
value: string;
|
||||
suffixes: Array<string>;
|
||||
fileTypes: Array<string>;
|
||||
onFileChange: (value: string) => void;
|
||||
disabled: boolean;
|
||||
onChange: (value: string[] | string) => void;
|
||||
value: string;
|
||||
suffixes: Array<string>;
|
||||
fileTypes: Array<string>;
|
||||
onFileChange: (value: string) => void;
|
||||
};
|
||||
|
||||
export type DisclosureComponentType = {
|
||||
children: ReactNode;
|
||||
button: {
|
||||
title: string;
|
||||
Icon: ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>;
|
||||
buttons?: {
|
||||
Icon: ReactElement;
|
||||
title: string;
|
||||
onClick: (event?: React.MouseEvent) => void;
|
||||
}[];
|
||||
};
|
||||
children: ReactNode;
|
||||
button: {
|
||||
title: string;
|
||||
Icon: ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>;
|
||||
buttons?: {
|
||||
Icon: ReactElement;
|
||||
title: string;
|
||||
onClick: (event?: React.MouseEvent) => void;
|
||||
}[];
|
||||
};
|
||||
};
|
||||
export type FloatComponentType = {
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
onChange: (value: string) => void;
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
|
||||
export type TooltipComponentType = {
|
||||
children: ReactElement;
|
||||
title: string;
|
||||
placement?:
|
||||
| "bottom-end"
|
||||
| "bottom-start"
|
||||
| "bottom"
|
||||
| "left-end"
|
||||
| "left-start"
|
||||
| "left"
|
||||
| "right-end"
|
||||
| "right-start"
|
||||
| "right"
|
||||
| "top-end"
|
||||
| "top-start"
|
||||
| "top";
|
||||
children: ReactElement;
|
||||
title: string;
|
||||
placement?:
|
||||
| "bottom-end"
|
||||
| "bottom-start"
|
||||
| "bottom"
|
||||
| "left-end"
|
||||
| "left-start"
|
||||
| "left"
|
||||
| "right-end"
|
||||
| "right-start"
|
||||
| "right"
|
||||
| "top-end"
|
||||
| "top-start"
|
||||
| "top";
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { HomeIcon } from "@heroicons/react/24/outline";
|
||||
|
||||
export type sidebarNavigationItemType = {
|
||||
name: string;
|
||||
href: string;
|
||||
icon: React.ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>;
|
||||
current: boolean;
|
||||
name: string;
|
||||
href: string;
|
||||
icon: React.ForwardRefExoticComponent<React.SVGProps<SVGSVGElement>>;
|
||||
current: boolean;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,20 +3,20 @@ import { APIClassType } from "../api/index";
|
|||
import { ReactFlowJsonObject, XYPosition } from "reactflow";
|
||||
|
||||
export type FlowType = {
|
||||
name: string;
|
||||
id: string;
|
||||
data: ReactFlowJsonObject;
|
||||
description: string;
|
||||
name: string;
|
||||
id: string;
|
||||
data: ReactFlowJsonObject;
|
||||
description: string;
|
||||
};
|
||||
export type NodeType = {
|
||||
id: string;
|
||||
type?: string;
|
||||
position: XYPosition;
|
||||
data: NodeDataType;
|
||||
id: string;
|
||||
type?: string;
|
||||
position: XYPosition;
|
||||
data: NodeDataType;
|
||||
};
|
||||
export type NodeDataType = {
|
||||
type: string;
|
||||
node?: APIClassType;
|
||||
id: string;
|
||||
value: any;
|
||||
type: string;
|
||||
node?: APIClassType;
|
||||
id: string;
|
||||
value: any;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,17 +1,22 @@
|
|||
import { FlowType } from "../flow";
|
||||
|
||||
export type TabsContextType = {
|
||||
save: () => void;
|
||||
tabIndex: number;
|
||||
setTabIndex: (index: number) => void;
|
||||
flows: Array<FlowType>;
|
||||
removeFlow: (id: string) => void;
|
||||
addFlow: (flowData?: FlowType) => void;
|
||||
updateFlow: (newFlow: FlowType) => void;
|
||||
incrementNodeId: () => number;
|
||||
downloadFlow: (flow: FlowType) => void;
|
||||
uploadFlow: () => void;
|
||||
hardReset: () => void;
|
||||
save: () => void;
|
||||
tabIndex: number;
|
||||
setTabIndex: (index: number) => void;
|
||||
flows: Array<FlowType>;
|
||||
removeFlow: (id: string) => void;
|
||||
addFlow: (flowData?: FlowType) => void;
|
||||
updateFlow: (newFlow: FlowType) => void;
|
||||
incrementNodeId: () => number;
|
||||
downloadFlow: (flow: FlowType) => void;
|
||||
uploadFlow: () => void;
|
||||
hardReset: () => void;
|
||||
};
|
||||
|
||||
export type LangFlowState={ tabIndex:number, flows:FlowType[], id:string, nodeId:number }
|
||||
export type LangFlowState = {
|
||||
tabIndex: number;
|
||||
flows: FlowType[];
|
||||
id: string;
|
||||
nodeId: number;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
|
||||
const template:{[char: string]: string}={}
|
||||
const template: { [char: string]: string } = {};
|
||||
|
||||
export type TemplateContextType = {
|
||||
templates: typeof template;
|
||||
setTemplates: (newState: {}) => void;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import { ReactFlowInstance } from "reactflow";
|
||||
|
||||
const types:{[char: string]: string}={};
|
||||
const template:{[char: string]: string}={}
|
||||
const data:{[char: string]: string}={}
|
||||
|
||||
const types: { [char: string]: string } = {};
|
||||
const template: { [char: string]: string } = {};
|
||||
const data: { [char: string]: string } = {};
|
||||
|
||||
export type typesContextType = {
|
||||
reactFlowInstance: ReactFlowInstance|null;
|
||||
reactFlowInstance: ReactFlowInstance | null;
|
||||
setReactFlowInstance: any;
|
||||
deleteNode: (idx: string) => void;
|
||||
types: typeof types;
|
||||
|
|
|
|||
|
|
@ -317,24 +317,25 @@ export function toFirstUpperCase(str: string) {
|
|||
}
|
||||
|
||||
export function toNormalCase(str: string) {
|
||||
let result = str
|
||||
.split("_")
|
||||
.map((word, index) => {
|
||||
if (index === 0) {
|
||||
return word[0].toUpperCase() + word.slice(1).toLowerCase();
|
||||
}
|
||||
return word.toLowerCase();
|
||||
})
|
||||
.join(" ");
|
||||
let result = str
|
||||
.split("_")
|
||||
.map((word, index) => {
|
||||
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(" ");
|
||||
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) {
|
||||
|
|
@ -397,15 +398,15 @@ export function isValidConnection(
|
|||
}
|
||||
|
||||
export function removeApiKeys(flow: FlowType): FlowType {
|
||||
let cleanFLow = _.cloneDeep(flow);
|
||||
cleanFLow.data.nodes.forEach((node) => {
|
||||
for (const key in node.data.node.template) {
|
||||
if (key.includes("api")) {
|
||||
node.data.node.template[key].value = "";
|
||||
}
|
||||
}
|
||||
});
|
||||
return cleanFLow;
|
||||
let cleanFLow = _.cloneDeep(flow);
|
||||
cleanFLow.data.nodes.forEach((node) => {
|
||||
for (const key in node.data.node.template) {
|
||||
if (key.includes("api")) {
|
||||
node.data.node.template[key].value = "";
|
||||
}
|
||||
}
|
||||
});
|
||||
return cleanFLow;
|
||||
}
|
||||
|
||||
export function updateObject<T extends Record<string, any>>(
|
||||
|
|
@ -443,7 +444,7 @@ export function updateTemplate(
|
|||
reference: APITemplateType,
|
||||
objectToUpdate: APITemplateType
|
||||
): APITemplateType {
|
||||
let clonedObject:APITemplateType = _.cloneDeep(reference);
|
||||
let clonedObject: APITemplateType = _.cloneDeep(reference);
|
||||
|
||||
// Loop through each key in the reference object
|
||||
for (const key in clonedObject) {
|
||||
|
|
@ -454,3 +455,34 @@ export function updateTemplate(
|
|||
}
|
||||
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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -73,6 +73,6 @@ module.exports = {
|
|||
}
|
||||
}
|
||||
})
|
||||
}),require('@tailwindcss/line-clamp')
|
||||
}),require('@tailwindcss/line-clamp'),require('@tailwindcss/typography'),
|
||||
],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from langflow.graph.nodes import (
|
|||
ToolNode,
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
assert isinstance(result, str)
|
||||
# The thought should be a Thought
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue