Merge branch 'dev' into frontChanges
This commit is contained in:
commit
97c60eaa5d
77 changed files with 4438 additions and 2357 deletions
|
|
@ -1,12 +1,13 @@
|
||||||
|
import asyncio
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from langchain.callbacks.base import AsyncCallbackHandler
|
from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler
|
||||||
|
|
||||||
from langflow.api.schemas import ChatResponse
|
from langflow.api.schemas import ChatResponse
|
||||||
|
|
||||||
|
|
||||||
# https://github.com/hwchase17/chat-langchain/blob/master/callback.py
|
# https://github.com/hwchase17/chat-langchain/blob/master/callback.py
|
||||||
class StreamingLLMCallbackHandler(AsyncCallbackHandler):
|
class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler):
|
||||||
"""Callback handler for streaming LLM responses."""
|
"""Callback handler for streaming LLM responses."""
|
||||||
|
|
||||||
def __init__(self, websocket):
|
def __init__(self, websocket):
|
||||||
|
|
@ -15,3 +16,17 @@ class StreamingLLMCallbackHandler(AsyncCallbackHandler):
|
||||||
async def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
|
async def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
|
||||||
resp = ChatResponse(message=token, type="stream", intermediate_steps="")
|
resp = ChatResponse(message=token, type="stream", intermediate_steps="")
|
||||||
await self.websocket.send_json(resp.dict())
|
await self.websocket.send_json(resp.dict())
|
||||||
|
|
||||||
|
|
||||||
|
class StreamingLLMCallbackHandler(BaseCallbackHandler):
|
||||||
|
"""Callback handler for streaming LLM responses."""
|
||||||
|
|
||||||
|
def __init__(self, websocket):
|
||||||
|
self.websocket = websocket
|
||||||
|
|
||||||
|
def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
|
||||||
|
resp = ChatResponse(message=token, type="stream", intermediate_steps="")
|
||||||
|
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
coroutine = self.websocket.send_json(resp.dict())
|
||||||
|
asyncio.run_coroutine_threadsafe(coroutine, loop)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,10 @@
|
||||||
from fastapi import APIRouter, WebSocket
|
from fastapi import (
|
||||||
|
APIRouter,
|
||||||
|
WebSocket,
|
||||||
|
WebSocketDisconnect,
|
||||||
|
WebSocketException,
|
||||||
|
status,
|
||||||
|
)
|
||||||
|
|
||||||
from langflow.api.chat_manager import ChatManager
|
from langflow.api.chat_manager import ChatManager
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
|
|
@ -12,7 +18,9 @@ async def websocket_endpoint(client_id: str, websocket: WebSocket):
|
||||||
"""Websocket endpoint for chat."""
|
"""Websocket endpoint for chat."""
|
||||||
try:
|
try:
|
||||||
await chat_manager.handle_websocket(client_id, websocket)
|
await chat_manager.handle_websocket(client_id, websocket)
|
||||||
except Exception as e:
|
except WebSocketException as exc:
|
||||||
# Log stack trace
|
logger.error(exc)
|
||||||
logger.exception(e)
|
await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=str(exc))
|
||||||
raise e
|
except WebSocketDisconnect as exc:
|
||||||
|
logger.error(exc)
|
||||||
|
await websocket.close(code=status.WS_1000_NORMAL_CLOSURE, reason=str(exc))
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import json
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
from fastapi import WebSocket
|
from fastapi import WebSocket, status
|
||||||
|
|
||||||
from langflow.api.schemas import ChatMessage, ChatResponse, FileResponse
|
from langflow.api.schemas import ChatMessage, ChatResponse, FileResponse
|
||||||
from langflow.cache import cache_manager
|
from langflow.cache import cache_manager
|
||||||
|
|
@ -47,7 +47,6 @@ class ChatManager:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.active_connections: Dict[str, WebSocket] = {}
|
self.active_connections: Dict[str, WebSocket] = {}
|
||||||
self.chat_history = ChatHistory()
|
self.chat_history = ChatHistory()
|
||||||
self.chat_history.attach(self.on_chat_history_update)
|
|
||||||
self.cache_manager = cache_manager
|
self.cache_manager = cache_manager
|
||||||
self.cache_manager.attach(self.update)
|
self.cache_manager.attach(self.update)
|
||||||
|
|
||||||
|
|
@ -91,7 +90,7 @@ class ChatManager:
|
||||||
self.active_connections[client_id] = websocket
|
self.active_connections[client_id] = websocket
|
||||||
|
|
||||||
def disconnect(self, client_id: str):
|
def disconnect(self, client_id: str):
|
||||||
del self.active_connections[client_id]
|
self.active_connections.pop(client_id, None)
|
||||||
|
|
||||||
async def send_message(self, client_id: str, message: str):
|
async def send_message(self, client_id: str, message: str):
|
||||||
websocket = self.active_connections[client_id]
|
websocket = self.active_connections[client_id]
|
||||||
|
|
@ -109,7 +108,7 @@ class ChatManager:
|
||||||
|
|
||||||
graph_data = payload
|
graph_data = payload
|
||||||
start_resp = ChatResponse(message=None, type="start", intermediate_steps="")
|
start_resp = ChatResponse(message=None, type="start", intermediate_steps="")
|
||||||
self.chat_history.add_message(client_id, start_resp)
|
await self.send_json(client_id, start_resp)
|
||||||
|
|
||||||
is_first_message = len(self.chat_history.get_history(client_id=client_id)) == 0
|
is_first_message = len(self.chat_history.get_history(client_id=client_id)) == 0
|
||||||
# Generate result and thought
|
# Generate result and thought
|
||||||
|
|
@ -143,11 +142,12 @@ class ChatManager:
|
||||||
break
|
break
|
||||||
|
|
||||||
response = ChatResponse(
|
response = ChatResponse(
|
||||||
message=result or "",
|
message=result,
|
||||||
intermediate_steps=intermediate_steps.strip(),
|
intermediate_steps=intermediate_steps.strip(),
|
||||||
type="end",
|
type="end",
|
||||||
files=file_responses,
|
files=file_responses,
|
||||||
)
|
)
|
||||||
|
await self.send_json(client_id, response)
|
||||||
self.chat_history.add_message(client_id, response)
|
self.chat_history.add_message(client_id, response)
|
||||||
|
|
||||||
async def handle_websocket(self, client_id: str, websocket: WebSocket):
|
async def handle_websocket(self, client_id: str, websocket: WebSocket):
|
||||||
|
|
@ -171,17 +171,24 @@ class ChatManager:
|
||||||
|
|
||||||
with self.cache_manager.set_client_id(client_id):
|
with self.cache_manager.set_client_id(client_id):
|
||||||
await self.process_message(client_id, payload)
|
await self.process_message(client_id, payload)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Handle any exceptions that might occur
|
# Handle any exceptions that might occur
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
# send a message to the client
|
# send a message to the client
|
||||||
await self.send_message(client_id, str(e))
|
|
||||||
raise e
|
|
||||||
finally:
|
|
||||||
await self.active_connections[client_id].close(
|
await self.active_connections[client_id].close(
|
||||||
code=1000, reason="Client disconnected"
|
code=status.WS_1011_INTERNAL_ERROR, reason=str(e)[:120]
|
||||||
)
|
)
|
||||||
self.disconnect(client_id)
|
self.disconnect(client_id)
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
connection = self.active_connections.get(client_id)
|
||||||
|
if connection:
|
||||||
|
await connection.close(code=1000, reason="Client disconnected")
|
||||||
|
self.disconnect(client_id)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception(e)
|
||||||
|
self.disconnect(client_id)
|
||||||
|
|
||||||
|
|
||||||
async def process_graph(
|
async def process_graph(
|
||||||
|
|
@ -203,8 +210,8 @@ async def process_graph(
|
||||||
# Generate result and thought
|
# Generate result and thought
|
||||||
try:
|
try:
|
||||||
logger.debug("Generating result and thought")
|
logger.debug("Generating result and thought")
|
||||||
result, intermediate_steps = get_result_and_steps(
|
result, intermediate_steps = await get_result_and_steps(
|
||||||
langchain_object, chat_message.message or ""
|
langchain_object, chat_message.message or "", websocket=websocket
|
||||||
)
|
)
|
||||||
logger.debug("Generated result and intermediate_steps")
|
logger.debug("Generated result and intermediate_steps")
|
||||||
return result, intermediate_steps
|
return result, intermediate_steps
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Dict
|
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
|
|
||||||
|
from langflow.api.schemas import (
|
||||||
|
ExportedFlow,
|
||||||
|
GraphData,
|
||||||
|
PredictRequest,
|
||||||
|
PredictResponse,
|
||||||
|
)
|
||||||
from langflow.interface.run import process_graph_cached
|
from langflow.interface.run import process_graph_cached
|
||||||
from langflow.interface.types import build_langchain_types_dict
|
from langflow.interface.types import build_langchain_types_dict
|
||||||
|
|
||||||
|
|
@ -16,10 +21,14 @@ def get_all():
|
||||||
return build_langchain_types_dict()
|
return build_langchain_types_dict()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/predict")
|
@router.post("/predict", response_model=PredictResponse)
|
||||||
def get_load(data: Dict[str, Any]):
|
async def get_load(predict_request: PredictRequest):
|
||||||
try:
|
try:
|
||||||
return process_graph_cached(data)
|
exported_flow: ExportedFlow = predict_request.exported_flow
|
||||||
|
graph_data: GraphData = exported_flow.data
|
||||||
|
data = graph_data.dict()
|
||||||
|
response = process_graph_cached(data, predict_request.message)
|
||||||
|
return PredictResponse(result=response.get("result", ""))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Log stack trace
|
# Log stack trace
|
||||||
logger.exception(e)
|
logger.exception(e)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,37 @@
|
||||||
from typing import Any, Union
|
from typing import Any, Dict, List, Union
|
||||||
|
|
||||||
from pydantic import BaseModel, validator
|
from pydantic import BaseModel, validator
|
||||||
|
|
||||||
|
|
||||||
|
class GraphData(BaseModel):
|
||||||
|
"""Data inside the exported flow."""
|
||||||
|
|
||||||
|
nodes: List[Dict[str, Any]]
|
||||||
|
edges: List[Dict[str, Any]]
|
||||||
|
|
||||||
|
|
||||||
|
class ExportedFlow(BaseModel):
|
||||||
|
"""Exported flow from LangFlow."""
|
||||||
|
|
||||||
|
description: str
|
||||||
|
name: str
|
||||||
|
id: str
|
||||||
|
data: GraphData
|
||||||
|
|
||||||
|
|
||||||
|
class PredictRequest(BaseModel):
|
||||||
|
"""Predict request schema."""
|
||||||
|
|
||||||
|
message: str
|
||||||
|
exported_flow: ExportedFlow
|
||||||
|
|
||||||
|
|
||||||
|
class PredictResponse(BaseModel):
|
||||||
|
"""Predict response schema."""
|
||||||
|
|
||||||
|
result: str
|
||||||
|
|
||||||
|
|
||||||
class ChatMessage(BaseModel):
|
class ChatMessage(BaseModel):
|
||||||
"""Chat message schema."""
|
"""Chat message schema."""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
from abc import ABC
|
||||||
from typing import Any, List, Optional
|
from typing import Any, List, Optional
|
||||||
|
|
||||||
from langchain import LLMChain
|
from langchain import LLMChain
|
||||||
|
|
@ -27,14 +28,31 @@ from langchain.agents.agent_toolkits.vectorstore.prompt import (
|
||||||
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS
|
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS
|
||||||
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS as SQL_FORMAT_INSTRUCTIONS
|
from langchain.agents.mrkl.prompt import FORMAT_INSTRUCTIONS as SQL_FORMAT_INSTRUCTIONS
|
||||||
from langchain.base_language import BaseLanguageModel
|
from langchain.base_language import BaseLanguageModel
|
||||||
|
|
||||||
from langchain.memory.chat_memory import BaseChatMemory
|
from langchain.memory.chat_memory import BaseChatMemory
|
||||||
from langchain.sql_database import SQLDatabase
|
from langchain.sql_database import SQLDatabase
|
||||||
from langchain.tools.python.tool import PythonAstREPLTool
|
from langchain.tools.python.tool import PythonAstREPLTool
|
||||||
from langchain.tools.sql_database.prompt import QUERY_CHECKER
|
from langchain.tools.sql_database.prompt import QUERY_CHECKER
|
||||||
|
|
||||||
|
|
||||||
class JsonAgent(AgentExecutor):
|
class CustomAgentExecutor(AgentExecutor, ABC):
|
||||||
|
"""Custom agent executor"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def function_name():
|
||||||
|
return "CustomAgentExecutor"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def initialize(cls, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def run(self, *args, **kwargs):
|
||||||
|
return super().run(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class JsonAgent(CustomAgentExecutor):
|
||||||
"""Json agent"""
|
"""Json agent"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -70,7 +88,7 @@ class JsonAgent(AgentExecutor):
|
||||||
return super().run(*args, **kwargs)
|
return super().run(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class CSVAgent(AgentExecutor):
|
class CSVAgent(CustomAgentExecutor):
|
||||||
"""CSV agent"""
|
"""CSV agent"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -118,7 +136,7 @@ class CSVAgent(AgentExecutor):
|
||||||
return super().run(*args, **kwargs)
|
return super().run(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class VectorStoreAgent(AgentExecutor):
|
class VectorStoreAgent(CustomAgentExecutor):
|
||||||
"""Vector Store agent"""
|
"""Vector Store agent"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -156,7 +174,7 @@ class VectorStoreAgent(AgentExecutor):
|
||||||
return super().run(*args, **kwargs)
|
return super().run(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class SQLAgent(AgentExecutor):
|
class SQLAgent(CustomAgentExecutor):
|
||||||
"""SQL agent"""
|
"""SQL agent"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -228,7 +246,7 @@ class SQLAgent(AgentExecutor):
|
||||||
return super().run(*args, **kwargs)
|
return super().run(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class VectorStoreRouterAgent(AgentExecutor):
|
class VectorStoreRouterAgent(CustomAgentExecutor):
|
||||||
"""Vector Store Router Agent"""
|
"""Vector Store Router Agent"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -267,7 +285,7 @@ class VectorStoreRouterAgent(AgentExecutor):
|
||||||
return super().run(*args, **kwargs)
|
return super().run(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class InitializeAgent(AgentExecutor):
|
class InitializeAgent(CustomAgentExecutor):
|
||||||
"""Implementation of initialize_agent function"""
|
"""Implementation of initialize_agent function"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ from typing import Any, Type
|
||||||
|
|
||||||
from langchain import PromptTemplate
|
from langchain import PromptTemplate
|
||||||
from langchain.agents import Agent
|
from langchain.agents import Agent
|
||||||
|
from langchain.base_language import BaseLanguageModel
|
||||||
from langchain.chains.base import Chain
|
from langchain.chains.base import Chain
|
||||||
from langchain.chat_models.base import BaseChatModel
|
from langchain.chat_models.base import BaseChatModel
|
||||||
from langchain.base_language import BaseLanguageModel
|
|
||||||
from langchain.tools import BaseTool
|
from langchain.tools import BaseTool
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@ from langchain.agents.load_tools import (
|
||||||
)
|
)
|
||||||
from langchain.agents.loading import load_agent_from_config
|
from langchain.agents.loading import load_agent_from_config
|
||||||
from langchain.agents.tools import Tool
|
from langchain.agents.tools import Tool
|
||||||
|
from langchain.base_language import BaseLanguageModel
|
||||||
from langchain.callbacks.base import BaseCallbackManager
|
from langchain.callbacks.base import BaseCallbackManager
|
||||||
from langchain.chains.loading import load_chain_from_config
|
from langchain.chains.loading import load_chain_from_config
|
||||||
from langchain.base_language import BaseLanguageModel
|
|
||||||
from langchain.llms.loading import load_llm_from_config
|
from langchain.llms.loading import load_llm_from_config
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError
|
||||||
|
|
||||||
|
|
@ -30,55 +30,84 @@ from langflow.utils import util, validate
|
||||||
|
|
||||||
def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
|
def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
|
||||||
"""Instantiate class from module type and key, and params"""
|
"""Instantiate class from module type and key, and params"""
|
||||||
|
params = convert_params_to_sets(params)
|
||||||
|
|
||||||
if node_type in CUSTOM_AGENTS:
|
if node_type in CUSTOM_AGENTS:
|
||||||
if custom_agent := CUSTOM_AGENTS.get(node_type):
|
custom_agent = CUSTOM_AGENTS.get(node_type)
|
||||||
return custom_agent.initialize(**params) # type: ignore
|
if custom_agent:
|
||||||
params = process_params(params)
|
return custom_agent.initialize(**params)
|
||||||
|
|
||||||
class_object = import_by_type(_type=base_type, name=node_type)
|
class_object = import_by_type(_type=base_type, name=node_type)
|
||||||
# check if it is a class before using issubclass
|
return instantiate_based_on_type(class_object, base_type, node_type, params)
|
||||||
|
|
||||||
# if isinstance(class_object, type) and issubclass(class_object, BaseModel):
|
|
||||||
# # validate params
|
|
||||||
# fields = class_object.__fields__
|
|
||||||
# params = {key: value for key, value in params.items() if key in fields}
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
params["disallowed_special"] = set(params["disallowed_special"])
|
||||||
|
return params
|
||||||
|
|
||||||
|
|
||||||
|
def instantiate_based_on_type(class_object, base_type, node_type, params):
|
||||||
if base_type == "agents":
|
if base_type == "agents":
|
||||||
# We need to initialize it differently
|
return instantiate_agent(class_object, params)
|
||||||
return load_agent_executor(class_object, params)
|
|
||||||
elif base_type == "prompts":
|
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 node_type == "ZeroShotPrompt":
|
||||||
if "tools" not in params:
|
if "tools" not in params:
|
||||||
params["tools"] = []
|
params["tools"] = []
|
||||||
return ZeroShotAgent.create_prompt(**params)
|
return ZeroShotAgent.create_prompt(**params)
|
||||||
elif base_type == "tools":
|
return None # Or some other default action
|
||||||
|
|
||||||
|
|
||||||
|
def instantiate_tool(node_type, class_object, params):
|
||||||
if node_type == "JsonSpec":
|
if node_type == "JsonSpec":
|
||||||
params["dict_"] = load_file_into_dict(params.pop("path"))
|
params["dict_"] = load_file_into_dict(params.pop("path"))
|
||||||
return class_object(**params)
|
return class_object(**params)
|
||||||
elif node_type == "PythonFunction":
|
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"]
|
function_string = params["code"]
|
||||||
if isinstance(function_string, str):
|
if isinstance(function_string, str):
|
||||||
return validate.eval_function(function_string)
|
return validate.eval_function(function_string)
|
||||||
raise ValueError("Function should be a string")
|
raise ValueError("Function should be a string")
|
||||||
elif node_type.lower() == "tool":
|
elif node_type.lower() == "tool":
|
||||||
return class_object(**params)
|
return class_object(**params)
|
||||||
elif base_type == "toolkits":
|
return None # Or some other default action
|
||||||
|
|
||||||
|
|
||||||
|
def instantiate_toolkit(node_type, class_object, params):
|
||||||
loaded_toolkit = class_object(**params)
|
loaded_toolkit = class_object(**params)
|
||||||
# Check if node_type has a loader
|
|
||||||
if toolkits_creator.has_create_function(node_type):
|
if toolkits_creator.has_create_function(node_type):
|
||||||
return load_toolkits_executor(node_type, loaded_toolkit, params)
|
return load_toolkits_executor(node_type, loaded_toolkit, params)
|
||||||
return loaded_toolkit
|
return loaded_toolkit
|
||||||
elif base_type == "embeddings":
|
|
||||||
# ? Why remove model from params?
|
|
||||||
try:
|
def instantiate_embedding(class_object, params):
|
||||||
params.pop("model")
|
params.pop("model", None)
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
# remove all params that are not in class_object.__fields__
|
|
||||||
try:
|
try:
|
||||||
return class_object(**params)
|
return class_object(**params)
|
||||||
except ValidationError:
|
except ValidationError:
|
||||||
|
|
@ -88,37 +117,33 @@ def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any:
|
||||||
if key in class_object.__fields__
|
if key in class_object.__fields__
|
||||||
}
|
}
|
||||||
return class_object(**params)
|
return class_object(**params)
|
||||||
elif base_type == "vectorstores":
|
|
||||||
|
|
||||||
|
def instantiate_vectorstore(class_object, params):
|
||||||
if len(params.get("documents", [])) == 0:
|
if len(params.get("documents", [])) == 0:
|
||||||
# Error when the pdf or other source was not correctly
|
|
||||||
# loaded.
|
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"The source you provided did not load correctly or was empty."
|
"The source you provided did not load correctly or was empty."
|
||||||
"This may cause an error in the vectorstore."
|
"This may cause an error in the vectorstore."
|
||||||
)
|
)
|
||||||
return class_object.from_documents(**params)
|
return class_object.from_documents(**params)
|
||||||
elif base_type == "documentloaders":
|
|
||||||
|
|
||||||
|
def instantiate_documentloader(class_object, params):
|
||||||
return class_object(**params).load()
|
return class_object(**params).load()
|
||||||
elif base_type == "textsplitters":
|
|
||||||
|
|
||||||
|
def instantiate_textsplitter(class_object, params):
|
||||||
documents = params.pop("documents")
|
documents = params.pop("documents")
|
||||||
text_splitter = class_object(**params)
|
text_splitter = class_object(**params)
|
||||||
return text_splitter.split_documents(documents)
|
return text_splitter.split_documents(documents)
|
||||||
elif base_type == "utilities":
|
|
||||||
|
|
||||||
|
def instantiate_utility(node_type, class_object, params):
|
||||||
if node_type == "SQLDatabase":
|
if node_type == "SQLDatabase":
|
||||||
return class_object.from_uri(params.pop("uri"))
|
return class_object.from_uri(params.pop("uri"))
|
||||||
|
|
||||||
return class_object(**params)
|
return class_object(**params)
|
||||||
|
|
||||||
|
|
||||||
def process_params(params):
|
|
||||||
"""Process params"""
|
|
||||||
if "allowed_special" in params:
|
|
||||||
params["allowed_special"] = set(params["allowed_special"])
|
|
||||||
if "disallowed_special" in params:
|
|
||||||
params["disallowed_special"] = set(params["disallowed_special"])
|
|
||||||
return params
|
|
||||||
|
|
||||||
|
|
||||||
def load_flow_from_json(path: str, build=True):
|
def load_flow_from_json(path: str, build=True):
|
||||||
# This is done to avoid circular imports
|
# This is done to avoid circular imports
|
||||||
from langflow.graph import Graph
|
from langflow.graph import Graph
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ from typing import Any, Dict
|
||||||
|
|
||||||
from chromadb.errors import NotEnoughElementsException # type: ignore
|
from chromadb.errors import NotEnoughElementsException # type: ignore
|
||||||
|
|
||||||
|
from langflow.api.callback import AsyncStreamingLLMCallbackHandler, StreamingLLMCallbackHandler # type: ignore
|
||||||
from langflow.cache.base import compute_dict_hash, load_cache, memoize_dict
|
from langflow.cache.base import compute_dict_hash, load_cache, memoize_dict
|
||||||
from langflow.graph.graph import Graph
|
from langflow.graph.graph import Graph
|
||||||
from langflow.interface import loading
|
|
||||||
from langflow.utils.logger import logger
|
from langflow.utils.logger import logger
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -66,47 +66,12 @@ def build_langchain_object(data_graph):
|
||||||
return graph.build()
|
return graph.build()
|
||||||
|
|
||||||
|
|
||||||
def process_graph(data_graph: Dict[str, Any]):
|
def process_graph_cached(data_graph: Dict[str, Any], message: str):
|
||||||
"""
|
"""
|
||||||
Process graph by extracting input variables and replacing ZeroShotPrompt
|
Process graph by extracting input variables and replacing ZeroShotPrompt
|
||||||
with PromptTemplate,then run the graph and return the result and thought.
|
with PromptTemplate,then run the graph and return the result and thought.
|
||||||
"""
|
"""
|
||||||
# Load langchain object
|
# Load langchain object
|
||||||
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]):
|
|
||||||
"""
|
|
||||||
Process graph by extracting input variables and replacing ZeroShotPrompt
|
|
||||||
with PromptTemplate,then run the graph and return the result and thought.
|
|
||||||
"""
|
|
||||||
# Load langchain object
|
|
||||||
message = data_graph.pop("message", "")
|
|
||||||
is_first_message = len(data_graph.get("chatHistory", [])) == 0
|
is_first_message = len(data_graph.get("chatHistory", [])) == 0
|
||||||
langchain_object = load_or_build_langchain_object(data_graph, is_first_message)
|
langchain_object = load_or_build_langchain_object(data_graph, is_first_message)
|
||||||
logger.debug("Loaded langchain object")
|
logger.debug("Loaded langchain object")
|
||||||
|
|
@ -119,7 +84,7 @@ def process_graph_cached(data_graph: Dict[str, Any]):
|
||||||
|
|
||||||
# Generate result and thought
|
# Generate result and thought
|
||||||
logger.debug("Generating result and thought")
|
logger.debug("Generating result and thought")
|
||||||
result, thought = get_result_and_steps(langchain_object, message)
|
result, thought = get_result_and_thought(langchain_object, message)
|
||||||
logger.debug("Generated result and thought")
|
logger.debug("Generated result and thought")
|
||||||
return {"result": str(result), "thought": thought.strip()}
|
return {"result": str(result), "thought": thought.strip()}
|
||||||
|
|
||||||
|
|
@ -185,8 +150,9 @@ def fix_memory_inputs(langchain_object):
|
||||||
update_memory_keys(langchain_object, possible_new_mem_key)
|
update_memory_keys(langchain_object, possible_new_mem_key)
|
||||||
|
|
||||||
|
|
||||||
def get_result_and_steps(langchain_object, message: str):
|
async def get_result_and_steps(langchain_object, message: str, **kwargs):
|
||||||
"""Get result and thought from extracted json"""
|
"""Get result and thought from extracted json"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if hasattr(langchain_object, "verbose"):
|
if hasattr(langchain_object, "verbose"):
|
||||||
langchain_object.verbose = True
|
langchain_object.verbose = True
|
||||||
|
|
@ -206,17 +172,21 @@ def get_result_and_steps(langchain_object, message: str):
|
||||||
# https://github.com/hwchase17/langchain/issues/2068
|
# https://github.com/hwchase17/langchain/issues/2068
|
||||||
# Deactivating until we have a frontend solution
|
# Deactivating until we have a frontend solution
|
||||||
# to display intermediate steps
|
# to display intermediate steps
|
||||||
langchain_object.return_intermediate_steps = False
|
langchain_object.return_intermediate_steps = True
|
||||||
|
|
||||||
fix_memory_inputs(langchain_object)
|
fix_memory_inputs(langchain_object)
|
||||||
|
|
||||||
with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer):
|
with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer):
|
||||||
try:
|
try:
|
||||||
output = langchain_object(chat_input)
|
async_callbacks = [AsyncStreamingLLMCallbackHandler(**kwargs)]
|
||||||
except ValueError as exc:
|
output = await langchain_object.acall(
|
||||||
|
chat_input, callbacks=async_callbacks
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
# make the error message more informative
|
# make the error message more informative
|
||||||
logger.debug(f"Error: {str(exc)}")
|
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 = (
|
intermediate_steps = (
|
||||||
output.get("intermediate_steps", []) if isinstance(output, dict) else []
|
output.get("intermediate_steps", []) if isinstance(output, dict) else []
|
||||||
|
|
@ -241,7 +211,7 @@ def get_result_and_steps(langchain_object, message: str):
|
||||||
return result, thought
|
return result, thought
|
||||||
|
|
||||||
|
|
||||||
def async_get_result_and_steps(langchain_object, message: str):
|
def get_result_and_thought(langchain_object, message: str):
|
||||||
"""Get result and thought from extracted json"""
|
"""Get result and thought from extracted json"""
|
||||||
try:
|
try:
|
||||||
if hasattr(langchain_object, "verbose"):
|
if hasattr(langchain_object, "verbose"):
|
||||||
|
|
@ -296,34 +266,6 @@ def async_get_result_and_steps(langchain_object, message: str):
|
||||||
return result, thought
|
return result, thought
|
||||||
|
|
||||||
|
|
||||||
def get_result_and_thought(extracted_json: Dict[str, Any], message: str):
|
|
||||||
"""Get result and thought from extracted json"""
|
|
||||||
try:
|
|
||||||
langchain_object = loading.load_langchain_type_from_config(
|
|
||||||
config=extracted_json
|
|
||||||
)
|
|
||||||
with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer):
|
|
||||||
output = langchain_object(message)
|
|
||||||
intermediate_steps = (
|
|
||||||
output.get("intermediate_steps", []) if isinstance(output, dict) else []
|
|
||||||
)
|
|
||||||
result = (
|
|
||||||
output.get(langchain_object.output_keys[0])
|
|
||||||
if isinstance(output, dict)
|
|
||||||
else output
|
|
||||||
)
|
|
||||||
|
|
||||||
if intermediate_steps:
|
|
||||||
thought = format_intermediate_steps(intermediate_steps)
|
|
||||||
else:
|
|
||||||
thought = output_buffer.getvalue()
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
result = f"Error: {str(e)}"
|
|
||||||
thought = ""
|
|
||||||
return result, thought
|
|
||||||
|
|
||||||
|
|
||||||
def format_intermediate_steps(intermediate_steps):
|
def format_intermediate_steps(intermediate_steps):
|
||||||
formatted_chain = "> Entering new AgentExecutor chain...\n"
|
formatted_chain = "> Entering new AgentExecutor chain...\n"
|
||||||
for step in intermediate_steps:
|
for step in intermediate_steps:
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,9 @@ import os
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from langchain.callbacks.manager import AsyncCallbackManager
|
from langchain.base_language import BaseLanguageModel
|
||||||
from langchain.chat_models import AzureChatOpenAI, ChatOpenAI
|
|
||||||
from langchain.llms import AzureOpenAI, OpenAI
|
|
||||||
from PIL.Image import Image
|
from PIL.Image import Image
|
||||||
|
|
||||||
from langflow.api.callback import StreamingLLMCallbackHandler
|
|
||||||
|
|
||||||
|
|
||||||
def load_file_into_dict(file_path: str) -> dict:
|
def load_file_into_dict(file_path: str) -> dict:
|
||||||
if not os.path.exists(file_path):
|
if not os.path.exists(file_path):
|
||||||
|
|
@ -48,10 +44,7 @@ def try_setting_streaming_options(langchain_object, websocket):
|
||||||
langchain_object.llm_chain, "llm"
|
langchain_object.llm_chain, "llm"
|
||||||
):
|
):
|
||||||
llm = langchain_object.llm_chain.llm
|
llm = langchain_object.llm_chain.llm
|
||||||
if isinstance(llm, (OpenAI, ChatOpenAI, AzureOpenAI, AzureChatOpenAI)):
|
if isinstance(llm, BaseLanguageModel):
|
||||||
llm.streaming = bool(hasattr(llm, "streaming"))
|
llm.streaming = bool(hasattr(llm, "streaming"))
|
||||||
stream_handler = StreamingLLMCallbackHandler(websocket)
|
|
||||||
stream_manager = AsyncCallbackManager([stream_handler])
|
|
||||||
llm.callback_manager = stream_manager
|
|
||||||
|
|
||||||
return langchain_object
|
return langchain_object
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,13 @@ class MidJourneyPromptChainNode(FrontendNode):
|
||||||
multiline=False,
|
multiline=False,
|
||||||
name="llm",
|
name="llm",
|
||||||
),
|
),
|
||||||
|
TemplateField(
|
||||||
|
field_type="BaseChatMemory",
|
||||||
|
required=False,
|
||||||
|
show=True,
|
||||||
|
name="memory",
|
||||||
|
advanced=False,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
description: str = "MidJourneyPromptChain is a chain you can use to generate new MidJourney prompts."
|
description: str = "MidJourneyPromptChain is a chain you can use to generate new MidJourney prompts."
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,10 @@ WORKDIR /home/node/app
|
||||||
COPY --chown=node:node . ./
|
COPY --chown=node:node . ./
|
||||||
|
|
||||||
COPY ./set_proxy.sh .
|
COPY ./set_proxy.sh .
|
||||||
RUN chmod +x set_proxy.sh && ./set_proxy.sh
|
RUN chmod +x set_proxy.sh && \
|
||||||
|
cat set_proxy.sh | tr -d '\r' > set_proxy_unix.sh && \
|
||||||
|
chmod +x set_proxy_unix.sh && \
|
||||||
|
./set_proxy_unix.sh
|
||||||
|
|
||||||
USER node
|
USER node
|
||||||
|
|
||||||
|
|
|
||||||
1749
src/frontend/package-lock.json
generated
1749
src/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -30,11 +30,16 @@
|
||||||
"react-error-boundary": "^4.0.2",
|
"react-error-boundary": "^4.0.2",
|
||||||
"react-icons": "^4.8.0",
|
"react-icons": "^4.8.0",
|
||||||
"react-laag": "^2.0.5",
|
"react-laag": "^2.0.5",
|
||||||
|
"react-markdown": "^8.0.7",
|
||||||
"react-router-dom": "^6.8.1",
|
"react-router-dom": "^6.8.1",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"react-syntax-highlighter": "^15.5.0",
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
"react-tabs": "^6.0.0",
|
"react-tabs": "^6.0.0",
|
||||||
"reactflow": "^11.5.5",
|
"reactflow": "^11.5.5",
|
||||||
|
"rehype-mathjax": "^4.0.2",
|
||||||
|
"remark-gfm": "^3.0.1",
|
||||||
|
"remark-math": "^5.1.1",
|
||||||
"tailwindcss": "^3.2.6",
|
"tailwindcss": "^3.2.6",
|
||||||
"typescript": "^4.9.5",
|
"typescript": "^4.9.5",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
|
|
@ -63,5 +68,8 @@
|
||||||
"last 1 safari version"
|
"last 1 safari version"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"proxy": "http://127.0.0.1:7860"
|
"proxy": "http://127.0.0.1:7860",
|
||||||
|
"devDependencies": {
|
||||||
|
"@tailwindcss/typography": "^0.5.9"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face{
|
@font-face {
|
||||||
font-family: text-security-disc;
|
font-family: text-security-disc;
|
||||||
src: url("assets/text-security-disc.woff") format("woff");
|
src: url("assets/text-security-disc.woff") format("woff");
|
||||||
}
|
}
|
||||||
|
|
@ -198,7 +198,9 @@ export default function ParameterComponent({
|
||||||
save();
|
save();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
):(<></>)}
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,5 @@
|
||||||
import { BugAntIcon, Cog6ToothIcon, ExclamationCircleIcon, InformationCircleIcon, TrashIcon } from "@heroicons/react/24/outline";
|
import { BugAntIcon, Cog6ToothIcon, ExclamationCircleIcon, InformationCircleIcon, TrashIcon } from "@heroicons/react/24/outline";
|
||||||
import {
|
import { classNames, nodeColors, nodeIcons, toNormalCase } from "../../utils";
|
||||||
classNames,
|
|
||||||
nodeColors,
|
|
||||||
nodeIcons,
|
|
||||||
toNormalCase,
|
|
||||||
} from "../../utils";
|
|
||||||
import ParameterComponent from "./components/parameterComponent";
|
import ParameterComponent from "./components/parameterComponent";
|
||||||
import { typesContext } from "../../contexts/typesContext";
|
import { typesContext } from "../../contexts/typesContext";
|
||||||
import { useContext, useState, useEffect, useRef, Fragment } from "react";
|
import { useContext, useState, useEffect, useRef, Fragment } from "react";
|
||||||
|
|
@ -35,7 +30,6 @@ export default function GenericNode({
|
||||||
const { reactFlowInstance } = useContext(typesContext);
|
const { reactFlowInstance } = useContext(typesContext);
|
||||||
const [params, setParams] = useState([]);
|
const [params, setParams] = useState([]);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (reactFlowInstance) {
|
if (reactFlowInstance) {
|
||||||
setParams(Object.values(reactFlowInstance.toObject()));
|
setParams(Object.values(reactFlowInstance.toObject()));
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,14 @@ export default function CrashErrorComponent({ error, resetErrorBoundary }) {
|
||||||
return (
|
return (
|
||||||
<div className="fixed top-0 left-0 w-full h-full flex items-center justify-center bg-gray-800 bg-opacity-50 z-50">
|
<div className="fixed top-0 left-0 w-full h-full flex items-center justify-center bg-gray-800 bg-opacity-50 z-50">
|
||||||
<div className="bg-white max-w-4xl h-1/3 min-h-fit rounded-lg shadow-lg p-8 text-start flex flex-col justify-evenly">
|
<div className="bg-white max-w-4xl h-1/3 min-h-fit rounded-lg shadow-lg p-8 text-start flex flex-col justify-evenly">
|
||||||
<h1 className="text-red-500 text-3xl mb-4">Oops! An unknown error has occurred.</h1>
|
<h1 className="text-red-500 text-3xl mb-4">
|
||||||
|
Oops! An unknown error has occurred.
|
||||||
|
</h1>
|
||||||
<p className="text-gray-700 mb-4 text-xl">
|
<p className="text-gray-700 mb-4 text-xl">
|
||||||
Please click the 'Reset Application' button
|
Please click the 'Reset Application' button to restore the
|
||||||
to restore the application's state. If the error persists, please
|
application's state. If the error persists, please create an issue on
|
||||||
create an issue on our GitHub page. We apologize for any inconvenience
|
our GitHub page. We apologize for any inconvenience this may have
|
||||||
this may have caused.
|
caused.
|
||||||
</p>
|
</p>
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled } from "@mui/material/styles";
|
||||||
import Tooltip, { TooltipProps, tooltipClasses } from '@mui/material/Tooltip';
|
import Tooltip, { TooltipProps, tooltipClasses } from "@mui/material/Tooltip";
|
||||||
|
|
||||||
export const LightTooltip = styled(({ className, ...props }: TooltipProps) => (
|
export const LightTooltip = styled(({ className, ...props }: TooltipProps) => (
|
||||||
<Tooltip {...props} classes={{ popper: className }} />
|
<Tooltip {...props} classes={{ popper: className }} />
|
||||||
))(({ theme }) => ({
|
))(({ theme }) => ({
|
||||||
[`& .${tooltipClasses.tooltip}`]: {
|
[`& .${tooltipClasses.tooltip}`]: {
|
||||||
backgroundColor: theme.palette.common.white,
|
backgroundColor: theme.palette.common.white,
|
||||||
color: 'rgba(0, 0, 0, 0.87)',
|
color: "rgba(0, 0, 0, 0.87)",
|
||||||
boxShadow: theme.shadows[2],
|
boxShadow: theme.shadows[2],
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,14 @@ import { ReactElement } from "react";
|
||||||
import { LightTooltip } from "../LightTooltipComponent";
|
import { LightTooltip } from "../LightTooltipComponent";
|
||||||
import { TooltipComponentType } from "../../types/components";
|
import { TooltipComponentType } from "../../types/components";
|
||||||
|
|
||||||
export default function Tooltip({ children, title,placement }:TooltipComponentType) {
|
export default function Tooltip({
|
||||||
return <LightTooltip placement={placement} title={title} arrow>{children}</LightTooltip>;
|
children,
|
||||||
|
title,
|
||||||
|
placement,
|
||||||
|
}: TooltipComponentType) {
|
||||||
|
return (
|
||||||
|
<LightTooltip placement={placement} title={title} arrow>
|
||||||
|
{children}
|
||||||
|
</LightTooltip>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 { useState } from "react";
|
||||||
import { ChatMessageType } from "../../../types/chat";
|
import { ChatMessageType } from "../../../types/chat";
|
||||||
import { nodeColors } from "../../../utils";
|
import { nodeColors } from "../../../utils";
|
||||||
var Convert = require('ansi-to-html');
|
var Convert = require("ansi-to-html");
|
||||||
var convert = new Convert({newline:true});
|
var convert = new Convert({ newline: true });
|
||||||
|
|
||||||
export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
|
export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
|
||||||
const [hidden, setHidden] = useState(true);
|
const [hidden, setHidden] = useState(true);
|
||||||
|
|
@ -29,12 +33,15 @@ export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
|
||||||
style={{ backgroundColor: nodeColors["thought"] }}
|
style={{ backgroundColor: nodeColors["thought"] }}
|
||||||
className=" text-start inline-block w-full pb-3 pt-3 px-5 cursor-pointer"
|
className=" text-start inline-block w-full pb-3 pt-3 px-5 cursor-pointer"
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: convert.toHtml(chat.thought)
|
__html: convert.toHtml(chat.thought),
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
)}
|
)}
|
||||||
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
|
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
|
||||||
<div className="w-full rounded-b-md px-4 pb-3 pt-3 pr-8" style={{ backgroundColor: nodeColors["chat"] }}>
|
<div
|
||||||
|
className="w-full rounded-b-md px-4 pb-3 pt-3 pr-8"
|
||||||
|
style={{ backgroundColor: nodeColors["chat"] }}
|
||||||
|
>
|
||||||
{chat.message}
|
{chat.message}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
import { Bars3CenterLeftIcon, ChatBubbleBottomCenterTextIcon } from "@heroicons/react/24/outline";
|
import {
|
||||||
|
Bars3CenterLeftIcon,
|
||||||
|
ChatBubbleBottomCenterTextIcon,
|
||||||
|
} from "@heroicons/react/24/outline";
|
||||||
import { nodeColors } from "../../../utils";
|
import { nodeColors } from "../../../utils";
|
||||||
import { PopUpContext } from "../../../contexts/popUpContext";
|
import { PopUpContext } from "../../../contexts/popUpContext";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import ChatModal from "../../../modals/chatModal";
|
import ChatModal from "../../../modals/chatModal";
|
||||||
|
|
||||||
export default function ChatTrigger({open, setOpen,flow}){
|
export default function ChatTrigger({ open, setOpen }) {
|
||||||
const {openPopUp} = useContext(PopUpContext)
|
const { openPopUp } = useContext(PopUpContext);
|
||||||
return(<Transition
|
return (
|
||||||
|
<Transition
|
||||||
show={!open}
|
show={!open}
|
||||||
appear={true}
|
appear={true}
|
||||||
enter="transition ease-out duration-300"
|
enter="transition ease-out duration-300"
|
||||||
|
|
@ -18,7 +22,10 @@ export default function ChatTrigger({open, setOpen,flow}){
|
||||||
leaveTo="translate-y-96"
|
leaveTo="translate-y-96"
|
||||||
>
|
>
|
||||||
<div className="absolute bottom-2 right-3">
|
<div className="absolute bottom-2 right-3">
|
||||||
<div style={{backgroundColor:nodeColors['chat']}} className="border flex justify-center align-center py-1 px-3 w-12 h-12 rounded-full dark:bg-gray-800 dark:border-gray-600 dark:text-white">
|
<div
|
||||||
|
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
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
|
|
@ -33,5 +40,6 @@ export default function ChatTrigger({open, setOpen,flow}){
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Transition>)
|
</Transition>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -26,7 +26,7 @@ export default function Chat({ flow }: ChatType) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ChatModal key={flow.id} flow={flow} open={open} setOpen={setOpen} />
|
<ChatModal key={flow.id} flow={flow} open={open} setOpen={setOpen} />
|
||||||
<ChatTrigger open={open} setOpen={setOpen} flow={flow} />
|
<ChatTrigger open={open} setOpen={setOpen} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,27 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { FloatComponentType } from "../../types/components";
|
import { FloatComponentType } from "../../types/components";
|
||||||
|
|
||||||
export default function FloatComponent({value, onChange, disabled}: FloatComponentType){
|
export default function FloatComponent({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
disabled,
|
||||||
|
}: FloatComponentType) {
|
||||||
const [myValue, setMyValue] = useState(value ?? "");
|
const [myValue, setMyValue] = useState(value ?? "");
|
||||||
useEffect(()=> {
|
useEffect(() => {
|
||||||
if(disabled){
|
if (disabled) {
|
||||||
setMyValue("");
|
setMyValue("");
|
||||||
onChange("");
|
onChange("");
|
||||||
}
|
}
|
||||||
}, [disabled, onChange])
|
}, [disabled, onChange]);
|
||||||
return (
|
return (
|
||||||
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
|
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={myValue}
|
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" : "")}
|
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"
|
placeholder="Type a number from zero to one"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setMyValue(e.target.value);
|
setMyValue(e.target.value);
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ export default function InputFileComponent({
|
||||||
disabled,
|
disabled,
|
||||||
suffixes,
|
suffixes,
|
||||||
fileTypes,
|
fileTypes,
|
||||||
onFileChange
|
onFileChange,
|
||||||
}: FileComponentType) {
|
}: FileComponentType) {
|
||||||
const [myValue, setMyValue] = useState(value);
|
const [myValue, setMyValue] = useState(value);
|
||||||
const { setErrorData } = useContext(alertContext);
|
const { setErrorData } = useContext(alertContext);
|
||||||
|
|
@ -17,23 +17,23 @@ export default function InputFileComponent({
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
setMyValue("");
|
setMyValue("");
|
||||||
onChange("");
|
onChange("");
|
||||||
onFileChange("")
|
onFileChange("");
|
||||||
}
|
}
|
||||||
}, [disabled, onChange]);
|
}, [disabled, onChange]);
|
||||||
|
|
||||||
function attachFile(fileReadEvent: ProgressEvent<FileReader>) {
|
function attachFile(fileReadEvent: ProgressEvent<FileReader>) {
|
||||||
fileReadEvent.preventDefault();
|
fileReadEvent.preventDefault();
|
||||||
const file = fileReadEvent.target.result;
|
const file = fileReadEvent.target.result;
|
||||||
onFileChange(file as string)
|
onFileChange(file as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkFileType(fileName:string):boolean{
|
function checkFileType(fileName: string): boolean {
|
||||||
for (let index = 0; index < suffixes.length; index++) {
|
for (let index = 0; index < suffixes.length; index++) {
|
||||||
if(fileName.endsWith(suffixes[index])){
|
if (fileName.endsWith(suffixes[index])) {
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleButtonClick = () => {
|
const handleButtonClick = () => {
|
||||||
|
|
|
||||||
|
|
@ -4,22 +4,34 @@ import { InputListComponentType } from "../../types/components";
|
||||||
|
|
||||||
var _ = require("lodash");
|
var _ = require("lodash");
|
||||||
|
|
||||||
export default function InputListComponent({ value, onChange, disabled}:InputListComponentType) {
|
export default function InputListComponent({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
disabled,
|
||||||
|
}: InputListComponentType) {
|
||||||
const [inputList, setInputList] = useState(value ?? [""]);
|
const [inputList, setInputList] = useState(value ?? [""]);
|
||||||
useEffect(()=> {
|
useEffect(() => {
|
||||||
if(disabled){
|
if (disabled) {
|
||||||
setInputList([""]);
|
setInputList([""]);
|
||||||
onChange([""]);
|
onChange([""]);
|
||||||
}
|
}
|
||||||
}, [disabled, onChange])
|
}, [disabled, onChange]);
|
||||||
return (
|
return (
|
||||||
<div className={(disabled ? "pointer-events-none cursor-not-allowed" : "") + "flex flex-col gap-3"}>
|
<div
|
||||||
|
className={
|
||||||
|
(disabled ? "pointer-events-none cursor-not-allowed" : "") +
|
||||||
|
"flex flex-col gap-3"
|
||||||
|
}
|
||||||
|
>
|
||||||
{inputList.map((i, idx) => (
|
{inputList.map((i, idx) => (
|
||||||
<div key={idx} className="w-full flex gap-3">
|
<div key={idx} className="w-full flex gap-3">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={i}
|
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" : "")}
|
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"
|
placeholder="Type a text"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setInputList((old) => {
|
setInputList((old) => {
|
||||||
|
|
@ -30,23 +42,33 @@ export default function InputListComponent({ value, onChange, disabled}:InputLis
|
||||||
onChange(inputList);
|
onChange(inputList);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{idx === inputList.length - 1 ?
|
{idx === inputList.length - 1 ? (
|
||||||
<button onClick={() => {setInputList((old) => {
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setInputList((old) => {
|
||||||
let newInputList = _.cloneDeep(old);
|
let newInputList = _.cloneDeep(old);
|
||||||
newInputList.push('');
|
newInputList.push("");
|
||||||
return newInputList;
|
return newInputList;
|
||||||
});
|
});
|
||||||
onChange(inputList);}}>
|
onChange(inputList);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<PlusIcon className="w-4 h-4 hover:text-blue-600" />
|
<PlusIcon className="w-4 h-4 hover:text-blue-600" />
|
||||||
</button>
|
</button>
|
||||||
: <button onClick={() => {setInputList((old) => {
|
) : (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setInputList((old) => {
|
||||||
let newInputList = _.cloneDeep(old);
|
let newInputList = _.cloneDeep(old);
|
||||||
newInputList.splice(idx, 1);
|
newInputList.splice(idx, 1);
|
||||||
return newInputList;
|
return newInputList;
|
||||||
});
|
});
|
||||||
onChange(inputList);}}>
|
onChange(inputList);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<XMarkIcon className="w-4 h-4 hover:text-red-600" />
|
<XMarkIcon className="w-4 h-4 hover:text-red-600" />
|
||||||
</button>}
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,21 @@ export default function IntComponent({
|
||||||
}
|
}
|
||||||
}, [disabled, onChange]);
|
}, [disabled, onChange]);
|
||||||
return (
|
return (
|
||||||
<div className={disabled ? "pointer-events-none cursor-not-allowed w-full" : "w-full"}>
|
<div
|
||||||
|
className={
|
||||||
|
disabled ? "pointer-events-none cursor-not-allowed w-full" : "w-full"
|
||||||
|
}
|
||||||
|
>
|
||||||
<input
|
<input
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
if (event.key !== 'Backspace' && event.key !== 'Enter' && event.key !== 'Delete' && event.key !== 'ArrowLeft' && event.key !== 'ArrowRight' && !/^[-]?\d*$/.test(event.key)) {
|
if (
|
||||||
|
event.key !== "Backspace" &&
|
||||||
|
event.key !== "Enter" &&
|
||||||
|
event.key !== "Delete" &&
|
||||||
|
event.key !== "ArrowLeft" &&
|
||||||
|
event.key !== "ArrowRight" &&
|
||||||
|
!/^[-]?\d*$/.test(event.key)
|
||||||
|
) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,28 @@
|
||||||
type LoadingComponentProps={
|
type LoadingComponentProps = {
|
||||||
remSize:number
|
remSize: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default function LoadingComponent({ remSize }: LoadingComponentProps) {
|
||||||
export default function LoadingComponent({remSize}:LoadingComponentProps){
|
return (
|
||||||
return(
|
|
||||||
<div role="status" className="w-min m-auto">
|
<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">
|
<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"/>
|
aria-hidden="true"
|
||||||
<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"/>
|
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>
|
</svg>
|
||||||
<br></br>
|
<br></br>
|
||||||
<span className="animate-pulse text-blue-600 text-lg">Loading...</span>
|
<span className="animate-pulse text-blue-600 text-lg">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -3,17 +3,21 @@ import { classNames } from "../../utils";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { ToggleComponentType } from "../../types/components";
|
import { ToggleComponentType } from "../../types/components";
|
||||||
|
|
||||||
export default function ToggleComponent({ enabled, setEnabled, disabled }:ToggleComponentType) {
|
export default function ToggleComponent({
|
||||||
useEffect(()=> {
|
enabled,
|
||||||
if(disabled){
|
setEnabled,
|
||||||
|
disabled,
|
||||||
|
}: ToggleComponentType) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (disabled) {
|
||||||
setEnabled(false);
|
setEnabled(false);
|
||||||
}
|
}
|
||||||
}, [disabled, setEnabled])
|
}, [disabled, setEnabled]);
|
||||||
return (
|
return (
|
||||||
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
|
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
|
||||||
<Switch
|
<Switch
|
||||||
checked={enabled}
|
checked={enabled}
|
||||||
onChange={(x:boolean) => {
|
onChange={(x: boolean) => {
|
||||||
setEnabled(x);
|
setEnabled(x);
|
||||||
}}
|
}}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
|
|
@ -25,7 +29,10 @@ export default function ToggleComponent({ enabled, setEnabled, disabled }:Toggle
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
enabled ? "translate-x-5" : "translate-x-0",
|
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"
|
"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
|
<span
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,13 @@ type alertContextType = {
|
||||||
notificationCenter: boolean;
|
notificationCenter: boolean;
|
||||||
setNotificationCenter: (newState: boolean) => void;
|
setNotificationCenter: (newState: boolean) => void;
|
||||||
notificationList: Array<AlertItemType>;
|
notificationList: Array<AlertItemType>;
|
||||||
pushNotificationList: (Object:AlertItemType) => void;
|
pushNotificationList: (Object: AlertItemType) => void;
|
||||||
clearNotificationList: () => void;
|
clearNotificationList: () => void;
|
||||||
removeFromNotificationList: (index: string) => void;
|
removeFromNotificationList: (index: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
//initial values to alertContextType
|
//initial values to alertContextType
|
||||||
const initialValue:alertContextType = {
|
const initialValue: alertContextType = {
|
||||||
errorData: { title: "", list: [] },
|
errorData: { title: "", list: [] },
|
||||||
setErrorData: () => {},
|
setErrorData: () => {},
|
||||||
errorOpen: false,
|
errorOpen: false,
|
||||||
|
|
@ -49,7 +49,7 @@ const initialValue:alertContextType = {
|
||||||
|
|
||||||
export const alertContext = createContext<alertContextType>(initialValue);
|
export const alertContext = createContext<alertContextType>(initialValue);
|
||||||
|
|
||||||
export function AlertProvider({ children }:{children:ReactNode}) {
|
export function AlertProvider({ children }: { children: ReactNode }) {
|
||||||
const [errorData, setErrorDataState] = useState<{
|
const [errorData, setErrorDataState] = useState<{
|
||||||
title: string;
|
title: string;
|
||||||
list?: Array<string>;
|
list?: Array<string>;
|
||||||
|
|
@ -73,11 +73,11 @@ export function AlertProvider({ children }:{children:ReactNode}) {
|
||||||
return newNotificationList;
|
return newNotificationList;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Sets the error data state, opens the error dialog and pushes the new error notification to the notification list
|
* Sets the error data state, opens the error dialog and pushes the new error notification to the notification list
|
||||||
* @param newState An object containing the new error data, including title and optional list of error messages
|
* @param newState An object containing the new error data, including title and optional list of error messages
|
||||||
*/
|
*/
|
||||||
function setErrorData(newState: { title: string; list?: Array<string> }) {
|
function setErrorData(newState: { title: string; list?: Array<string> }) {
|
||||||
setErrorDataState(newState);
|
setErrorDataState(newState);
|
||||||
setErrorOpen(true);
|
setErrorOpen(true);
|
||||||
if (newState.title) {
|
if (newState.title) {
|
||||||
|
|
@ -89,12 +89,12 @@ function setErrorData(newState: { title: string; list?: Array<string> }) {
|
||||||
id: _.uniqueId(),
|
id: _.uniqueId(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Sets the state of the notice data and opens the notice modal, also adds a new notice to the notification center if the title is defined.
|
* Sets the state of the notice data and opens the notice modal, also adds a new notice to the notification center if the title is defined.
|
||||||
* @param newState An object containing the title of the notice and optionally a link.
|
* @param newState An object containing the title of the notice and optionally a link.
|
||||||
*/
|
*/
|
||||||
function setNoticeData(newState: { title: string; link?: string }) {
|
function setNoticeData(newState: { title: string; link?: string }) {
|
||||||
setNoticeDataState(newState);
|
setNoticeDataState(newState);
|
||||||
setNoticeOpen(true);
|
setNoticeOpen(true);
|
||||||
if (newState.title) {
|
if (newState.title) {
|
||||||
|
|
@ -107,25 +107,26 @@ function setNoticeData(newState: { title: string; link?: string }) {
|
||||||
id: _.uniqueId(),
|
id: _.uniqueId(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Update the success data state and show a success alert notification.
|
* Update the success data state and show a success alert notification.
|
||||||
* @param newState - A state object with a "title" property to set in the success data state.
|
* @param newState - A state object with a "title" property to set in the success data state.
|
||||||
*/
|
*/
|
||||||
function setSuccessData(newState: { title: string }) {
|
function setSuccessData(newState: { title: string }) {
|
||||||
setSuccessDataState(newState); // update the success data state with the provided new state
|
setSuccessDataState(newState); // update the success data state with the provided new state
|
||||||
setSuccessOpen(true); // open the success alert
|
setSuccessOpen(true); // open the success alert
|
||||||
|
|
||||||
// If the new state has a "title" property, add a new success notification to the list
|
// If the new state has a "title" property, add a new success notification to the list
|
||||||
if (newState.title) {
|
if (newState.title) {
|
||||||
setNotificationCenter(true); // show the notification center
|
setNotificationCenter(true); // show the notification center
|
||||||
pushNotificationList({ // add the new notification to the list
|
pushNotificationList({
|
||||||
|
// add the new notification to the list
|
||||||
type: "success",
|
type: "success",
|
||||||
title: newState.title,
|
title: newState.title,
|
||||||
id: _.uniqueId(),
|
id: _.uniqueId(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function clearNotificationList() {
|
function clearNotificationList() {
|
||||||
setNotificationList([]);
|
setNotificationList([]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,13 @@ export const darkContext = createContext<darkContextType>(initialValue);
|
||||||
|
|
||||||
export function DarkProvider({ children }) {
|
export function DarkProvider({ children }) {
|
||||||
const [dark, setDark] = useState(false);
|
const [dark, setDark] = useState(false);
|
||||||
useEffect(()=>{
|
useEffect(() => {
|
||||||
if(dark){
|
if (dark) {
|
||||||
document.getElementById("body").classList.add("dark");
|
document.getElementById("body").classList.add("dark");
|
||||||
} else {
|
} else {
|
||||||
document.getElementById("body").classList.remove("dark");
|
document.getElementById("body").classList.remove("dark");
|
||||||
}
|
}
|
||||||
}, [dark])
|
}, [dark]);
|
||||||
return (
|
return (
|
||||||
<darkContext.Provider
|
<darkContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ const initialValue = {
|
||||||
|
|
||||||
export const locationContext = createContext<locationContextType>(initialValue);
|
export const locationContext = createContext<locationContextType>(initialValue);
|
||||||
|
|
||||||
export function LocationProvider({ children }:{children:ReactNode}) {
|
export function LocationProvider({ children }: { children: ReactNode }) {
|
||||||
const [current, setCurrent] = useState(initialValue.current);
|
const [current, setCurrent] = useState(initialValue.current);
|
||||||
const [isStackedOpen, setIsStackedOpen] = useState(
|
const [isStackedOpen, setIsStackedOpen] = useState(
|
||||||
initialValue.isStackedOpen
|
initialValue.isStackedOpen
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,11 @@ const PopUpProvider = ({ children }: PopUpProviderProps) => {
|
||||||
const [popUpElements, setPopUpElements] = useState<JSX.Element[]>([]);
|
const [popUpElements, setPopUpElements] = useState<JSX.Element[]>([]);
|
||||||
|
|
||||||
const openPopUp = (element: JSX.Element) => {
|
const openPopUp = (element: JSX.Element) => {
|
||||||
setPopUpElements(prevPopUps => [element, ...prevPopUps]);
|
setPopUpElements((prevPopUps) => [element, ...prevPopUps]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const closePopUp = () => {
|
const closePopUp = () => {
|
||||||
setPopUpElements(prevPopUps => prevPopUps.slice(1));
|
setPopUpElements((prevPopUps) => prevPopUps.slice(1));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
|
||||||
// create a link element and set its properties
|
// create a link element and set its properties
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
link.href = jsonString;
|
link.href = jsonString;
|
||||||
link.download = `${normalCaseToSnakeCase(flows[tabIndex].name)}.json`;
|
link.download = `${flows[tabIndex].name}.json`;
|
||||||
|
|
||||||
// simulate a click on the link element to trigger the download
|
// simulate a click on the link element to trigger the download
|
||||||
link.click();
|
link.click();
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { createContext, ReactNode, useEffect, useState } from "react";
|
import { createContext, ReactNode, useEffect, useState } from "react";
|
||||||
import { Node} from "reactflow";
|
import { Node } from "reactflow";
|
||||||
import { typesContextType } from "../types/typesContext";
|
import { typesContextType } from "../types/typesContext";
|
||||||
import { getAll } from "../controllers/API";
|
import { getAll } from "../controllers/API";
|
||||||
import { APIKindType } from "../types/api";
|
import { APIKindType } from "../types/api";
|
||||||
|
|
||||||
//context to share types adn functions from nodes to flow
|
//context to share types adn functions from nodes to flow
|
||||||
|
|
||||||
const initialValue:typesContextType = {
|
const initialValue: typesContextType = {
|
||||||
reactFlowInstance: null,
|
reactFlowInstance: null,
|
||||||
setReactFlowInstance: () => {},
|
setReactFlowInstance: () => {},
|
||||||
deleteNode: () => {},
|
deleteNode: () => {},
|
||||||
|
|
@ -14,13 +14,13 @@ const initialValue:typesContextType = {
|
||||||
setTypes: () => {},
|
setTypes: () => {},
|
||||||
templates: {},
|
templates: {},
|
||||||
setTemplates: () => {},
|
setTemplates: () => {},
|
||||||
data:{},
|
data: {},
|
||||||
setData:()=>{}
|
setData: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const typesContext = createContext<typesContextType>(initialValue);
|
export const typesContext = createContext<typesContextType>(initialValue);
|
||||||
|
|
||||||
export function TypesProvider({ children }:{children:ReactNode}) {
|
export function TypesProvider({ children }: { children: ReactNode }) {
|
||||||
const [types, setTypes] = useState({});
|
const [types, setTypes] = useState({});
|
||||||
const [reactFlowInstance, setReactFlowInstance] = useState(null);
|
const [reactFlowInstance, setReactFlowInstance] = useState(null);
|
||||||
const [templates, setTemplates] = useState({});
|
const [templates, setTemplates] = useState({});
|
||||||
|
|
@ -35,11 +35,11 @@ export function TypesProvider({ children }:{children:ReactNode}) {
|
||||||
setData(result.data);
|
setData(result.data);
|
||||||
setTemplates(
|
setTemplates(
|
||||||
Object.keys(result.data).reduce((acc, curr) => {
|
Object.keys(result.data).reduce((acc, curr) => {
|
||||||
Object.keys(result.data[curr]).forEach((c: keyof APIKindType)=>{
|
Object.keys(result.data[curr]).forEach((c: keyof APIKindType) => {
|
||||||
acc[c] = result.data[curr][c]
|
acc[c] = result.data[curr][c];
|
||||||
})
|
});
|
||||||
return acc;
|
return acc;
|
||||||
},{})
|
}, {})
|
||||||
);
|
);
|
||||||
// Set the types by reducing over the keys of the result data and updating the accumulator.
|
// Set the types by reducing over the keys of the result data and updating the accumulator.
|
||||||
setTypes(
|
setTypes(
|
||||||
|
|
@ -59,11 +59,15 @@ export function TypesProvider({ children }:{children:ReactNode}) {
|
||||||
getTypes();
|
getTypes();
|
||||||
}, [setTypes]);
|
}, [setTypes]);
|
||||||
|
|
||||||
function deleteNode(idx:string) {
|
function deleteNode(idx: string) {
|
||||||
reactFlowInstance.setNodes(
|
reactFlowInstance.setNodes(
|
||||||
reactFlowInstance.getNodes().filter((n:Node) => n.id !== idx)
|
reactFlowInstance.getNodes().filter((n: Node) => n.id !== idx)
|
||||||
|
);
|
||||||
|
reactFlowInstance.setEdges(
|
||||||
|
reactFlowInstance
|
||||||
|
.getEdges()
|
||||||
|
.filter((ns) => ns.source !== idx && ns.target !== idx)
|
||||||
);
|
);
|
||||||
reactFlowInstance.setEdges(reactFlowInstance.getEdges().filter((ns) => ns.source !== idx && ns.target !== idx));
|
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<typesContext.Provider
|
<typesContext.Provider
|
||||||
|
|
@ -76,7 +80,7 @@ export function TypesProvider({ children }:{children:ReactNode}) {
|
||||||
setTemplates,
|
setTemplates,
|
||||||
templates,
|
templates,
|
||||||
data,
|
data,
|
||||||
setData
|
setData,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,13 @@
|
||||||
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
|
||||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||||
sans-serif;
|
sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
||||||
monospace;
|
monospace;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ const root = ReactDOM.createRoot(
|
||||||
root.render(
|
root.render(
|
||||||
<ContextWrapper>
|
<ContextWrapper>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
|
|
||||||
<App />
|
<App />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</ContextWrapper>
|
</ContextWrapper>
|
||||||
|
|
|
||||||
|
|
@ -87,9 +87,13 @@ export default function NodeModal({ data }: { data: NodeDataType }) {
|
||||||
<div className="flex h-full w-full">
|
<div className="flex h-full w-full">
|
||||||
<div className="overflow-hidden px-4 sm:p-4 w-full rounded-lg bg-white dark:bg-gray-800 shadow">
|
<div className="overflow-hidden px-4 sm:p-4 w-full rounded-lg bg-white dark:bg-gray-800 shadow">
|
||||||
<div className="flex flex-col h-full gap-5">
|
<div className="flex flex-col h-full gap-5">
|
||||||
{
|
{Object.keys(data.node.template)
|
||||||
Object.keys(data.node.template)
|
.filter(
|
||||||
.filter((t) => t.charAt(0) !== "_"&& data.node.template[t].advanced && data.node.template[t].show)
|
(t) =>
|
||||||
|
t.charAt(0) !== "_" &&
|
||||||
|
data.node.template[t].advanced &&
|
||||||
|
data.node.template[t].show
|
||||||
|
)
|
||||||
.map((t: string, idx) => {
|
.map((t: string, idx) => {
|
||||||
return (
|
return (
|
||||||
<ModalField
|
<ModalField
|
||||||
|
|
@ -99,9 +103,7 @@ export default function NodeModal({ data }: { data: NodeDataType }) {
|
||||||
data.node.template[t].display_name
|
data.node.template[t].display_name
|
||||||
? data.node.template[t].display_name
|
? data.node.template[t].display_name
|
||||||
: data.node.template[t].name
|
: data.node.template[t].name
|
||||||
? toNormalCase(
|
? toNormalCase(data.node.template[t].name)
|
||||||
data.node.template[t].name
|
|
||||||
)
|
|
||||||
: toNormalCase(t)
|
: toNormalCase(t)
|
||||||
}
|
}
|
||||||
required={data.node.template[t].required}
|
required={data.node.template[t].required}
|
||||||
|
|
@ -116,8 +118,7 @@ export default function NodeModal({ data }: { data: NodeDataType }) {
|
||||||
type={data.node.template[t].type}
|
type={data.node.template[t].type}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})
|
})}
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ export default function ChatInput({
|
||||||
}}
|
}}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
disabled={lockChat}
|
disabled={lockChat}
|
||||||
style={{resize: "none" }}
|
style={{ resize: "none" }}
|
||||||
value={lockChat ? "Thinking..." : chatValue}
|
value={lockChat ? "Thinking..." : chatValue}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setChatValue(e.target.value);
|
setChatValue(e.target.value);
|
||||||
|
|
|
||||||
|
|
@ -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,20 +1,31 @@
|
||||||
import { ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline";
|
import { ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { ChatMessageType } from "../../../types/chat";
|
import { ChatMessageType } from "../../../types/chat";
|
||||||
import { classNames } from "../../../utils";
|
import { classNames } from "../../../utils";
|
||||||
import AiIcon from "../../../assets/Gooey Ring-5s-271px.svg";
|
import AiIcon from "../../../assets/Gooey Ring-5s-271px.svg";
|
||||||
import { UserIcon } from "@heroicons/react/24/solid";
|
import { UserIcon } from "@heroicons/react/24/solid";
|
||||||
import FileCard from "../fileComponent";
|
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 = require("ansi-to-html");
|
||||||
var convert = new Convert({ newline: true });
|
var convert = new Convert({ newline: true });
|
||||||
|
|
||||||
export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
|
export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
|
||||||
|
const [message, setMessage] = useState("");
|
||||||
|
useEffect(() => {
|
||||||
|
setMessage(chat.message);
|
||||||
|
}, [chat.message]);
|
||||||
const [hidden, setHidden] = useState(true);
|
const [hidden, setHidden] = useState(true);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"w-full py-2 pl-2 flex",
|
"w-full py-2 pl-2 flex",
|
||||||
chat.isSend ? "bg-white dark:bg-gray-800 " : "bg-gray-200 dark:bg-gray-700"
|
chat.isSend
|
||||||
|
? "bg-white dark:bg-gray-800 "
|
||||||
|
: "bg-gray-200 dark:bg-gray-700"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|
@ -49,8 +60,49 @@ export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
|
||||||
)}
|
)}
|
||||||
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
|
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
|
||||||
<div className="w-full px-4 pb-3 pt-3 pr-8">
|
<div className="w-full px-4 pb-3 pt-3 pr-8">
|
||||||
<span className="dark:text-white">
|
<div className="dark:text-white">
|
||||||
{chat.message}
|
<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 && (
|
{chat.files && (
|
||||||
<div className="my-2 w-full">
|
<div className="my-2 w-full">
|
||||||
{chat.files.map((file, index) => {
|
{chat.files.map((file, index) => {
|
||||||
|
|
@ -66,14 +118,20 @@ export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full flex items-center">
|
<div className="w-full flex items-center">
|
||||||
<div className="text-start inline-block px-3 text-sm text-gray-600 dark:text-white">
|
<div className="text-start inline-block px-3 text-sm text-gray-600 dark:text-white">
|
||||||
{chat.message}
|
<ReactMarkdown
|
||||||
|
remarkPlugins={[remarkGfm, remarkMath]}
|
||||||
|
rehypePlugins={[rehypeMathjax]}
|
||||||
|
className="markdown prose"
|
||||||
|
>
|
||||||
|
{message}
|
||||||
|
</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,55 @@
|
||||||
import { CloudArrowDownIcon, DocumentIcon } from "@heroicons/react/24/outline";
|
import { CloudArrowDownIcon, DocumentIcon } from "@heroicons/react/24/outline";
|
||||||
import * as base64js from 'base64-js';
|
import * as base64js from "base64-js";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
export default function FileCard({ fileName, content, fileType }) {
|
export default function FileCard({ fileName, content, fileType }) {
|
||||||
const handleDownload = () => {
|
const handleDownload = () => {
|
||||||
const byteArray = new Uint8Array(base64js.toByteArray(content));
|
const byteArray = new Uint8Array(base64js.toByteArray(content));
|
||||||
const blob = new Blob([byteArray], { type: 'application/octet-stream' });
|
const blob = new Blob([byteArray], { type: "application/octet-stream" });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const link = document.createElement('a');
|
const link = document.createElement("a");
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.download = fileName+".png";
|
link.download = fileName + ".png";
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
};
|
};
|
||||||
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
function handleMouseEnter() {
|
||||||
|
setIsHovered(true);
|
||||||
|
}
|
||||||
|
function handleMouseLeave() {
|
||||||
|
setIsHovered(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileType === "image") {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="relative w-1/4 h-1/4"
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={`data:image/png;base64,${content}`}
|
||||||
|
alt="generated image"
|
||||||
|
className="rounded-lg w-full h-full"
|
||||||
|
/>
|
||||||
|
{isHovered && (
|
||||||
|
<div
|
||||||
|
className={`absolute top-0 right-0 bg-gray-100 text-gray-700 rounded-bl-lg px-1 text-sm font-bold dark:bg-gray-700 dark:text-gray-300`}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
className="text-gray-500 py-1 px-2 dark:bg-gray-700 dark:text-gray-300"
|
||||||
|
onClick={handleDownload}
|
||||||
|
>
|
||||||
|
<CloudArrowDownIcon className="hover:scale-110 w-5 h-5 text-current"></CloudArrowDownIcon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
|
@ -22,12 +58,19 @@ export default function FileCard({ fileName, content, fileType }) {
|
||||||
>
|
>
|
||||||
<div className="flex gap-2 text-current items-center w-full mr-2">
|
<div className="flex gap-2 text-current items-center w-full mr-2">
|
||||||
{" "}
|
{" "}
|
||||||
|
{fileType === "image" ? (
|
||||||
|
<img
|
||||||
|
src={`data:image/png;base64,${content}`}
|
||||||
|
alt=""
|
||||||
|
className="w-8 h-8"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
<DocumentIcon className="w-8 h-8" />
|
<DocumentIcon className="w-8 h-8" />
|
||||||
|
)}
|
||||||
<div className="flex flex-col items-start">
|
<div className="flex flex-col items-start">
|
||||||
{" "}
|
{" "}
|
||||||
<div className="truncate text-sm text-current">{fileName}</div>
|
<div className="truncate text-sm text-current">{fileName}</div>
|
||||||
<div className="truncate text-xs text-gray-500">{fileType}</div>
|
<div className="truncate text-xs text-gray-500">{fileType}</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<CloudArrowDownIcon className="w-6 h-6 text-current ml-auto" />
|
<CloudArrowDownIcon className="w-6 h-6 text-current ml-auto" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,7 @@
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import {
|
import { ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline";
|
||||||
ChatBubbleOvalLeftEllipsisIcon,
|
|
||||||
LockClosedIcon,
|
|
||||||
PaperAirplaneIcon,
|
|
||||||
} from "@heroicons/react/24/outline";
|
|
||||||
import { Fragment, useContext, useEffect, useRef, useState } from "react";
|
import { Fragment, useContext, useEffect, useRef, useState } from "react";
|
||||||
import { PopUpContext } from "../../contexts/popUpContext";
|
|
||||||
import { FlowType, NodeType } from "../../types/flow";
|
import { FlowType, NodeType } from "../../types/flow";
|
||||||
import { TabsContext } from "../../contexts/tabsContext";
|
|
||||||
import { alertContext } from "../../contexts/alertContext";
|
import { alertContext } from "../../contexts/alertContext";
|
||||||
import { toNormalCase } from "../../utils";
|
import { toNormalCase } from "../../utils";
|
||||||
import { typesContext } from "../../contexts/typesContext";
|
import { typesContext } from "../../contexts/typesContext";
|
||||||
|
|
@ -32,8 +26,15 @@ export default function ChatModal({
|
||||||
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
|
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
|
||||||
const { reactFlowInstance } = useContext(typesContext);
|
const { reactFlowInstance } = useContext(typesContext);
|
||||||
const { setErrorData, setNoticeData } = useContext(alertContext);
|
const { setErrorData, setNoticeData } = useContext(alertContext);
|
||||||
const [ws, setWs] = useState<WebSocket | null>(null);
|
const ws = useRef<WebSocket | null>(null);
|
||||||
const [lockChat, setLockChat] = useState(false);
|
const [lockChat, setLockChat] = useState(false);
|
||||||
|
const isOpen = useRef(open);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
isOpen.current = open;
|
||||||
|
}, [open]);
|
||||||
|
var isStream = false;
|
||||||
|
|
||||||
const addChatHistory = (
|
const addChatHistory = (
|
||||||
message: string,
|
message: string,
|
||||||
isSend: boolean,
|
isSend: boolean,
|
||||||
|
|
@ -53,29 +54,53 @@ export default function ChatModal({
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function connectWS() {
|
//add proper type signature for function
|
||||||
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}`;
|
|
||||||
|
|
||||||
const newWs = new WebSocket(urlWs);
|
function updateLastMessage({
|
||||||
newWs.onopen = () => {
|
str,
|
||||||
console.log("WebSocket connection established!");
|
thought,
|
||||||
};
|
end = false,
|
||||||
newWs.onmessage = (event) => {
|
files,
|
||||||
try {
|
}: {
|
||||||
|
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);
|
setLockChat(false);
|
||||||
const data = JSON.parse(event.data);
|
setTimeout(() => {
|
||||||
console.log("Received data:", data);
|
connectWS();
|
||||||
//get chat history
|
}, 1000);
|
||||||
if (Array.isArray(data)) {
|
}
|
||||||
console.log(data);
|
}
|
||||||
|
|
||||||
|
function handleWsMessage(data: any) {
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
//set chat history
|
||||||
setChatHistory((_) => {
|
setChatHistory((_) => {
|
||||||
let newChatHistory: ChatMessageType[] = [];
|
let newChatHistory: ChatMessageType[] = [];
|
||||||
data.forEach(
|
data.forEach(
|
||||||
|
|
@ -107,77 +132,94 @@ export default function ChatModal({
|
||||||
return newChatHistory;
|
return newChatHistory;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (data.type === "start") {
|
||||||
|
addChatHistory("", false);
|
||||||
|
isStream = true;
|
||||||
|
}
|
||||||
if (data.type === "end") {
|
if (data.type === "end") {
|
||||||
|
if (data.intermediate_steps) {
|
||||||
|
updateLastMessage({
|
||||||
|
str: data.message,
|
||||||
|
thought: data.intermediate_steps,
|
||||||
|
end: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
if (data.files) {
|
if (data.files) {
|
||||||
addChatHistory(
|
updateLastMessage({
|
||||||
data.message,
|
end: true,
|
||||||
false,
|
files: data.files,
|
||||||
data.intermediate_steps,
|
});
|
||||||
data.files
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
addChatHistory(data.message, false, data.intermediate_steps);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (data.type == "file") {
|
|
||||||
console.log(data);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (event.data !== "Error: 1005") {
|
|
||||||
setErrorData({ title: event.data });
|
|
||||||
newWs.close();
|
|
||||||
connectWS();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
newWs.onclose = (_) => {
|
|
||||||
if (open) {
|
|
||||||
setLockChat(false);
|
setLockChat(false);
|
||||||
setTimeout(() => {
|
isStream = false;
|
||||||
connectWS();
|
|
||||||
}, 1000);
|
|
||||||
}
|
}
|
||||||
|
if (data.type === "stream" && isStream) {
|
||||||
|
updateLastMessage({ str: data.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectWS() {
|
||||||
|
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}`;
|
||||||
|
|
||||||
|
const newWs = new WebSocket(urlWs);
|
||||||
|
newWs.onopen = () => {
|
||||||
|
console.log("WebSocket connection established!");
|
||||||
|
};
|
||||||
|
console.log(flow.id);
|
||||||
|
newWs.onmessage = (event) => {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
console.log("Received data:", data);
|
||||||
|
handleWsMessage(data);
|
||||||
|
//get chat history
|
||||||
|
};
|
||||||
|
newWs.onclose = (event) => {
|
||||||
|
handleOnClose(event);
|
||||||
};
|
};
|
||||||
newWs.onerror = (ev) => {
|
newWs.onerror = (ev) => {
|
||||||
console.log(ev, "error");
|
console.log(ev, "error");
|
||||||
|
setErrorData({
|
||||||
|
title: "There was an error on web connection, please: ",
|
||||||
|
list: [
|
||||||
|
"Refresh the page",
|
||||||
|
"Use a new flow tab",
|
||||||
|
"Check if the backend is up",
|
||||||
|
],
|
||||||
|
});
|
||||||
};
|
};
|
||||||
setWs(newWs);
|
ws.current = newWs;
|
||||||
|
|
||||||
return newWs;
|
|
||||||
} catch {
|
} catch {
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title: "There was an error on web connection, please: ",
|
title: "There was an error on web connection, please: ",
|
||||||
list: [
|
list: [
|
||||||
"refresh the page",
|
"Refresh the page",
|
||||||
"use a new flow tab",
|
"Use a new flow tab",
|
||||||
"check if the backend is up",
|
"Check if the backend is up",
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ws && (ws.readyState === ws.CLOSED || ws.readyState === ws.CLOSING)) {
|
connectWS();
|
||||||
let newWs = connectWS();
|
|
||||||
return () => {
|
return () => {
|
||||||
console.log("trigger");
|
console.log("unmount");
|
||||||
newWs.close();
|
console.log(ws);
|
||||||
};
|
if (ws) {
|
||||||
|
ws.current.close();
|
||||||
}
|
}
|
||||||
}, [lockChat]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let newWs = connectWS();
|
|
||||||
return () => {
|
|
||||||
console.log("trigger");
|
|
||||||
newWs.close();
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
async function sendAll(data: sendAllProps) {
|
async function sendAll(data: sendAllProps) {
|
||||||
try {
|
try {
|
||||||
if (ws) {
|
if (ws) {
|
||||||
ws.send(JSON.stringify(data));
|
ws.current.send(JSON.stringify(data));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setErrorData({
|
setErrorData({
|
||||||
|
|
@ -193,12 +235,6 @@ export default function ChatModal({
|
||||||
if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" });
|
if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" });
|
||||||
}, [chatHistory]);
|
}, [chatHistory]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (ws && ws.readyState === ws.CLOSED) {
|
|
||||||
setLockChat(false);
|
|
||||||
}
|
|
||||||
}, [lockChat]);
|
|
||||||
|
|
||||||
function validateNode(n: NodeType): Array<string> {
|
function validateNode(n: NodeType): Array<string> {
|
||||||
if (!n.data?.node?.template || !Object.keys(n.data.node.template)) {
|
if (!n.data?.node?.template || !Object.keys(n.data.node.template)) {
|
||||||
setNoticeData({
|
setNoticeData({
|
||||||
|
|
@ -227,8 +263,7 @@ export default function ChatModal({
|
||||||
e.targetHandle.split("|")[2] === n.id
|
e.targetHandle.split("|")[2] === n.id
|
||||||
)
|
)
|
||||||
? [
|
? [
|
||||||
`${type} is missing ${
|
`${type} is missing ${template.display_name
|
||||||
template.display_name
|
|
||||||
? template.display_name
|
? template.display_name
|
||||||
: toNormalCase(template[t].name)
|
: toNormalCase(template[t].name)
|
||||||
}.`,
|
}.`,
|
||||||
|
|
@ -255,7 +290,6 @@ export default function ChatModal({
|
||||||
let message = chatValue;
|
let message = chatValue;
|
||||||
setChatValue("");
|
setChatValue("");
|
||||||
addChatHistory(message, true);
|
addChatHistory(message, true);
|
||||||
|
|
||||||
sendAll({
|
sendAll({
|
||||||
...reactFlowInstance.toObject(),
|
...reactFlowInstance.toObject(),
|
||||||
message,
|
message,
|
||||||
|
|
@ -278,20 +312,14 @@ export default function ChatModal({
|
||||||
}
|
}
|
||||||
function clearChat() {
|
function clearChat() {
|
||||||
setChatHistory([]);
|
setChatHistory([]);
|
||||||
ws.send(JSON.stringify({ clear_history: true }));
|
ws.current.send(JSON.stringify({ clear_history: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
const { closePopUp } = useContext(PopUpContext);
|
|
||||||
function setModalOpen(x: boolean) {
|
function setModalOpen(x: boolean) {
|
||||||
setOpen(x);
|
setOpen(x);
|
||||||
if (x === false) {
|
|
||||||
setTimeout(() => {
|
|
||||||
closePopUp();
|
|
||||||
}, 300);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={open} appear={true} as={Fragment}>
|
<Transition.Root show={open} appear={open} as={Fragment}>
|
||||||
<Dialog
|
<Dialog
|
||||||
as="div"
|
as="div"
|
||||||
className="relative z-10"
|
className="relative z-10"
|
||||||
|
|
@ -321,11 +349,11 @@ export default function ChatModal({
|
||||||
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
|
||||||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className=" drop-shadow-2xl relative flex flex-col justify-between transform h-[95%] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[690px]">
|
<Dialog.Panel className=" drop-shadow-2xl relative flex flex-col justify-between transform h-[95%] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all w-[690px]">
|
||||||
<div className="relative w-full">
|
<div className="relative w-full p-4">
|
||||||
<button
|
<button
|
||||||
onClick={() => clearChat()}
|
onClick={() => clearChat()}
|
||||||
className="absolute top-2 right-2 hover:text-red-500 dark:text-gray-300 dark:hover:text-red-500"
|
className="absolute top-2 right-3 hover:text-red-500 dark:text-gray-300 dark:hover:text-red-500 z-30"
|
||||||
>
|
>
|
||||||
<FaEraser className="w-4 h-4" />
|
<FaEraser className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -334,7 +362,7 @@ export default function ChatModal({
|
||||||
{chatHistory.length > 0 ? (
|
{chatHistory.length > 0 ? (
|
||||||
chatHistory.map((c, i) => <ChatMessage chat={c} key={i} />)
|
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>
|
||||||
👋{" "}
|
👋{" "}
|
||||||
<span className="text-gray-600 dark:text-gray-300 text-lg">
|
<span className="text-gray-600 dark:text-gray-300 text-lg">
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ export default function CodeAreaModal({
|
||||||
title: "Code is ready to run",
|
title: "Code is ready to run",
|
||||||
});
|
});
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
setValue(code)
|
setValue(code);
|
||||||
} else {
|
} else {
|
||||||
if (funcErrors.length !== 0) {
|
if (funcErrors.length !== 0) {
|
||||||
setErrorData({
|
setErrorData({
|
||||||
|
|
@ -142,7 +142,7 @@ export default function CodeAreaModal({
|
||||||
list: funcErrors,
|
list: funcErrors,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if(importsErrors.length!==0){
|
if (importsErrors.length !== 0) {
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title: "There is an error in your imports",
|
title: "There is an error in your imports",
|
||||||
list: importsErrors,
|
list: importsErrors,
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,8 @@ export default function PromptAreaModal({
|
||||||
return setErrorData({
|
return setErrorData({
|
||||||
title:
|
title:
|
||||||
"There is something wrong with this prompt, please review it",
|
"There is something wrong with this prompt, please review it",
|
||||||
list:[error.response.data.detail]});
|
list: [error.response.data.detail],
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,28 @@
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
import { XMarkIcon, ClipboardDocumentListIcon } from "@heroicons/react/24/outline";
|
import {
|
||||||
|
XMarkIcon,
|
||||||
|
ClipboardDocumentListIcon,
|
||||||
|
} from "@heroicons/react/24/outline";
|
||||||
import { Fragment, useContext, useRef, useState } from "react";
|
import { Fragment, useContext, useRef, useState } from "react";
|
||||||
import { PopUpContext } from "../../contexts/popUpContext";
|
import { PopUpContext } from "../../contexts/popUpContext";
|
||||||
|
|
||||||
export default function TextAreaModal({value, setValue}:{setValue:(value:string)=>void,value:string|string[]}){
|
export default function TextAreaModal({
|
||||||
|
value,
|
||||||
|
setValue,
|
||||||
|
}: {
|
||||||
|
setValue: (value: string) => void;
|
||||||
|
value: string | string[];
|
||||||
|
}) {
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
const [myValue, setMyValue] = useState(value);
|
const [myValue, setMyValue] = useState(value);
|
||||||
const { closePopUp } = useContext(PopUpContext);
|
const { closePopUp } = useContext(PopUpContext);
|
||||||
const ref = useRef();
|
const ref = useRef();
|
||||||
function setModalOpen(x:boolean){
|
function setModalOpen(x: boolean) {
|
||||||
setOpen(x);
|
setOpen(x);
|
||||||
if(x === false){
|
if (x === false) {
|
||||||
setTimeout(() => {closePopUp()}, 300);
|
setTimeout(() => {
|
||||||
|
closePopUp();
|
||||||
|
}, 300);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
|
@ -78,7 +89,15 @@ export default function TextAreaModal({value, setValue}:{setValue:(value:string)
|
||||||
<div className="h-full w-full bg-gray-200 dark:bg-gray-900 p-4 gap-4 flex flex-row justify-center items-center">
|
<div className="h-full w-full bg-gray-200 dark:bg-gray-900 p-4 gap-4 flex flex-row justify-center items-center">
|
||||||
<div className="flex h-full w-full">
|
<div className="flex h-full w-full">
|
||||||
<div className="overflow-hidden px-4 py-5 sm:p-6 w-full rounded-lg bg-white dark:bg-gray-800 shadow">
|
<div className="overflow-hidden px-4 py-5 sm:p-6 w-full rounded-lg bg-white dark:bg-gray-800 shadow">
|
||||||
<textarea ref={ref} className="form-input h-full w-full rounded-lg border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-white" value={myValue} onChange={(e) => {setMyValue(e.target.value); setValue(e.target.value)}}/>
|
<textarea
|
||||||
|
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>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -100,5 +119,5 @@ export default function TextAreaModal({value, setValue}:{setValue:(value:string)
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Transition.Root>
|
</Transition.Root>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
import { ConnectionLineComponentProps } from 'reactflow';
|
import { ConnectionLineComponentProps } from "reactflow";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const ConnectionLineComponent = ({
|
const ConnectionLineComponent = ({
|
||||||
fromX,
|
fromX,
|
||||||
fromY,
|
fromY,
|
||||||
toX,
|
toX,
|
||||||
toY,
|
toY,
|
||||||
connectionLineStyle = {} // provide a default value for connectionLineStyle
|
connectionLineStyle = {}, // provide a default value for connectionLineStyle
|
||||||
}:ConnectionLineComponentProps) => {
|
}: ConnectionLineComponentProps) => {
|
||||||
return (
|
return (
|
||||||
<g>
|
<g>
|
||||||
<path
|
<path
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,11 @@
|
||||||
import {
|
import { ChevronRightIcon } from "@heroicons/react/24/solid";
|
||||||
ChevronRightIcon,
|
|
||||||
} from "@heroicons/react/24/solid";
|
|
||||||
import { Disclosure } from "@headlessui/react";
|
import { Disclosure } from "@headlessui/react";
|
||||||
import { DisclosureComponentType } from "../../../../types/components";
|
import { DisclosureComponentType } from "../../../../types/components";
|
||||||
|
|
||||||
export default function DisclosureComponent({
|
export default function DisclosureComponent({
|
||||||
button: { title, Icon, buttons = [] },
|
button: { title, Icon, buttons = [] },
|
||||||
children,
|
children,
|
||||||
}: DisclosureComponentType
|
}: DisclosureComponentType) {
|
||||||
) {
|
|
||||||
return (
|
return (
|
||||||
<Disclosure as="div" key={title}>
|
<Disclosure as="div" key={title}>
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,10 @@ import DisclosureComponent from "../DisclosureComponent";
|
||||||
import { nodeColors, nodeIcons, nodeNames } from "../../../../utils";
|
import { nodeColors, nodeIcons, nodeNames } from "../../../../utils";
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { typesContext } from "../../../../contexts/typesContext";
|
import { typesContext } from "../../../../contexts/typesContext";
|
||||||
import {
|
import { APIClassType, APIObjectType } from "../../../../types/api";
|
||||||
APIClassType,
|
|
||||||
APIObjectType,
|
|
||||||
} from "../../../../types/api";
|
|
||||||
|
|
||||||
export default function ExtraSidebar() {
|
export default function ExtraSidebar() {
|
||||||
const {data} = useContext(typesContext)
|
const { data } = useContext(typesContext);
|
||||||
|
|
||||||
function onDragStart(
|
function onDragStart(
|
||||||
event: React.DragEvent<any>,
|
event: React.DragEvent<any>,
|
||||||
|
|
@ -22,7 +19,9 @@ export default function ExtraSidebar() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-1 w-full">
|
<div className="mt-1 w-full">
|
||||||
{Object.keys(data).sort().map((d: keyof APIObjectType, i) => (
|
{Object.keys(data)
|
||||||
|
.sort()
|
||||||
|
.map((d: keyof APIObjectType, i) => (
|
||||||
<DisclosureComponent
|
<DisclosureComponent
|
||||||
key={i}
|
key={i}
|
||||||
button={{
|
button={{
|
||||||
|
|
@ -31,7 +30,9 @@ export default function ExtraSidebar() {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="p-2 flex flex-col gap-2">
|
<div className="p-2 flex flex-col gap-2">
|
||||||
{Object.keys(data[d]).sort().map((t: string, k) => (
|
{Object.keys(data[d])
|
||||||
|
.sort()
|
||||||
|
.map((t: string, k) => (
|
||||||
<div key={k}>
|
<div key={k}>
|
||||||
<div
|
<div
|
||||||
draggable
|
draggable
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,16 @@ import { FlowType } from "../../../../types/flow";
|
||||||
|
|
||||||
var _ = require("lodash");
|
var _ = require("lodash");
|
||||||
|
|
||||||
export default function TabComponent({ selected, flow, onClick }:{flow:FlowType,selected:boolean,onClick:()=>void}) {
|
export default function TabComponent({
|
||||||
const { removeFlow, updateFlow, flows } =
|
selected,
|
||||||
useContext(TabsContext);
|
flow,
|
||||||
|
onClick,
|
||||||
|
}: {
|
||||||
|
flow: FlowType;
|
||||||
|
selected: boolean;
|
||||||
|
onClick: () => void;
|
||||||
|
}) {
|
||||||
|
const { removeFlow, updateFlow, flows } = useContext(TabsContext);
|
||||||
const [isRename, setIsRename] = useState(false);
|
const [isRename, setIsRename] = useState(false);
|
||||||
const [value, setValue] = useState("");
|
const [value, setValue] = useState("");
|
||||||
return (
|
return (
|
||||||
|
|
@ -85,4 +92,3 @@ export default function TabComponent({ selected, flow, onClick }:{flow:FlowType,
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -102,9 +102,7 @@ export default function TabsManagerComponent() {
|
||||||
>
|
>
|
||||||
<AlertDropdown />
|
<AlertDropdown />
|
||||||
</div>
|
</div>
|
||||||
<div className="h-screen w-screen fixed top-0 left-0">
|
<div className="h-screen w-screen fixed top-0 left-0"></div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
109
src/frontend/src/pages/FlowPage/hooks/useUndoRedo.ts
Normal file
109
src/frontend/src/pages/FlowPage/hooks/useUndoRedo.ts
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { Edge, Node, useReactFlow } from 'reactflow';
|
||||||
|
|
||||||
|
type UseUndoRedoOptions = {
|
||||||
|
maxHistorySize: number;
|
||||||
|
enableShortcuts: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UseUndoRedo = (options?: UseUndoRedoOptions) => {
|
||||||
|
undo: () => void;
|
||||||
|
redo: () => void;
|
||||||
|
takeSnapshot: () => void;
|
||||||
|
canUndo: boolean;
|
||||||
|
canRedo: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type HistoryItem = {
|
||||||
|
nodes: Node[];
|
||||||
|
edges: Edge[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultOptions: UseUndoRedoOptions = {
|
||||||
|
maxHistorySize: 100,
|
||||||
|
enableShortcuts: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://redux.js.org/usage/implementing-undo-history
|
||||||
|
export const useUndoRedo: UseUndoRedo = ({
|
||||||
|
maxHistorySize = defaultOptions.maxHistorySize,
|
||||||
|
enableShortcuts = defaultOptions.enableShortcuts,
|
||||||
|
} = defaultOptions) => {
|
||||||
|
// the past and future arrays store the states that we can jump to
|
||||||
|
const [past, setPast] = useState<HistoryItem[]>([]);
|
||||||
|
const [future, setFuture] = useState<HistoryItem[]>([]);
|
||||||
|
|
||||||
|
const { setNodes, setEdges, getNodes, getEdges } = useReactFlow();
|
||||||
|
|
||||||
|
const takeSnapshot = useCallback(() => {
|
||||||
|
// push the current graph to the past state
|
||||||
|
setPast((past) => [
|
||||||
|
...past.slice(past.length - maxHistorySize + 1, past.length),
|
||||||
|
{ nodes: getNodes(), edges: getEdges() },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// whenever we take a new snapshot, the redo operations need to be cleared to avoid state mismatches
|
||||||
|
setFuture([]);
|
||||||
|
}, [getNodes, getEdges, maxHistorySize]);
|
||||||
|
|
||||||
|
const undo = useCallback(() => {
|
||||||
|
// get the last state that we want to go back to
|
||||||
|
const pastState = past[past.length - 1];
|
||||||
|
|
||||||
|
if (pastState) {
|
||||||
|
// first we remove the state from the history
|
||||||
|
setPast((past) => past.slice(0, past.length - 1));
|
||||||
|
// we store the current graph for the redo operation
|
||||||
|
setFuture((future) => [...future, { nodes: getNodes(), edges: getEdges() }]);
|
||||||
|
// now we can set the graph to the past state
|
||||||
|
setNodes(pastState.nodes);
|
||||||
|
setEdges(pastState.edges);
|
||||||
|
}
|
||||||
|
}, [setNodes, setEdges, getNodes, getEdges, past]);
|
||||||
|
|
||||||
|
const redo = useCallback(() => {
|
||||||
|
const futureState = future[future.length - 1];
|
||||||
|
|
||||||
|
if (futureState) {
|
||||||
|
setFuture((future) => future.slice(0, future.length - 1));
|
||||||
|
setPast((past) => [...past, { nodes: getNodes(), edges: getEdges() }]);
|
||||||
|
setNodes(futureState.nodes);
|
||||||
|
setEdges(futureState.edges);
|
||||||
|
}
|
||||||
|
}, [setNodes, setEdges, getNodes, getEdges, future]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// this effect is used to attach the global event handlers
|
||||||
|
if (!enableShortcuts) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyDownHandler = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'z' && (event.ctrlKey || event.metaKey) && event.shiftKey) {
|
||||||
|
redo();
|
||||||
|
}
|
||||||
|
else if (event.key === 'y' && (event.ctrlKey || event.metaKey)) {
|
||||||
|
event.preventDefault(); // prevent the default action
|
||||||
|
redo();
|
||||||
|
} else if (event.key === 'z' && (event.ctrlKey || event.metaKey)) {
|
||||||
|
undo();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('keydown', keyDownHandler);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('keydown', keyDownHandler);
|
||||||
|
};
|
||||||
|
}, [undo, redo, enableShortcuts]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
undo,
|
||||||
|
redo,
|
||||||
|
takeSnapshot,
|
||||||
|
canUndo: !!past.length,
|
||||||
|
canRedo: !!future.length,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useUndoRedo;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useCallback, useContext, useEffect, useRef } from "react";
|
import { useCallback, useContext, useEffect, useRef, useState } from "react";
|
||||||
import ReactFlow, {
|
import ReactFlow, {
|
||||||
Background,
|
Background,
|
||||||
Controls,
|
Controls,
|
||||||
|
|
@ -10,6 +10,12 @@ import ReactFlow, {
|
||||||
EdgeChange,
|
EdgeChange,
|
||||||
Connection,
|
Connection,
|
||||||
Edge,
|
Edge,
|
||||||
|
useKeyPress,
|
||||||
|
useOnSelectionChange,
|
||||||
|
NodeDragHandler,
|
||||||
|
OnEdgesDelete,
|
||||||
|
OnNodesDelete,
|
||||||
|
SelectionDragHandler,
|
||||||
} from "reactflow";
|
} from "reactflow";
|
||||||
import { locationContext } from "../../contexts/locationContext";
|
import { locationContext } from "../../contexts/locationContext";
|
||||||
import ExtraSidebar from "./components/extraSidebarComponent";
|
import ExtraSidebar from "./components/extraSidebarComponent";
|
||||||
|
|
@ -22,6 +28,7 @@ import ConnectionLineComponent from "./components/ConnectionLineComponent";
|
||||||
import { FlowType, NodeType } from "../../types/flow";
|
import { FlowType, NodeType } from "../../types/flow";
|
||||||
import { APIClassType } from "../../types/api";
|
import { APIClassType } from "../../types/api";
|
||||||
import { isValidConnection } from "../../utils";
|
import { isValidConnection } from "../../utils";
|
||||||
|
import useUndoRedo from "./hooks/useUndoRedo";
|
||||||
|
|
||||||
const nodeTypes = {
|
const nodeTypes = {
|
||||||
genericNode: GenericNode,
|
genericNode: GenericNode,
|
||||||
|
|
@ -29,13 +36,97 @@ const nodeTypes = {
|
||||||
|
|
||||||
var _ = require("lodash");
|
var _ = require("lodash");
|
||||||
|
|
||||||
export default function FlowPage({ flow }:{flow:FlowType}) {
|
export default function FlowPage({ flow }: { flow: FlowType }) {
|
||||||
let { updateFlow, incrementNodeId} =
|
let { updateFlow, incrementNodeId } =
|
||||||
useContext(TabsContext);
|
useContext(TabsContext);
|
||||||
const { types, reactFlowInstance, setReactFlowInstance, templates } =
|
const { types, reactFlowInstance, setReactFlowInstance, templates } =
|
||||||
useContext(typesContext);
|
useContext(typesContext);
|
||||||
const reactFlowWrapper = useRef(null);
|
const reactFlowWrapper = useRef(null);
|
||||||
|
|
||||||
|
const { undo, redo, canUndo, canRedo, takeSnapshot } = useUndoRedo();
|
||||||
|
|
||||||
|
const 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 [position, setPosition] = useState({ x: 0, y: 0 });
|
||||||
|
|
||||||
|
const handleMouseMove = (event) => {
|
||||||
|
setPosition({ x: event.clientX, y: event.clientY });
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const bounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||||
|
const insidePosition = reactFlowInstance.project({
|
||||||
|
x: position.x - bounds.left,
|
||||||
|
y: position.y - bounds.top
|
||||||
|
});
|
||||||
|
|
||||||
|
lastCopiedSelection.nodes.forEach((n) => {
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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 })))
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const { setExtraComponent, setExtraNavigation } = useContext(locationContext);
|
const { setExtraComponent, setExtraNavigation } = useContext(locationContext);
|
||||||
const { setErrorData } = useContext(alertContext);
|
const { setErrorData } = useContext(alertContext);
|
||||||
const [nodes, setNodes, onNodesChange] = useNodesState(
|
const [nodes, setNodes, onNodesChange] = useNodesState(
|
||||||
|
|
@ -47,6 +138,10 @@ export default function FlowPage({ flow }:{flow:FlowType}) {
|
||||||
const { setViewport } = useReactFlow();
|
const { setViewport } = useReactFlow();
|
||||||
const edgeUpdateSuccessful = useRef(true);
|
const edgeUpdateSuccessful = useRef(true);
|
||||||
|
|
||||||
|
function getId() {
|
||||||
|
return `dndnode_` + incrementNodeId();
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (reactFlowInstance && flow) {
|
if (reactFlowInstance && flow) {
|
||||||
flow.data = reactFlowInstance.toObject();
|
flow.data = reactFlowInstance.toObject();
|
||||||
|
|
@ -81,6 +176,7 @@ export default function FlowPage({ flow }:{flow:FlowType}) {
|
||||||
|
|
||||||
const onConnect = useCallback(
|
const onConnect = useCallback(
|
||||||
(params: Connection) => {
|
(params: Connection) => {
|
||||||
|
takeSnapshot();
|
||||||
setEdges((eds) =>
|
setEdges((eds) =>
|
||||||
addEdge({ ...params, className: "animate-pulse" }, eds)
|
addEdge({ ...params, className: "animate-pulse" }, eds)
|
||||||
);
|
);
|
||||||
|
|
@ -89,9 +185,26 @@ export default function FlowPage({ flow }:{flow:FlowType}) {
|
||||||
return newX;
|
return newX;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[setEdges, setNodes]
|
[setEdges, setNodes, 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 onEdgesDelete: OnEdgesDelete = useCallback(() => {
|
||||||
|
// 👇 make deleting edges undoable
|
||||||
|
takeSnapshot();
|
||||||
|
}, [takeSnapshot]);
|
||||||
|
|
||||||
const onDragOver = useCallback((event: React.DragEvent) => {
|
const onDragOver = useCallback((event: React.DragEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.dataTransfer.dropEffect = "move";
|
event.dataTransfer.dropEffect = "move";
|
||||||
|
|
@ -100,11 +213,7 @@ export default function FlowPage({ flow }:{flow:FlowType}) {
|
||||||
const onDrop = useCallback(
|
const onDrop = useCallback(
|
||||||
(event: React.DragEvent) => {
|
(event: React.DragEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
takeSnapshot();
|
||||||
// Helper function to generate a unique node ID
|
|
||||||
function getId() {
|
|
||||||
return `dndnode_` + incrementNodeId();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the current bounds of the ReactFlow wrapper element
|
// Get the current bounds of the ReactFlow wrapper element
|
||||||
const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect();
|
||||||
|
|
@ -152,16 +261,17 @@ export default function FlowPage({ flow }:{flow:FlowType}) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Specify dependencies for useCallback
|
// Specify dependencies for useCallback
|
||||||
[incrementNodeId, reactFlowInstance, setErrorData, setNodes]
|
[incrementNodeId, reactFlowInstance, setErrorData, setNodes, takeSnapshot]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onDelete = (mynodes) => {
|
const onDelete = useCallback((mynodes) => {
|
||||||
|
takeSnapshot();
|
||||||
setEdges(
|
setEdges(
|
||||||
edges.filter(
|
edges.filter(
|
||||||
(ns) => !mynodes.some((n) => ns.source === n.id || ns.target === n.id)
|
(ns) => !mynodes.some((n) => ns.source === n.id || ns.target === n.id)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
}, [takeSnapshot, edges, setEdges]);
|
||||||
|
|
||||||
const onEdgeUpdateStart = useCallback(() => {
|
const onEdgeUpdateStart = useCallback(() => {
|
||||||
edgeUpdateSuccessful.current = false;
|
edgeUpdateSuccessful.current = false;
|
||||||
|
|
@ -186,7 +296,7 @@ export default function FlowPage({ flow }:{flow:FlowType}) {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full" ref={reactFlowWrapper}>
|
<div className="w-full h-full" onMouseMove={handleMouseMove} ref={reactFlowWrapper}>
|
||||||
{Object.keys(templates).length > 0 && Object.keys(types).length > 0 ? (
|
{Object.keys(templates).length > 0 && Object.keys(types).length > 0 ? (
|
||||||
<>
|
<>
|
||||||
<ReactFlow
|
<ReactFlow
|
||||||
|
|
@ -197,6 +307,7 @@ export default function FlowPage({ flow }:{flow:FlowType}) {
|
||||||
edges={edges}
|
edges={edges}
|
||||||
onNodesChange={onNodesChange}
|
onNodesChange={onNodesChange}
|
||||||
onEdgesChange={onEdgesChangeMod}
|
onEdgesChange={onEdgesChangeMod}
|
||||||
|
onKeyDown={(e) => onKeyDown(e)}
|
||||||
onConnect={onConnect}
|
onConnect={onConnect}
|
||||||
onLoad={setReactFlowInstance}
|
onLoad={setReactFlowInstance}
|
||||||
onInit={setReactFlowInstance}
|
onInit={setReactFlowInstance}
|
||||||
|
|
@ -204,13 +315,16 @@ export default function FlowPage({ flow }:{flow:FlowType}) {
|
||||||
onEdgeUpdate={onEdgeUpdate}
|
onEdgeUpdate={onEdgeUpdate}
|
||||||
onEdgeUpdateStart={onEdgeUpdateStart}
|
onEdgeUpdateStart={onEdgeUpdateStart}
|
||||||
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
||||||
|
onNodeDragStart={onNodeDragStart}
|
||||||
|
onSelectionDragStart={onSelectionDragStart}
|
||||||
|
onEdgesDelete={onEdgesDelete}
|
||||||
connectionLineComponent={ConnectionLineComponent}
|
connectionLineComponent={ConnectionLineComponent}
|
||||||
onDragOver={onDragOver}
|
onDragOver={onDragOver}
|
||||||
onDrop={onDrop}
|
onDrop={onDrop}
|
||||||
onNodesDelete={onDelete}
|
onNodesDelete={onDelete}
|
||||||
selectNodesOnDrag={false}
|
selectNodesOnDrag={false}
|
||||||
>
|
>
|
||||||
<Background className="dark:bg-gray-900"/>
|
<Background className="dark:bg-gray-900" />
|
||||||
<Controls className="[&>button]:text-black [&>button]:dark:bg-gray-800 hover:[&>button]:dark:bg-gray-700 [&>button]:dark:text-gray-400 [&>button]:dark:fill-gray-400 [&>button]:dark:border-gray-600">
|
<Controls className="[&>button]:text-black [&>button]:dark:bg-gray-800 hover:[&>button]:dark:bg-gray-700 [&>button]:dark:text-gray-400 [&>button]:dark:fill-gray-400 [&>button]:dark:border-gray-600">
|
||||||
</Controls>
|
</Controls>
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
|
|
|
||||||
|
|
@ -14,4 +14,9 @@ export type TabsContextType = {
|
||||||
hardReset: () => 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,5 +1,4 @@
|
||||||
|
const template: { [char: string]: string } = {};
|
||||||
const template:{[char: string]: string}={}
|
|
||||||
|
|
||||||
export type TemplateContextType = {
|
export type TemplateContextType = {
|
||||||
templates: typeof template;
|
templates: typeof template;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
import { ReactFlowInstance } from "reactflow";
|
import { ReactFlowInstance } from "reactflow";
|
||||||
|
|
||||||
const types:{[char: string]: string}={};
|
const types: { [char: string]: string } = {};
|
||||||
const template:{[char: string]: string}={}
|
const template: { [char: string]: string } = {};
|
||||||
const data:{[char: string]: string}={}
|
const data: { [char: string]: string } = {};
|
||||||
|
|
||||||
|
|
||||||
export type typesContextType = {
|
export type typesContextType = {
|
||||||
reactFlowInstance: ReactFlowInstance|null;
|
reactFlowInstance: ReactFlowInstance | null;
|
||||||
setReactFlowInstance: any;
|
setReactFlowInstance: any;
|
||||||
deleteNode: (idx: string) => void;
|
deleteNode: (idx: string) => void;
|
||||||
types: typeof types;
|
types: typeof types;
|
||||||
|
|
|
||||||
|
|
@ -327,7 +327,8 @@ export function toNormalCase(str: string) {
|
||||||
})
|
})
|
||||||
.join(" ");
|
.join(" ");
|
||||||
|
|
||||||
return result.split("-")
|
return result
|
||||||
|
.split("-")
|
||||||
.map((word, index) => {
|
.map((word, index) => {
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
return word[0].toUpperCase() + word.slice(1).toLowerCase();
|
return word[0].toUpperCase() + word.slice(1).toLowerCase();
|
||||||
|
|
@ -443,7 +444,7 @@ export function updateTemplate(
|
||||||
reference: APITemplateType,
|
reference: APITemplateType,
|
||||||
objectToUpdate: APITemplateType
|
objectToUpdate: APITemplateType
|
||||||
): APITemplateType {
|
): APITemplateType {
|
||||||
let clonedObject:APITemplateType = _.cloneDeep(reference);
|
let clonedObject: APITemplateType = _.cloneDeep(reference);
|
||||||
|
|
||||||
// Loop through each key in the reference object
|
// Loop through each key in the reference object
|
||||||
for (const key in clonedObject) {
|
for (const key in clonedObject) {
|
||||||
|
|
@ -454,3 +455,34 @@ export function updateTemplate(
|
||||||
}
|
}
|
||||||
return clonedObject;
|
return clonedObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface languageMap {
|
||||||
|
[key: string]: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const programmingLanguages: languageMap = {
|
||||||
|
javascript: ".js",
|
||||||
|
python: ".py",
|
||||||
|
java: ".java",
|
||||||
|
c: ".c",
|
||||||
|
cpp: ".cpp",
|
||||||
|
"c++": ".cpp",
|
||||||
|
"c#": ".cs",
|
||||||
|
ruby: ".rb",
|
||||||
|
php: ".php",
|
||||||
|
swift: ".swift",
|
||||||
|
"objective-c": ".m",
|
||||||
|
kotlin: ".kt",
|
||||||
|
typescript: ".ts",
|
||||||
|
go: ".go",
|
||||||
|
perl: ".pl",
|
||||||
|
rust: ".rs",
|
||||||
|
scala: ".scala",
|
||||||
|
haskell: ".hs",
|
||||||
|
lua: ".lua",
|
||||||
|
shell: ".sh",
|
||||||
|
sql: ".sql",
|
||||||
|
html: ".html",
|
||||||
|
css: ".css",
|
||||||
|
// add more file extensions here, make sure the key is same as language prop in CodeBlock.tsx component
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -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,
|
ToolNode,
|
||||||
WrapperNode,
|
WrapperNode,
|
||||||
)
|
)
|
||||||
from langflow.interface.run import get_result_and_steps
|
from langflow.interface.run import get_result_and_thought
|
||||||
from langflow.utils.payload import get_root_node
|
from langflow.utils.payload import get_root_node
|
||||||
|
|
||||||
# Test cases for the graph module
|
# Test cases for the graph module
|
||||||
|
|
@ -335,7 +335,7 @@ def test_get_result_and_thought(basic_graph):
|
||||||
# now build again and check if FakeListLLM was used
|
# now build again and check if FakeListLLM was used
|
||||||
|
|
||||||
# Get the result and thought
|
# Get the result and thought
|
||||||
result, thought = get_result_and_steps(langchain_object, message)
|
result, thought = get_result_and_thought(langchain_object, message)
|
||||||
# The result should be a str
|
# The result should be a str
|
||||||
assert isinstance(result, str)
|
assert isinstance(result, str)
|
||||||
# The thought should be a Thought
|
# The thought should be a Thought
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue