remove dead code and unused dependencies (#1488)

* added esLint to frontend project

* Remove formModalPropsType from index.ts

* Remove chat input component

* Delete codeBlock and fileComponent components

* Remove unused code for BuildTrigger component

* Refactor Chat component and remove unused code

* Delete chatTrigger component

* Delete unused SVG and PNG files

* Remove LightTooltipComponent

* Remove LoadingSpinner component

* Delete RadialProgressComponent

* Delete ReactTooltipComponent

* Remove TooltipComponent

* Delete ElementStack component

* Remove ToggleComponent

* Remove unused dependencies from package.json

* Update package json

* add accordion

* change accordion sidebar to shadcn

* added million lint

* Remove MillionCompiler plugin from Vite config

* Refactor authContext autoLogin dependency

* Add console.log statements for debugging

* Refactored position of elements in IO branch

* fix assets imports

* fix re-render on pageComponent

* Add StrictMode to root render and update key in ExtraSidebarComponent

* Add showCanvas state and useEffect to update it to improve performance

* Refactor FlowPage component and remove ExtraSidebar from main area

* Refactor FlowToolbar to prevent Unnecessary re-render

* get node position with zustand in NodeToolbarComponent

* Fix ShareModal rendering issue

* Remove ExtraSidebar component and fix CodeAreaComponent bug

* Refactor: Use useMemo to avoid unnecessary render

* merge zustandIo

* Remove console.log statements

* Update package-lock.json and refactor extraSidebarComponent

* update package json

* Remove unused msgpack wheel file and add new nvidia_nvjitlink_cu12 wheel file

* Fix import formatting in loading.py

* Imported missing module and removed unused import

---------

Co-authored-by: cristhianzl <cristhian.lousa@gmail.com>
Co-authored-by: Lucas Oliveira <lucas.edu.oli@hotmail.com>
Co-authored-by: igorrCarvalho <igorsilvabhz6@gmail.com>
Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@logspace.ai>
This commit is contained in:
anovazzi1 2024-03-30 17:09:51 -03:00 committed by GitHub
commit e61896bc1f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
71 changed files with 3496 additions and 3399 deletions

4
poetry.lock generated
View file

@ -4846,7 +4846,6 @@ files = [
{file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"},
{file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"},
{file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"},
{file = "msgpack-1.0.8-py3-none-any.whl", hash = "sha256:24f727df1e20b9876fa6e95f840a2a2651e34c0ad147676356f4bf5fbb0206ca"},
{file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"},
]
@ -5316,6 +5315,7 @@ description = "Nvidia JIT LTO Library"
optional = true
python-versions = ">=3"
files = [
{file = "nvidia_nvjitlink_cu12-12.4.99-py3-none-manylinux2014_aarch64.whl", hash = "sha256:75d6498c96d9adb9435f2bbdbddb479805ddfb97b5c1b32395c694185c20ca57"},
{file = "nvidia_nvjitlink_cu12-12.4.99-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c6428836d20fe7e327191c175791d38570e10762edc588fb46749217cd444c74"},
{file = "nvidia_nvjitlink_cu12-12.4.99-py3-none-win_amd64.whl", hash = "sha256:991905ffa2144cb603d8ca7962d75c35334ae82bf92820b6ba78157277da1ad2"},
]
@ -10240,4 +10240,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.10,<3.12"
content-hash = "b66acb0ed04e62c9f311828307ac1503bc7a19912753c217d4ea6237f474543a"
content-hash = "014454e08274cc189a4b8b74c6577a8731bb0d9693f40f6f98b4c20bdf70b58d"

View file

@ -78,6 +78,12 @@ pytube = "^15.0.0"
llama-index = "^0.10.13"
langchain-openai = "^0.0.5"
unstructured = { extras = ["md"], version = "^0.12.4" }
opentelemetry-api = "^1.23.0"
opentelemetry-sdk = "^1.23.0"
opentelemetry-exporter-otlp = "^1.23.0"
opentelemetry-instrumentation-fastapi = "^0.44b0"
opentelemetry-instrumentation-httpx = "^0.44b0"
opentelemetry-instrumentation-asgi = "^0.44b0"
dspy-ai = "^2.4.0"
crewai = "^0.22.5"
html2text = "^2024.2.26"

View file

@ -13,8 +13,8 @@ class FileComponent(CustomComponent):
def build_config(self) -> Dict[str, Any]:
return {
"path": {
"display_name": "Path",
"paths": {
"display_name": "Paths",
"field_type": "file",
"file_types": TEXT_FILE_TYPES,
"info": f"Supported file types: {', '.join(TEXT_FILE_TYPES)}",

View file

@ -0,0 +1,30 @@
import ast
import json
from langflow import CustomComponent
from langflow.schema import Record
class JSONInputComponent(CustomComponent):
display_name = "JSON Input"
description = "Load a JSON object as input."
def build_config(self):
return {
"json_str": {
"display_name": "JSON String",
"multiline": True,
"info": "The JSON string to load.",
}
}
def build(self, json_str: str) -> Record:
try:
data = json.loads(json_str)
except json.JSONDecodeError:
try:
data = ast.literal_eval(json_str)
except (SyntaxError, ValueError):
raise ValueError("Invalid JSON string.")
record = Record(data=data)
return record

View file

@ -1,7 +1,6 @@
import ast
import json
from typing import AsyncIterator, Callable, Dict, Iterator, List, Optional, Union
import yaml
from langchain_core.messages import AIMessage
from loguru import logger

View file

@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Any, Awaitable, Callable, List, Optional, Tuple, Type, Union, cast
from typing import TYPE_CHECKING, Any, Callable, Coroutine, List, Optional, Tuple, Union
from pydantic.v1 import BaseModel, Field, create_model
from sqlmodel import select
@ -63,30 +63,28 @@ def find_flow(flow_name: str, user_id: str) -> Optional[str]:
async def run_flow(
inputs: Optional[Union[dict, List[dict]]] = None,
inputs: Union[dict, List[dict]] = None,
tweaks: Optional[dict] = None,
flow_id: Optional[str] = None,
flow_name: Optional[str] = None,
user_id: Optional[str] = None,
) -> Any:
if not user_id:
raise ValueError("Session is invalid")
graph = await load_flow(user_id, flow_id, flow_name, tweaks)
if inputs is None:
inputs = []
inputs_list: list[dict[str, str]] = []
inputs_list = []
inputs_components = []
types = []
for input_dict in inputs:
inputs_list.append({INPUT_FIELD_NAME: cast(str, input_dict.get("input_value", ""))})
inputs_list.append({INPUT_FIELD_NAME: input_dict.get("input_value")})
inputs_components.append(input_dict.get("components", []))
types.append(input_dict.get("type", []))
return await graph.arun(inputs_list, inputs_components=inputs_components, types=types)
def generate_function_for_flow(inputs: List["Vertex"], flow_id: str) -> Callable[..., Awaitable[Any]]:
def generate_function_for_flow(inputs: List["Vertex"], flow_id: str) -> Coroutine:
"""
Generate a dynamic flow function based on the given inputs and flow ID.
@ -140,14 +138,12 @@ async def flow_function({func_args}):
"""
compiled_func = compile(func_body, "<string>", "exec")
local_scope: dict = {}
local_scope = {}
exec(compiled_func, globals(), local_scope)
return local_scope["flow_function"]
def build_function_and_schema(
flow_record: Record, graph: "Graph"
) -> Tuple[Callable[..., Awaitable[Any]], Type[BaseModel]]:
def build_function_and_schema(flow_record: Record, graph: "Graph") -> Tuple[Callable, BaseModel]:
"""
Builds a dynamic function and schema for a given flow.
@ -182,7 +178,7 @@ def get_flow_inputs(graph: "Graph") -> List["Vertex"]:
return inputs
def build_schema_from_inputs(name: str, inputs: List["Vertex"]) -> Type[BaseModel]:
def build_schema_from_inputs(name: str, inputs: List[tuple[str, str, str]]) -> BaseModel:
"""
Builds a schema from the given inputs.
@ -200,4 +196,4 @@ def build_schema_from_inputs(name: str, inputs: List["Vertex"]) -> Type[BaseMode
field_name = input_.display_name.lower().replace(" ", "_")
description = input_.description
fields[field_name] = (str, Field(default="", description=description))
return create_model(name, **fields) # type: ignore
return create_model(name, **fields)

View file

@ -193,7 +193,7 @@
"list": false,
"show": true,
"multiline": true,
"value": "from langchain_core.prompts import PromptTemplate\n\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"A component for creating prompts using templates\"\n icon = \"terminal-square\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Text:\n from langflow.base.prompts.utils import dict_values_to_string\n\n prompt_template = PromptTemplate.from_template(Text(template))\n kwargs = dict_values_to_string(kwargs)\n kwargs = {k: \"\\n\".join(v) if isinstance(v, list) else v for k, v in kwargs.items()}\n try:\n formated_prompt = prompt_template.format(**kwargs)\n except Exception as exc:\n raise ValueError(f\"Error formatting prompt: {exc}\") from exc\n self.status = f'Prompt:\\n\"{formated_prompt}\"'\n return formated_prompt\n",
"value": "from langchain_core.prompts import PromptTemplate\n\nfrom langflow import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"A component for creating prompts using templates\"\n icon = \"terminal-square\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Text:\n from langflow.base.prompts.utils import dict_values_to_string\n\n prompt_template = PromptTemplate.from_template(Text(template))\n kwargs = dict_values_to_string(kwargs)\n kwargs = {k: \"\\n\".join(v) if isinstance(v, list) else v for k, v in kwargs.items()}\n try:\n formated_prompt = prompt_template.format(**kwargs)\n except Exception as exc:\n raise ValueError(f\"Error formatting prompt: {exc}\") from exc\n self.status = f'Prompt:\\n\"{formated_prompt}\"'\n return formated_prompt\n",
"fileTypes": [],
"file_path": "",
"password": false,
@ -797,7 +797,7 @@
"list": false,
"show": true,
"multiline": true,
"value": "from typing import Optional\n\nfrom langchain_openai import ChatOpenAI\n\nfrom langflow.base.models.model import LCModelComponent\nfrom langflow.field_typing import NestedDict, Text\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI's models.\"\n icon = \"OpenAI\"\n\n def build_config(self):\n return {\n \"input_value\": {\"display_name\": \"Input\"},\n \"max_tokens\": {\n \"display_name\": \"Max Tokens\",\n \"advanced\": False,\n \"required\": False,\n },\n \"model_kwargs\": {\n \"display_name\": \"Model Kwargs\",\n \"advanced\": True,\n \"required\": False,\n },\n \"model_name\": {\n \"display_name\": \"Model Name\",\n \"advanced\": False,\n \"required\": False,\n \"options\": [\n \"gpt-4-turbo-preview\",\n \"gpt-4-0125-preview\",\n \"gpt-4-1106-preview\",\n \"gpt-4-vision-preview\",\n \"gpt-3.5-turbo-0125\",\n \"gpt-3.5-turbo-1106\",\n ],\n },\n \"openai_api_base\": {\n \"display_name\": \"OpenAI API Base\",\n \"advanced\": False,\n \"required\": False,\n \"info\": (\n \"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\\n\\n\"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\"\n ),\n },\n \"openai_api_key\": {\n \"display_name\": \"OpenAI API Key\",\n \"advanced\": False,\n \"required\": False,\n \"password\": True,\n },\n \"temperature\": {\n \"display_name\": \"Temperature\",\n \"advanced\": False,\n \"required\": False,\n \"value\": 0.7,\n },\n \"stream\": {\n \"display_name\": \"Stream\",\n \"info\": \"Stream the response from the model.\",\n },\n }\n\n def build(\n self,\n input_value: Text,\n max_tokens: Optional[int] = 256,\n model_kwargs: NestedDict = {},\n model_name: str = \"gpt-4-1106-preview\",\n openai_api_base: Optional[str] = None,\n openai_api_key: Optional[str] = None,\n temperature: float = 0.7,\n stream: bool = False,\n ) -> Text:\n if not openai_api_base:\n openai_api_base = \"https://api.openai.com/v1\"\n output = ChatOpenAI(\n max_tokens=max_tokens,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=openai_api_key,\n temperature=temperature,\n )\n\n return self.get_result(output=output, stream=stream, input_value=input_value)\n",
"value": "from typing import Optional\n\nfrom langchain_openai import ChatOpenAI\n\nfrom langflow.components.models.base.model import LCModelComponent\nfrom langflow.field_typing import NestedDict, Text\n\n\nclass OpenAIModelComponent(LCModelComponent):\n display_name = \"OpenAI\"\n description = \"Generates text using OpenAI's models.\"\n icon = \"OpenAI\"\n\n def build_config(self):\n return {\n \"input_value\": {\"display_name\": \"Input\"},\n \"max_tokens\": {\n \"display_name\": \"Max Tokens\",\n \"advanced\": False,\n \"required\": False,\n },\n \"model_kwargs\": {\n \"display_name\": \"Model Kwargs\",\n \"advanced\": True,\n \"required\": False,\n },\n \"model_name\": {\n \"display_name\": \"Model Name\",\n \"advanced\": False,\n \"required\": False,\n \"options\": [\n \"gpt-4-turbo-preview\",\n \"gpt-4-0125-preview\",\n \"gpt-4-1106-preview\",\n \"gpt-4-vision-preview\",\n \"gpt-3.5-turbo-0125\",\n \"gpt-3.5-turbo-1106\",\n ],\n },\n \"openai_api_base\": {\n \"display_name\": \"OpenAI API Base\",\n \"advanced\": False,\n \"required\": False,\n \"info\": (\n \"The base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\\n\\n\"\n \"You can change this to use other APIs like JinaChat, LocalAI and Prem.\"\n ),\n },\n \"openai_api_key\": {\n \"display_name\": \"OpenAI API Key\",\n \"advanced\": False,\n \"required\": False,\n \"password\": True,\n },\n \"temperature\": {\n \"display_name\": \"Temperature\",\n \"advanced\": False,\n \"required\": False,\n \"value\": 0.7,\n },\n \"stream\": {\n \"display_name\": \"Stream\",\n \"info\": \"Stream the response from the model.\",\n },\n }\n\n def build(\n self,\n input_value: Text,\n max_tokens: Optional[int] = 256,\n model_kwargs: NestedDict = {},\n model_name: str = \"gpt-4-1106-preview\",\n openai_api_base: Optional[str] = None,\n openai_api_key: Optional[str] = None,\n temperature: float = 0.7,\n stream: bool = False,\n ) -> Text:\n if not openai_api_base:\n openai_api_base = \"https://api.openai.com/v1\"\n output = ChatOpenAI(\n max_tokens=max_tokens,\n model_kwargs=model_kwargs,\n model=model_name,\n base_url=openai_api_base,\n api_key=openai_api_key,\n temperature=temperature,\n )\n\n return self.get_result(output=output, stream=stream, input_value=input_value)\n",
"fileTypes": [],
"file_path": "",
"password": false,

View file

@ -2,6 +2,7 @@ import inspect
import json
from typing import TYPE_CHECKING, Any, Callable, Dict, Sequence, Type
import orjson
from langchain.agents import agent as agent_module
from langchain.agents.agent import AgentExecutor

View file

@ -0,0 +1,70 @@
from typing import List, Union
from langchain.agents import AgentExecutor, BaseMultiActionAgent, BaseSingleActionAgent
from langflow import CustomComponent
from langflow.field_typing import BaseMemory, Text, Tool
class LCAgentComponent(CustomComponent):
def build_config(self):
return {
"lc": {
"display_name": "LangChain",
"info": "The LangChain to interact with.",
},
"handle_parsing_errors": {
"display_name": "Handle Parsing Errors",
"info": "If True, the agent will handle parsing errors. If False, the agent will raise an error.",
"advanced": True,
},
"output_key": {
"display_name": "Output Key",
"info": "The key to use to get the output from the agent.",
"advanced": True,
},
"memory": {
"display_name": "Memory",
"info": "Memory to use for the agent.",
},
"tools": {
"display_name": "Tools",
"info": "Tools the agent can use.",
},
"input_value": {
"display_name": "Input",
"info": "Input text to pass to the agent.",
},
}
async def run_agent(
self,
agent: Union[BaseSingleActionAgent, BaseMultiActionAgent, AgentExecutor],
inputs: str,
input_variables: list[str],
tools: List[Tool],
memory: BaseMemory = None,
handle_parsing_errors: bool = True,
output_key: str = "output",
) -> Text:
if isinstance(agent, AgentExecutor):
runnable = agent
else:
runnable = AgentExecutor.from_agent_and_tools(
agent=agent, tools=tools, verbose=True, memory=memory, handle_parsing_errors=handle_parsing_errors
)
input_dict = {"input": inputs}
for var in input_variables:
if var not in ["agent_scratchpad", "input"]:
input_dict[var] = ""
result = await runnable.ainvoke(input_dict)
self.status = result
if output_key in result:
return result.get(output_key)
elif "output" not in result:
if output_key != "output":
raise ValueError(f"Output key not found in result. Tried '{output_key}' and 'output'.")
else:
raise ValueError("Output key not found in result. Tried 'output'.")
return result.get("output")

View file

@ -0,0 +1,3 @@
from .model import LCModelComponent
__all__ = ["LCModelComponent"]

View file

@ -0,0 +1,48 @@
from typing import Optional
from langchain_core.language_models.chat_models import BaseChatModel
from langchain_core.language_models.llms import LLM
from langchain_core.messages import HumanMessage, SystemMessage
from langflow import CustomComponent
class LCModelComponent(CustomComponent):
display_name: str = "Model Name"
description: str = "Model Description"
def get_result(self, runnable: LLM, stream: bool, input_value: str):
"""
Retrieves the result from the output of a Runnable object.
Args:
output (Runnable): The output object to retrieve the result from.
stream (bool): Indicates whether to use streaming or invocation mode.
input_value (str): The input value to pass to the output object.
Returns:
The result obtained from the output object.
"""
if stream:
result = runnable.stream(input_value)
else:
message = runnable.invoke(input_value)
result = message.content if hasattr(message, "content") else message
self.status = result
return result
def get_chat_result(
self, runnable: BaseChatModel, stream: bool, input_value: str, system_message: Optional[str] = None
):
messages = []
if input_value:
messages.append(HumanMessage(input_value))
if system_message:
messages.append(SystemMessage(system_message))
if stream:
result = runnable.stream(messages)
else:
message = runnable.invoke(messages)
result = message.content
self.status = result
return result

View file

@ -0,0 +1,37 @@
from langchain_community.tools.searchapi import SearchAPIRun
from langchain_community.utilities.searchapi import SearchApiAPIWrapper
from langflow import CustomComponent
from langflow.field_typing import Tool
class SearchApiToolComponent(CustomComponent):
display_name: str = "SearchApi Tool"
description: str = "Real-time search engine results API."
documentation: str = "https://www.searchapi.io/docs/google"
field_config = {
"engine": {
"display_name": "Engine",
"field_type": "str",
"info": "The search engine to use.",
},
"api_key": {
"display_name": "API Key",
"field_type": "str",
"required": True,
"password": True,
"info": "The API key to use SearchApi.",
},
}
def build(
self,
engine: str,
api_key: str,
) -> Tool:
search_api_wrapper = SearchApiAPIWrapper(engine=engine, searchapi_api_key=api_key)
tool = SearchAPIRun(api_wrapper=search_api_wrapper)
self.status = tool
return tool

View file

@ -0,0 +1,103 @@
from contextlib import contextmanager
from typing import TYPE_CHECKING, Generator
from langflow.services import ServiceType, service_manager
if TYPE_CHECKING:
from sqlmodel import Session
from langflow.services.cache.service import CacheService
from langflow.services.chat.service import ChatService
from langflow.services.credentials.service import CredentialService
from langflow.services.database.service import DatabaseService
from langflow.services.monitor.service import MonitorService
from langflow.services.plugins.service import PluginService
from langflow.services.session.service import SessionService
from langflow.services.settings.service import SettingsService
from langflow.services.socket.service import SocketIOService
from langflow.services.storage.service import StorageService
from langflow.services.store.service import StoreService
from langflow.services.task.service import TaskService
def get_socket_service() -> "SocketIOService":
return service_manager.get(ServiceType.SOCKETIO_SERVICE) # type: ignore
def get_storage_service() -> "StorageService":
return service_manager.get(ServiceType.STORAGE_SERVICE) # type: ignore
def get_credential_service() -> "CredentialService":
return service_manager.get(ServiceType.CREDENTIAL_SERVICE) # type: ignore
def get_plugins_service() -> "PluginService":
return service_manager.get(ServiceType.PLUGIN_SERVICE) # type: ignore
def get_settings_service() -> "SettingsService":
try:
return service_manager.get(ServiceType.SETTINGS_SERVICE) # type: ignore
except ValueError:
# initialize settings service
from langflow.services.manager import initialize_settings_service
initialize_settings_service()
return service_manager.get(ServiceType.SETTINGS_SERVICE) # type: ignore
def get_db_service() -> "DatabaseService":
return service_manager.get(ServiceType.DATABASE_SERVICE) # type: ignore
def get_session() -> Generator["Session", None, None]:
db_service = get_db_service()
yield from db_service.get_session()
@contextmanager
def session_scope():
"""
Context manager for managing a session scope.
Yields:
session: The session object.
Raises:
Exception: If an error occurs during the session scope.
"""
session = next(get_session())
try:
yield session
session.commit()
except:
session.rollback()
raise
finally:
session.close()
def get_cache_service() -> "CacheService":
return service_manager.get(ServiceType.CACHE_SERVICE) # type: ignore
def get_session_service() -> "SessionService":
return service_manager.get(ServiceType.SESSION_SERVICE) # type: ignore
def get_monitor_service() -> "MonitorService":
return service_manager.get(ServiceType.MONITOR_SERVICE) # type: ignore
def get_task_service() -> "TaskService":
return service_manager.get(ServiceType.TASK_SERVICE) # type: ignore
def get_chat_service() -> "ChatService":
return service_manager.get(ServiceType.CHAT_SERVICE) # type: ignore
def get_store_service() -> "StoreService":
return service_manager.get(ServiceType.STORE_SERVICE) # type: ignore

View file

@ -0,0 +1,83 @@
import importlib
import inspect
from typing import TYPE_CHECKING, Type, get_type_hints
from cachetools import LRUCache, cached
from loguru import logger
from langflow.services.schema import ServiceType
if TYPE_CHECKING:
from langflow.services.base import Service
class ServiceFactory:
def __init__(
self,
service_class,
):
self.service_class = service_class
self.dependencies = infer_service_types(self, import_all_services_into_a_dict())
def create(self, *args, **kwargs) -> "Service":
raise self.service_class(*args, **kwargs)
def hash_factory(factory: ServiceFactory) -> str:
return factory.service_class.__name__
def hash_dict(d: dict) -> str:
return str(d)
def hash_infer_service_types_args(factory_class: Type[ServiceFactory], available_services=None) -> str:
factory_hash = hash_factory(factory_class)
services_hash = hash_dict(available_services)
return f"{factory_hash}_{services_hash}"
@cached(cache=LRUCache(maxsize=10), key=hash_infer_service_types_args)
def infer_service_types(factory_class: Type[ServiceFactory], available_services=None) -> "ServiceType":
create_method = factory_class.create
type_hints = get_type_hints(create_method, globalns=available_services)
service_types = []
for param_name, param_type in type_hints.items():
# Skip the return type if it's included in type hints
if param_name == "return":
continue
# Convert the type to the expected enum format directly without appending "_SERVICE"
type_name = param_type.__name__.upper().replace("SERVICE", "_SERVICE")
try:
# Attempt to find a matching enum value
service_type = ServiceType[type_name]
service_types.append(service_type)
except KeyError:
raise ValueError(f"No matching ServiceType for parameter type: {param_type.__name__}")
return service_types
@cached(cache=LRUCache(maxsize=1))
def import_all_services_into_a_dict():
# Services are all in langflow.services.{service_name}.service
# and are subclass of Service
# We want to import all of them and put them in a dict
# to use as globals
from langflow.services.base import Service
services = {}
for service_type in ServiceType:
try:
service_name = ServiceType(service_type).value.replace("_service", "")
module_name = f"langflow.services.{service_name}.service"
module = importlib.import_module(module_name)
for name, obj in inspect.getmembers(module, inspect.isclass):
if issubclass(obj, Service) and obj is not Service:
services[name] = obj
break
except Exception as exc:
logger.exception(exc)
raise RuntimeError("Could not initialize services. Please check your settings.") from exc
return services

View file

@ -0,0 +1,32 @@
{
"extends": [
"eslint:recommended",
"plugin:node/recommended"
],
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
"no-console": "warn",
"no-self-assign": "warn",
"no-self-compare":"warn",
"complexity": ["error", { "max": 15 }],
"indent": ["error", 2, { "SwitchCase": 1 }],
"no-dupe-keys": "error",
"no-invalid-regexp": "error",
"no-undef": "error",
"no-return-assign": "error",
"no-redeclare": "error",
"no-empty": "error",
"no-await-in-loop": "error",
"node/exports-style": ["error", "module.exports"],
"node/file-extension-in-import": ["error", "always"],
"node/prefer-global/buffer": ["error", "always"],
"node/prefer-global/console": ["error", "always"],
"node/prefer-global/process": ["error", "always"],
"node/prefer-global/url-search-params": ["error", "always"],
"node/prefer-global/url": ["error", "always"],
"node/prefer-promises/dns": "error",
"node/prefer-promises/fs": "error"
}
}

File diff suppressed because it is too large Load diff

View file

@ -3,12 +3,8 @@
"version": "0.1.2",
"private": true,
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@headlessui/react": "^1.7.17",
"@heroicons/react": "^2.0.18",
"@mui/material": "^5.14.7",
"@preact/signals-react": "^2.0.0",
"@million/lint": "^0.0.73",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.4",
@ -29,9 +25,7 @@
"@tailwindcss/forms": "^0.5.6",
"@tailwindcss/line-clamp": "^0.4.4",
"@types/axios": "^0.14.0",
"accordion": "^3.0.2",
"ace-builds": "^1.24.1",
"add": "^2.0.6",
"ansi-to-html": "^0.7.2",
"axios": "^1.5.0",
"base64-js": "^1.5.1",
@ -43,6 +37,7 @@
"framer-motion": "^11.0.6",
"lodash": "^4.17.21",
"lucide-react": "^0.331.0",
"million": "^3.0.6",
"moment": "^2.29.4",
"playwright": "^1.42.0",
"react": "^18.2.0",
@ -55,8 +50,6 @@
"react-markdown": "^8.0.7",
"react-router-dom": "^6.15.0",
"react-syntax-highlighter": "^15.5.0",
"react-tabs": "^6.0.2",
"react-tooltip": "^5.21.1",
"react18-json-view": "^0.2.3",
"reactflow": "^11.9.2",
"rehype-mathjax": "^4.0.3",
@ -64,8 +57,6 @@
"remark-math": "^5.1.1",
"shadcn-ui": "^0.2.3",
"short-unique-id": "^4.4.4",
"switch": "^0.0.0",
"table": "^6.8.1",
"tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.7",
"uuid": "^9.0.0",
@ -117,6 +108,8 @@
"@vitejs/plugin-react-swc": "^3.3.2",
"autoprefixer": "^10.4.15",
"daisyui": "^4.0.4",
"eslint": "^8.57.0",
"eslint-plugin-node": "^11.1.0",
"postcss": "^8.4.29",
"prettier": "^2.8.8",
"prettier-plugin-organize-imports": "^3.2.3",

View file

@ -1,5 +1,5 @@
import { cloneDeep } from "lodash";
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { NodeToolbar, useUpdateNodeInternals } from "reactflow";
import ShadTooltip from "../../components/ShadTooltipComponent";
import IconComponent from "../../components/genericIconComponent";
@ -293,7 +293,6 @@ export default function GenericNode({
);
}
};
const getSpecificClassFromBuildStatus = (
buildStatus: BuildStatus | undefined,
validationStatus: validationStatusType | null
@ -342,32 +341,37 @@ export default function GenericNode({
const getNodeSizeClass = (showNode) =>
showNode ? "w-96 rounded-lg" : "w-26 h-26 rounded-full";
return (
<>
const memoizedNodeToolbarComponent = useMemo(() => {
return (
<NodeToolbar>
<NodeToolbarComponent
position={{ x: xPos, y: yPos }}
data={data}
deleteNode={(id) => {
takeSnapshot();
deleteNode(id);
}}
setShowNode={(show: boolean) => {
setNode(data.id, (old) => ({
...old,
data: { ...old.data, showNode: show },
}));
}}
setShowState={setShowNode}
numberOfHandles={handles}
showNode={showNode}
openAdvancedModal={false}
onCloseAdvancedModal={() => {}}
updateNodeCode={updateNodeCode}
isOutdated={isOutdated}
selected={selected}
></NodeToolbarComponent>
data={data}
deleteNode={(id) => {
takeSnapshot();
deleteNode(id);
}}
setShowNode={(show) => {
setNode(data.id, (old) => ({
...old,
data: { ...old.data, showNode: show },
}));
}}
setShowState={setShowNode}
numberOfHandles={handles}
showNode={showNode}
openAdvancedModal={false}
onCloseAdvancedModal={() => {}}
updateNodeCode={updateNodeCode}
isOutdated={isOutdated}
selected={selected}
/>
</NodeToolbar>
)
}, [data, deleteNode, takeSnapshot, setNode, setShowNode, handles, showNode, updateNodeCode, isOutdated, selected]);
return (
<>
{memoizedNodeToolbarComponent}
<div
className={getNodeBorderClassName(
selected,

View file

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: none; display: block; shape-rendering: auto;" width="271px" height="271px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<defs>
<filter id="ldio-978hsxudfzl-filter" x="-100%" y="-100%" width="300%" height="300%" color-interpolation-filters="sRGB">
<feGaussianBlur in="SourceGraphic" stdDeviation="3.6"></feGaussianBlur>
<feComponentTransfer result="cutoff">
<feFuncA type="table" tableValues="0 0 0 0 0 0 1 1 1 1 1"></feFuncA>
</feComponentTransfer>
</filter>
</defs>
<g filter="url(#ldio-978hsxudfzl-filter)"><g transform="translate(50 50)">
<g>
<circle cx="8" cy="0" r="5" fill="#2edbb5">
<animate attributeName="r" keyTimes="0;0.5;1" values="5.3999999999999995;12.6;5.3999999999999995" dur="5s" repeatCount="indefinite" begin="-0.2s"></animate>
</circle>
<animateTransform attributeName="transform" type="rotate" keyTimes="0;1" values="0;360" dur="5s" repeatCount="indefinite" begin="0s"></animateTransform>
</g>
</g><g transform="translate(50 50)">
<g>
<circle cx="8" cy="0" r="5" fill="#1d99ff">
<animate attributeName="r" keyTimes="0;0.5;1" values="5.3999999999999995;12.6;5.3999999999999995" dur="2.5s" repeatCount="indefinite" begin="-0.15000000000000002s"></animate>
</circle>
<animateTransform attributeName="transform" type="rotate" keyTimes="0;1" values="0;360" dur="2.5s" repeatCount="indefinite" begin="-0.05s"></animateTransform>
</g>
</g><g transform="translate(50 50)">
<g>
<circle cx="8" cy="0" r="5" fill="#4f41ff">
<animate attributeName="r" keyTimes="0;0.5;1" values="5.3999999999999995;12.6;5.3999999999999995" dur="1.6666666666666665s" repeatCount="indefinite" begin="-0.1s"></animate>
</circle>
<animateTransform attributeName="transform" type="rotate" keyTimes="0;1" values="0;360" dur="1.6666666666666665s" repeatCount="indefinite" begin="-0.1s"></animateTransform>
</g>
</g><g transform="translate(50 50)">
<g>
<circle cx="8" cy="0" r="5" fill="#8400ff">
<animate attributeName="r" keyTimes="0;0.5;1" values="5.3999999999999995;12.6;5.3999999999999995" dur="1.25s" repeatCount="indefinite" begin="-0.05s"></animate>
</circle>
<animateTransform attributeName="transform" type="rotate" keyTimes="0;1" values="0;360" dur="1.25s" repeatCount="indefinite" begin="-0.15000000000000002s"></animateTransform>
</g>
</g></g>
<!-- [ldio] generated by https://loading.io/ --></svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View file

@ -12,6 +12,7 @@ export default function AccordionComponent({
children,
open = [],
keyValue,
sideBar,
}: AccordionComponentType): JSX.Element {
const [value, setValue] = useState(
open.length === 0 ? "" : getOpenAccordion()
@ -45,7 +46,9 @@ export default function AccordionComponent({
onClick={() => {
handleClick();
}}
className="ml-3"
className={
sideBar ? "w-full bg-muted px-[0.75rem] py-[0.5rem]" : "ml-3"
}
>
{trigger}
</AccordionTrigger>

View file

@ -1,69 +0,0 @@
import { cloneDeep } from "lodash";
import useFlowStore from "../../stores/flowStore";
import { IOInputProps } from "../../types/components";
import IOFileInput from "../IOInputs/FileInput";
import { Textarea } from "../ui/textarea";
export default function IOInputField({
inputType,
inputId,
left,
}: IOInputProps): JSX.Element | undefined {
const nodes = useFlowStore((state) => state.nodes);
const setNode = useFlowStore((state) => state.setNode);
const node = nodes.find((node) => node.id === inputId);
function handleInputType() {
if (!node) return <>"No node found!"</>;
switch (inputType) {
case "TextInput":
return (
<Textarea
className={`w-full ${left ? "" : " h-full"}`}
placeholder={"Enter text..."}
value={node.data.node!.template["input_value"].value}
onChange={(e) => {
e.target.value;
if (node) {
let newNode = cloneDeep(node);
newNode.data.node!.template["input_value"].value =
e.target.value;
setNode(node.id, newNode);
}
}}
/>
);
case "FileLoader":
return (
<IOFileInput
field={node.data.node!.template["file_path"]["value"]}
updateValue={(e) => {
if (node) {
let newNode = cloneDeep(node);
newNode.data.node!.template["file_path"].value = e;
setNode(node.id, newNode);
}
}}
/>
);
default:
return (
<Textarea
className="w-full custom-scroll"
placeholder={"Enter text..."}
value={node.data.node!.template["input_value"]}
onChange={(e) => {
e.target.value;
if (node) {
let newNode = cloneDeep(node);
newNode.data.node!.template["input_value"].value =
e.target.value;
setNode(node.id, newNode);
}
}}
/>
);
}
}
return handleInputType();
}

View file

@ -1,47 +0,0 @@
import useFlowStore from "../../stores/flowStore";
import { IOOutputProps } from "../../types/components";
import { Textarea } from "../ui/textarea";
export default function IOOutputView({
outputType,
outputId,
left,
}: IOOutputProps): JSX.Element | undefined {
const nodes = useFlowStore((state) => state.nodes);
const setNode = useFlowStore((state) => state.setNode);
const flowPool = useFlowStore((state) => state.flowPool);
const node = nodes.find((node) => node.id === outputId);
function handleOutputType() {
if (!node) return <>"No node found!"</>;
switch (outputType) {
case "TextOutput":
return (
<Textarea
className={`w-full custom-scroll ${left ? "" : " h-full"}`}
placeholder={"Empty"}
// update to real value on flowPool
value={
(flowPool[node.id] ?? [])[(flowPool[node.id]?.length ?? 1) - 1]
?.params ?? ""
}
readOnly
/>
);
default:
return (
<Textarea
className={`w-full custom-scroll ${left ? "" : " h-full"}`}
placeholder={"Empty"}
// update to real value on flowPool
value={
(flowPool[node.id] ?? [])[(flowPool[node.id]?.length ?? 1) - 1]
?.params ?? ""
}
readOnly
/>
);
}
}
return handleOutputType();
}

View file

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

View file

@ -1,3 +0,0 @@
export default function LoadingSpinner({}) {
return <></>;
}

View file

@ -1,33 +0,0 @@
import { useNavigate } from "react-router-dom";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { cn } from "../../utils/utils";
import IconComponent from "../genericIconComponent";
import { Card, CardContent } from "../ui/card";
export default function NewFlowCardComponent({}: {}) {
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const navigate = useNavigate();
return (
<Card
className={cn(
"group relative flex h-48 w-2/6 flex-col justify-between overflow-hidden transition-all hover:shadow-md"
)}
>
<CardContent className="flex h-full w-full items-center justify-center align-middle">
<button
onClick={() => {
addFlow(true).then((id) => {
navigate("/flow/" + id);
});
}}
>
<IconComponent
className={cn("h-12 w-12 text-muted-foreground")}
name="PlusCircle"
/>
</button>
</CardContent>
</Card>
);
}

View file

@ -1,18 +0,0 @@
import { RadialProgressType } from "../../types/components";
export default function RadialProgressComponent({
value,
color,
}: RadialProgressType): JSX.Element {
const style = {
"--value": value! * 100,
"--size": "1.5rem",
"--thickness": "2px",
} as React.CSSProperties;
return (
<div className={"radial-progress " + color} style={style}>
<strong className="text-[8px]">{Math.trunc(value! * 100)}%</strong>
</div>
);
}

View file

@ -1,45 +0,0 @@
"use client";
import type { FC } from "react";
import React from "react";
import { Tooltip as ReactTooltip } from "react-tooltip";
import "react-tooltip/dist/react-tooltip.css";
import { TooltipProps } from "../../types/components";
import { classNames } from "../../utils/utils";
const TooltipReact: FC<TooltipProps> = ({
selector,
content,
disabled,
position = "top",
children,
htmlContent,
className,
clickable,
delayShow,
}: TooltipProps): JSX.Element => {
return (
<div className="tooltip-container">
{React.cloneElement(children as React.ReactElement, {
"data-tooltip-id": selector,
})}
<ReactTooltip
id={selector}
content={content}
className={classNames(
"z-[9999] !bg-white !text-xs !font-normal !text-foreground !opacity-100 !shadow-md",
className!
)}
place={position}
clickable={clickable}
isOpen={disabled ? false : undefined}
delayShow={delayShow}
positionStrategy="absolute"
float={true}
>
{htmlContent && htmlContent}
</ReactTooltip>
</div>
);
};
export default TooltipReact;

View file

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

View file

@ -1,121 +0,0 @@
import { Transition } from "@headlessui/react";
import { useState } from "react";
import Loading from "../../../components/ui/loading";
import { FlowType } from "../../../types/flow";
import { MISSED_ERROR_ALERT } from "../../../constants/alerts_constants";
import { BuildStatus } from "../../../constants/enums";
import useAlertStore from "../../../stores/alertStore";
import useFlowStore from "../../../stores/flowStore";
import { validateNodes } from "../../../utils/reactflowUtils";
import RadialProgressComponent from "../../RadialProgress";
import IconComponent from "../../genericIconComponent";
export default function BuildTrigger({
open,
flow,
}: {
open: boolean;
flow: FlowType;
}): JSX.Element {
const isBuilding = useFlowStore((state) => state.isBuilding);
const setIsBuilding = useFlowStore((state) => state.setIsBuilding);
const buildFlow = useFlowStore((state) => state.buildFlow);
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const updateBuildStatus = useFlowStore((state) => state.updateBuildStatus);
const setErrorData = useAlertStore((state) => state.setErrorData);
const eventClick = isBuilding ? "pointer-events-none" : "";
const [progress, setProgress] = useState(0);
async function handleBuild(flow: FlowType): Promise<void> {
try {
if (isBuilding) {
return;
}
const errorsObjs = validateNodes(nodes, edges);
const errors = errorsObjs.flatMap((errorObj) => errorObj.errors);
if (errors.length > 0) {
setErrorData({
title: MISSED_ERROR_ALERT,
list: errors,
});
const ids = errorsObjs.map((errorObj) => errorObj.id);
updateBuildStatus(ids, BuildStatus.ERROR);
return;
}
const minimumLoadingTime = 200; // in milliseconds
const startTime = Date.now();
setIsBuilding(true);
await enforceMinimumLoadingTime(startTime, minimumLoadingTime);
await buildFlow({});
} catch (error) {
console.error("Error:", error);
} finally {
setIsBuilding(false);
}
}
const hasIO = useFlowStore((state) => state.hasIO);
async function enforceMinimumLoadingTime(
startTime: number,
minimumLoadingTime: number
) {
const elapsedTime = Date.now() - startTime;
const remainingTime = minimumLoadingTime - elapsedTime;
if (remainingTime > 0) {
return new Promise((resolve) => setTimeout(resolve, remainingTime));
}
}
return (
<Transition
show={!open}
appear={true}
enter="transition ease-out duration-300"
enterFrom="translate-y-96"
enterTo="translate-y-0"
leave="transition ease-in duration-300"
leaveFrom="translate-y-0"
leaveTo="translate-y-96"
>
<div
className={hasIO ? "fixed bottom-20 right-4" : "fixed bottom-4 right-4"}
>
<div
className={`${eventClick} round-button-form`}
onClick={() => {
handleBuild(flow);
}}
>
<button>
<div className="round-button-div">
{isBuilding && progress < 1 ? (
// Render your loading animation here when isBuilding is true
<RadialProgressComponent
// ! confirm below works
color={"text-build-trigger"}
value={progress}
></RadialProgressComponent>
) : isBuilding ? (
<Loading
strokeWidth={1.5}
className="build-trigger-loading-icon"
/>
) : (
<IconComponent
name="Zap"
className="sh-6 w-6 fill-build-trigger stroke-build-trigger stroke-1"
/>
)}
</div>
</button>
</div>
</div>
</Transition>
);
}

View file

@ -1,71 +0,0 @@
import { Transition } from "@headlessui/react";
import {
CHAT_CANNOT_OPEN_DESCRIPTION,
CHAT_CANNOT_OPEN_TITLE,
FLOW_NOT_BUILT_DESCRIPTION,
FLOW_NOT_BUILT_TITLE,
} from "../../../constants/constants";
import useAlertStore from "../../../stores/alertStore";
import { chatTriggerPropType } from "../../../types/components";
import IconComponent from "../../genericIconComponent";
export default function ChatTrigger({
open,
setOpen,
isBuilt,
canOpen,
}: chatTriggerPropType): JSX.Element {
const setErrorData = useAlertStore((state) => state.setErrorData);
function handleClick(): void {
if (isBuilt) {
if (canOpen) {
setOpen(true);
} else {
setErrorData({
title: CHAT_CANNOT_OPEN_TITLE,
list: [CHAT_CANNOT_OPEN_DESCRIPTION],
});
}
} else {
setErrorData({
title: FLOW_NOT_BUILT_TITLE,
list: [FLOW_NOT_BUILT_DESCRIPTION],
});
}
}
return (
<Transition
show={!open}
appear={true}
enter="transition ease-out duration-300"
enterFrom="translate-y-96"
enterTo="translate-y-0"
leave="transition ease-in duration-300"
leaveFrom="translate-y-0"
leaveTo="translate-y-96"
>
<button
onClick={handleClick}
className={
"shadow-round-btn-shadow hover:shadow-round-btn-shadow message-button " +
(!isBuilt || !canOpen ? "cursor-not-allowed" : "cursor-pointer")
}
>
<div className="flex gap-3">
<IconComponent
name="MessagesSquare"
className={
"h-6 w-6 transition-all " +
(isBuilt && canOpen
? "message-button-icon"
: "disabled-message-button-icon")
}
/>
</div>
</button>
</Transition>
);
}

View file

@ -7,14 +7,12 @@ import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { useStoreStore } from "../../stores/storeStore";
import { ChatType } from "../../types/chat";
import { classNames } from "../../utils/utils";
import IOView from "../IOview";
import IOModal from "../../modals/IOModal";
import ForwardedIconComponent from "../genericIconComponent";
import { Separator } from "../ui/separator";
export default function FlowToolbar({ flow }: ChatType): JSX.Element {
export default function FlowToolbar(): JSX.Element {
const [open, setOpen] = useState(false);
const flowState = useFlowStore((state) => state.flowState);
const nodes = useFlowStore((state) => state.nodes);
const hasIO = useFlowStore((state) => state.hasIO);
const hasStore = useStoreStore((state) => state.hasStore);
const validApiKey = useStoreStore((state) => state.validApiKey);
@ -92,15 +90,15 @@ export default function FlowToolbar({ flow }: ChatType): JSX.Element {
<div className="flex">
<div className="flex h-full w-full gap-1 rounded-sm text-medium-indigo transition-all">
{hasIO ? (
<IOView open={open} setOpen={setOpen} disable={!hasIO}>
<div className="relative inline-flex w-full items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-medium-indigo transition-all transition-all duration-150 ease-in-out ease-in-out hover:bg-hover">
<IOModal open={open} setOpen={setOpen} disable={!hasIO}>
<div className="relative inline-flex w-full items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-medium-indigo transition-all transition-all duration-500 ease-in-out ease-in-out hover:bg-hover">
<ForwardedIconComponent
name="Zap"
className={"message-button-icon h-5 w-5 transition-all"}
/>
Run
</div>
</IOView>
</IOModal>
) : (
<div
className={`relative inline-flex w-full cursor-not-allowed items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-muted-foreground transition-all duration-150 ease-in-out ease-in-out`}

View file

@ -1,72 +0,0 @@
import { IconCheck, IconClipboard, IconDownload } from "@tabler/icons-react";
import { useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
import { programmingLanguages } from "../../../../constants/constants";
import { Props } from "../../../../types/components";
export function CodeBlock({ language, value }: Props): JSX.Element {
const [isCopied, setIsCopied] = useState<Boolean>(false);
const copyToClipboard = (): void => {
if (!navigator.clipboard || !navigator.clipboard.writeText) {
return;
}
navigator.clipboard.writeText(value).then(() => {
setIsCopied(true);
setTimeout(() => {
setIsCopied(false);
}, 2000);
});
};
const downloadAsFile = (): void => {
const fileExtension = programmingLanguages[language] || ".file";
const suggestedFileName = `${"generated-code"}${fileExtension}`;
const fileName = window.prompt("enter file name", suggestedFileName);
if (!fileName) {
// user pressed cancel on prompt
return;
}
const blob = new Blob([value], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.download = fileName;
link.href = url;
link.style.display = "none";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
return (
<div className="codeblock font-sans text-[16px]">
<div className="code-block-modal">
<span className="code-block-modal-span">{language}</span>
<div className="flex items-center">
<button className="code-block-modal-button" onClick={copyToClipboard}>
{isCopied ? <IconCheck size={18} /> : <IconClipboard size={18} />}
{isCopied ? "Copied!" : "Copy Code"}
</button>
<button className="code-block-modal-button" onClick={downloadAsFile}>
<IconDownload size={18} />
</button>
</div>
</div>
<SyntaxHighlighter
className="overflow-auto"
language={language}
style={oneDark}
customStyle={{ margin: 0 }}
>
{value}
</SyntaxHighlighter>
</div>
);
}
CodeBlock.displayName = "CodeBlock";

View file

@ -1,85 +0,0 @@
import * as base64js from "base64-js";
import { useState } from "react";
import IconComponent from "../../../components/genericIconComponent";
import { fileCardPropsType } from "../../../types/components";
export default function FileCard({
fileName,
content,
fileType,
}: fileCardPropsType): JSX.Element {
const handleDownload = (): void => {
const byteArray = new Uint8Array(base64js.toByteArray(content));
const blob = new Blob([byteArray], { type: "application/octet-stream" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = fileName + ".png";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
const [isHovered, setIsHovered] = useState(false);
function handleMouseEnter(): void {
setIsHovered(true);
}
function handleMouseLeave(): void {
setIsHovered(false);
}
if (fileType === "image") {
return (
<div
className="relative h-1/4 w-1/4"
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<img
src={`data:image/png;base64,${content}`}
alt="generated image"
className="h-full w-full rounded-lg"
/>
{isHovered && (
<div className={`file-card-modal-image-div `}>
<button
className="file-card-modal-image-button "
onClick={handleDownload}
>
<IconComponent
name="DownloadCloud"
className="h-5 w-5 text-current hover:scale-110"
/>
</button>
</div>
)}
</div>
);
}
return (
<button onClick={handleDownload} className="file-card-modal-button">
<div className="file-card-modal-div">
ooooooooooooooo{" "}
{fileType === "image" ? (
<img
src={`data:image/png;base64,${content}`}
alt=""
className="h-8 w-8"
/>
) : (
<IconComponent name="File" className="h-8 w-8" />
)}
<div className="file-card-modal-footer">
{" "}
<div className="file-card-modal-name">{fileName}</div>
<div className="file-card-modal-type">{fileType}</div>
</div>
<IconComponent
name="DownloadCloud"
className="ml-auto h-6 w-6 text-current"
/>
</div>
</button>
);
}

View file

@ -1,30 +0,0 @@
import React, { ReactNode } from "react";
interface ElementStackProps {
children: ReactNode[];
}
const ElementStack: React.FC<ElementStackProps> = ({ children }) => {
return (
<div
className={`grid grid-cols-1`}
style={{ display: "grid", gridAutoFlow: "row" }}
>
{children.map((child, index) => (
<div
key={index}
style={{
gridColumn: 1,
gridRow: 1,
transform: `translateX(${index * 0.1}rem)`,
zIndex: children.length - index,
}}
>
{child}
</div>
))}
</div>
);
};
export default ElementStack;

View file

@ -1,59 +0,0 @@
import { Switch } from "@headlessui/react";
import { useEffect } from "react";
import { ToggleComponentType } from "../../types/components";
import { classNames } from "../../utils/utils";
export default function ToggleComponent({
enabled,
setEnabled,
disabled,
}: ToggleComponentType): JSX.Element {
// set component state as disabled
useEffect(() => {
if (disabled) {
setEnabled(false);
}
}, [disabled, setEnabled]);
return (
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
<Switch
checked={enabled}
onChange={(isEnabled: boolean) => {
setEnabled(isEnabled);
}}
className={classNames(
enabled ? "bg-primary" : "bg-input",
"toggle-component-switch "
)}
>
<span className="sr-only">Use setting</span>
<span
className={classNames(
enabled ? "translate-x-5" : "translate-x-0",
"toggle-component-span",
disabled ? "bg-input " : "bg-background"
)}
>
<span
className={classNames(
enabled
? "opacity-0 duration-100 ease-out"
: "opacity-100 duration-200 ease-in",
"toggle-component-second-span"
)}
aria-hidden="true"
></span>
<span
className={classNames(
enabled
? "opacity-100 duration-200 ease-in"
: "opacity-0 duration-100 ease-out",
"toggle-component-second-span"
)}
aria-hidden="true"
></span>
</span>
</Switch>
</div>
);
}

View file

@ -32,7 +32,7 @@ const AccordionTrigger = React.forwardRef<
)}
>
{children}
<ChevronDownIcon className="h-4 w-4 text-muted-foreground transition-transform duration-200" />
<ChevronDownIcon className="h-4 w-4 font-bold text-primary transition-transform duration-200" />
</div>
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>

View file

@ -14,3 +14,8 @@ export enum BuildStatus {
INACTIVE = "INACTIVE",
ERROR = "ERROR",
}
export enum InputOutput {
INPUT = "input",
OUTPUT = "output",
}

View file

@ -83,7 +83,7 @@ export function AuthProvider({ children }): React.ReactElement {
useFlowsManagerStore.setState({ isLoading: false });
}
});
}, [setUserData, setLoading, autoLogin, setIsAdmin]);
}, [autoLogin]);
function getUser() {
getLoggedUser()

View file

@ -9,13 +9,16 @@ import "./style/index.css";
import "./style/applies.css";
// @ts-ignore
import "./style/classes.css";
import { StrictMode } from "react";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<ContextWrapper>
<App />
</ContextWrapper>
<StrictMode>
<ContextWrapper>
<App />
</ContextWrapper>
</StrictMode>
);
reportWebVitals();

View file

@ -1,11 +1,11 @@
import { Button } from "../../ui/button";
import { Button } from "../../../../../../components/ui/button";
import { useEffect, useState } from "react";
import { BASE_URL_API } from "../../../constants/constants";
import { uploadFile } from "../../../controllers/API";
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
import { IOFileInputProps } from "../../../types/components";
import IconComponent from "../../genericIconComponent";
import { BASE_URL_API } from "../../../../../../constants/constants";
import { uploadFile } from "../../../../../../controllers/API";
import useFlowsManagerStore from "../../../../../../stores/flowsManagerStore";
import { IOFileInputProps } from "../../../../../../types/components";
import IconComponent from "../../../../../../components/genericIconComponent";
export default function IOFileInput({ field, updateValue }: IOFileInputProps) {
//component to handle file upload from chatIO

View file

@ -0,0 +1,109 @@
import { cloneDeep } from "lodash";
import { InputOutput } from "../../../../constants/enums";
import useFlowStore from "../../../../stores/flowStore";
import { IOFieldViewProps } from "../../../../types/components";
import IOFileInput from "./components/FileInput";
import { Textarea } from "../../../../components/ui/textarea";
export default function IOFieldView({
type,
fieldType,
fieldId,
left,
}: IOFieldViewProps): JSX.Element | undefined {
const nodes = useFlowStore((state) => state.nodes);
const setNode = useFlowStore((state) => state.setNode);
const flowPool = useFlowStore((state) => state.flowPool);
const node = nodes.find((node) => node.id === fieldId);
function handleOutputType() {
if (!node) return <>"No node found!"</>;
switch (type) {
case InputOutput.INPUT:
switch (fieldType) {
case "TextInput":
return (
<Textarea
className={`w-full ${left ? "" : " h-full"}`}
placeholder={"Enter text..."}
value={node.data.node!.template["input_value"].value}
onChange={(e) => {
e.target.value;
if (node) {
let newNode = cloneDeep(node);
newNode.data.node!.template["input_value"].value =
e.target.value;
setNode(node.id, newNode);
}
}}
/>
);
case "FileLoader":
return (
<IOFileInput
field={node.data.node!.template["file_path"]["value"]}
updateValue={(e) => {
if (node) {
let newNode = cloneDeep(node);
newNode.data.node!.template["file_path"].value = e;
setNode(node.id, newNode);
}
}}
/>
);
default:
return (
<Textarea
className="w-full custom-scroll"
placeholder={"Enter text..."}
value={node.data.node!.template["input_value"]}
onChange={(e) => {
e.target.value;
if (node) {
let newNode = cloneDeep(node);
newNode.data.node!.template["input_value"].value =
e.target.value;
setNode(node.id, newNode);
}
}}
/>
);
}
case InputOutput.OUTPUT:
switch (fieldType) {
case "TextOutput":
return (
<Textarea
className={`w-full custom-scroll ${left ? "" : " h-full"}`}
placeholder={"Empty"}
// update to real value on flowPool
value={
(flowPool[node.id] ?? [])[
(flowPool[node.id]?.length ?? 1) - 1
]?.params ?? ""
}
readOnly
/>
);
default:
return (
<Textarea
className={`w-full custom-scroll ${left ? "" : " h-full"}`}
placeholder={"Empty"}
// update to real value on flowPool
value={
(flowPool[node.id] ?? [])[
(flowPool[node.id]?.length ?? 1) - 1
]?.params ?? ""
}
readOnly
/>
);
}
default:
break;
}
}
return handleOutputType();
}

View file

@ -1,13 +1,13 @@
import { useEffect, useState } from "react";
import IconComponent from "../../../components/genericIconComponent";
import { Textarea } from "../../../components/ui/textarea";
import IconComponent from "../../../../../components/genericIconComponent";
import { Textarea } from "../../../../../components/ui/textarea";
import {
CHAT_INPUT_PLACEHOLDER,
CHAT_INPUT_PLACEHOLDER_SEND,
} from "../../../constants/constants";
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
import { chatInputType } from "../../../types/components";
import { classNames } from "../../../utils/utils";
} from "../../../../../constants/constants";
import useFlowsManagerStore from "../../../../../stores/flowsManagerStore";
import { chatInputType } from "../../../../../types/components";
import { classNames } from "../../../../../utils/utils";
export default function ChatInput({
lockChat,
@ -25,14 +25,6 @@ export default function ChatInput({
}
}, [lockChat, inputRef]);
/* function handleChange(value: number) {
console.log(value);
if (value > 0) {
setRepeat(value);
} else {
setRepeat(1);
}
} */
useEffect(() => {
if (inputRef.current) {
@ -50,7 +42,7 @@ export default function ChatInput({
event.key === "Enter" &&
!lockChat &&
!saveLoading &&
!event.shiftKey
!event.shiftKey && !event.nativeEvent.isComposing
) {
sendMessage(repeat);
}

View file

@ -2,8 +2,8 @@ import { IconCheck, IconClipboard, IconDownload } from "@tabler/icons-react";
import { useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
import { programmingLanguages } from "../../../../constants/constants";
import { Props } from "../../../../types/components";
import { programmingLanguages } from "../../../../../../constants/constants";
import { Props } from "../../../../../../types/components";
export function CodeBlock({ language, value }: Props): JSX.Element {
const [isCopied, setIsCopied] = useState<Boolean>(false);

View file

@ -4,15 +4,14 @@ import Markdown from "react-markdown";
import rehypeMathjax from "rehype-mathjax";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import MaleTechnology from "../../../assets/male-technologist.png";
import Robot from "../../../assets/robot.png";
import SanitizedHTMLWrapper from "../../../components/SanitizedHTMLWrapper";
import CodeTabsComponent from "../../../components/codeTabsComponent";
import IconComponent from "../../../components/genericIconComponent";
import useAlertStore from "../../../stores/alertStore";
import useFlowStore from "../../../stores/flowStore";
import { chatMessagePropsType } from "../../../types/components";
import { classNames, cn } from "../../../utils/utils";
import MaleTechnology from "../../../../../assets/male-technologist.png";
import Robot from "../../../../../assets/robot.png";
import SanitizedHTMLWrapper from "../../../../../components/SanitizedHTMLWrapper";
import CodeTabsComponent from "../../../../../components/codeTabsComponent";
import IconComponent from "../../../../../components/genericIconComponent";
import useFlowStore from "../../../../../stores/flowStore";
import { chatMessagePropsType } from "../../../../../types/components";
import { classNames } from "../../../../../utils/utils";
import FileCard from "../fileComponent";
export default function ChatMessage({
@ -34,7 +33,6 @@ export default function ChatMessage({
const [isStreaming, setIsStreaming] = useState(false);
const eventSource = useRef<EventSource | undefined>(undefined);
const updateFlowPool = useFlowStore((state) => state.updateFlowPool);
const setErrorData = useAlertStore((state) => state.setErrorData);
const chatMessageRef = useRef(chatMessage);
// Sync ref with state
@ -59,17 +57,7 @@ export default function ChatMessage({
setIsStreaming(false);
eventSource.current?.close();
setStreamUrl(undefined);
// property data is not available in the event object
// so check if the event object has a data property
if (event.data) {
let parsedData = JSON.parse(event.data);
if (parsedData.error) {
reject(new Error(parsedData.error));
} else
reject(new Error("An error occurred while streaming the output"));
} else {
reject(new Error("An error occurred while streaming the output"));
}
reject(new Error("Streaming failed"));
};
eventSource.current.addEventListener("close", (event) => {
setStreamUrl(undefined); // Update state to reflect the stream is closed
@ -91,10 +79,7 @@ export default function ChatMessage({
}
})
.catch((error) => {
setErrorData({
title: "Streaming Error",
list: [error.message],
});
console.error(error);
setLockChat(false);
});
}
@ -123,28 +108,30 @@ export default function ChatMessage({
chat.isSend ? "" : " "
)}
>
<div
className={classNames(
"mr-3 mt-1 flex max-w-16 flex-col items-center gap-1 overflow-hidden px-3 pb-3"
)}
>
<div className="flex flex-col items-center gap-1">
<div
className={cn(
"relative flex h-8 w-8 items-center justify-center overflow-hidden rounded-md p-5 text-2xl",
!chat.isSend ? "bg-chat-bot-icon" : "bg-chat-user-icon"
)}
>
<img
src={!chat.isSend ? Robot : MaleTechnology}
className="absolute scale-[60%]"
alt={!chat.isSend ? "robot_image" : "male_technology"}
/>
<div className={classNames("form-modal-chatbot-icon")}>
{!chat.isSend ? (
<div className="form-modal-chat-image">
<div className="form-modal-chat-bot-icon ">
<img
src={Robot}
className="form-modal-chat-icon-img"
alt="robot_image"
/>
</div>
<span className="truncate text-xs">{chat.sender_name}</span>
</div>
<span className="max-w-16 truncate text-xs">
{chat.sender_name}
</span>
</div>
) : (
<div className="form-modal-chat-image">
<div className="form-modal-chat-user-icon ">
<img
src={MaleTechnology}
className="form-modal-chat-icon-img"
alt="male_technology"
/>
</div>
<span className="truncate text-xs">{chat.sender_name}</span>
</div>
)}
</div>
{!chat.isSend ? (
<div className="form-modal-chat-text-position min-w-96 flex-grow">

View file

@ -1,7 +1,7 @@
import * as base64js from "base64-js";
import { useState } from "react";
import IconComponent from "../../../components/genericIconComponent";
import { fileCardPropsType } from "../../../types/components";
import IconComponent from "../../../../../components/genericIconComponent";
import { fileCardPropsType } from "../../../../../types/components";
export default function FileCard({
fileName,

View file

@ -1,31 +1,32 @@
import { useEffect, useRef, useState } from "react";
import IconComponent from "../../components/genericIconComponent";
import { NOCHATOUTPUT_NOTICE_ALERT } from "../../constants/alerts_constants";
import IconComponent from "../../../../components/genericIconComponent";
import { NOCHATOUTPUT_NOTICE_ALERT } from "../../../../constants/alerts_constants";
import {
CHAT_FIRST_INITIAL_TEXT,
CHAT_SECOND_INITIAL_TEXT,
} from "../../constants/constants";
import { deleteFlowPool } from "../../controllers/API";
import useAlertStore from "../../stores/alertStore";
import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { sendAllProps } from "../../types/api";
} from "../../../../constants/constants";
import { deleteFlowPool } from "../../../../controllers/API";
import useAlertStore from "../../../../stores/alertStore";
import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { sendAllProps } from "../../../../types/api";
import {
ChatMessageType,
ChatOutputType,
FlowPoolObjectType,
} from "../../types/chat";
import { classNames } from "../../utils/utils";
} from "../../../../types/chat";
import { classNames } from "../../../../utils/utils";
import ChatInput from "./chatInput";
import ChatMessage from "./chatMessage";
import { chatViewProps } from "../../../../types/components";
export default function NewChatView({
export default function ChatView({
sendMessage,
chatValue,
setChatValue,
lockChat,
setLockChat,
}): JSX.Element {
}: chatViewProps): JSX.Element {
const { flowPool, outputs, inputs, CleanFlowPool } = useFlowStore();
const { setNoticeData } = useAlertStore();
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);

View file

@ -1,36 +1,37 @@
import { useEffect, useState } from "react";
import AccordionComponent from "../../components/AccordionComponent";
import IOFieldView from "./components/IOFieldView";
import ShadTooltip from "../../components/ShadTooltipComponent";
import IconComponent from "../../components/genericIconComponent";
import ChatView from "./components/chatView";
import { Badge } from "../../components/ui/badge";
import { Button } from "../../components/ui/button";
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "../../components/ui/tabs";
import {
CHAT_FORM_DIALOG_SUBTITLE,
OUTPUTS_MODAL_TITLE,
TEXT_INPUT_MODAL_TITLE,
} from "../../constants/constants";
import BaseModal from "../../modals/baseModal";
import { InputOutput } from "../../constants/enums";
import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { NodeType } from "../../types/flow";
import { updateVerticesOrder } from "../../utils/buildUtils";
import { cn } from "../../utils/utils";
import AccordionComponent from "../AccordionComponent";
import IOInputField from "../IOInputField";
import IOOutputView from "../IOOutputView";
import ShadTooltip from "../ShadTooltipComponent";
import IconComponent from "../genericIconComponent";
import NewChatView from "../newChatView";
import { Badge } from "../ui/badge";
import { Button } from "../ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
import BaseModal from "../baseModal";
import { IOModalPropsType } from "../../types/components";
export default function IOView({
export default function IOModal({
children,
open,
setOpen,
disable,
}: {
children: JSX.Element;
open: boolean;
setOpen: (open: boolean) => void;
disable?: boolean;
}): JSX.Element {
}: IOModalPropsType): JSX.Element {
const inputs = useFlowStore((state) => state.inputs).filter(
(input) => input.type !== "ChatInput"
);
@ -212,10 +213,11 @@ export default function IOView({
<div className="file-component-tab-column">
<div className="">
{input && (
<IOInputField
<IOFieldView
type={InputOutput.INPUT}
left={true}
inputType={input.type}
inputId={input.id}
fieldType={input.type}
fieldId={input.id}
/>
)}
</div>
@ -279,10 +281,11 @@ export default function IOView({
<div className="file-component-tab-column">
<div className="">
{output && (
<IOOutputView
<IOFieldView
type={InputOutput.OUTPUT}
left={true}
outputType={output.type}
outputId={output.id}
fieldType={output.type}
fieldId={output.id}
/>
)}
</div>
@ -318,16 +321,18 @@ export default function IOView({
{inputs.some(
(input) => input.id === selectedViewField.id
) ? (
<IOInputField
<IOFieldView
type={InputOutput.INPUT}
left={false}
inputType={selectedViewField.type!}
inputId={selectedViewField.id!}
fieldType={selectedViewField.type!}
fieldId={selectedViewField.id!}
/>
) : (
<IOOutputView
<IOFieldView
type={InputOutput.OUTPUT}
left={false}
outputType={selectedViewField.type!}
outputId={selectedViewField.id!}
fieldType={selectedViewField.type!}
fieldId={selectedViewField.id!}
/>
)}
</div>
@ -339,7 +344,7 @@ export default function IOView({
selectedViewField ? "hidden" : ""
)}
>
<NewChatView
<ChatView
sendMessage={sendMessage}
chatValue={chatValue}
setChatValue={setChatValue}

View file

@ -1,6 +1,6 @@
import { useNavigate } from "react-router-dom";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { Card, CardContent, CardDescription, CardTitle } from "../ui/card";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { Card, CardContent, CardDescription, CardTitle } from "../../../../components/ui/card";
export default function NewFlowCardComponent() {
const addFlow = useFlowsManagerStore((state) => state.addFlow);

View file

@ -1,21 +1,22 @@
import { useNavigate } from "react-router-dom";
/// <reference types="vite-plugin-svgr/client" />
//@ts-ignore
import { ReactComponent as TransferFiles } from "../../assets/undraw_transfer_files_re_a2a9.svg";
import { ReactComponent as TransferFiles } from "../../../../assets/undraw_transfer_files_re_a2a9.svg";
//@ts-ignore
import { ReactComponent as BasicPrompt } from "../../assets/undraw_design_components_9vy6.svg";
import { ReactComponent as BasicPrompt } from "../../../../assets/undraw_design_components_9vy6.svg";
//@ts-ignore
import { ReactComponent as ChatWithHistory } from "../../assets/undraw_mobile_messages_re_yx8w.svg";
import { ReactComponent as ChatWithHistory } from "../../../../assets/undraw_mobile_messages_re_yx8w.svg";
//@ts-ignore
import { ReactComponent as Assistant } from "../../assets/undraw_team_collaboration_re_ow29.svg";
import { ReactComponent as Assistant } from "../../../../assets/undraw_team_collaboration_re_ow29.svg";
//@ts-ignore
import { ReactComponent as APIRequest } from "../../assets/undraw_real_time_analytics_re_yliv.svg";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { FlowType } from "../../types/flow";
import { updateIds } from "../../utils/reactflowUtils";
import { Card, CardContent, CardDescription, CardTitle } from "../ui/card";
import { ReactComponent as APIRequest } from "../../../../assets/undraw_real_time_analytics_re_yliv.svg";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { FlowType } from "../../../../types/flow";
import { updateIds } from "../../../../utils/reactflowUtils";
import { Card, CardContent, CardDescription, CardTitle } from "../../../../components/ui/card";
import { UndrawCardComponentProps } from "../../../../types/components";
export default function UndrawCardComponent({ flow }: { flow: FlowType }) {
export default function UndrawCardComponent({ flow }: UndrawCardComponentProps): JSX.Element {
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const navigate = useNavigate();

View file

@ -0,0 +1,32 @@
import NewFlowCardComponent from "./components/NewFlowCardComponent";
import UndrawCardComponent from "./components/undrawCards";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import BaseModal from "../baseModal";
import { newFlowModalPropsType } from "../../types/components";
export default function NewFlowModal({ open, setOpen }: newFlowModalPropsType): JSX.Element {
const examples = useFlowsManagerStore((state) => state.examples);
return (
<BaseModal size="three-cards" open={open} setOpen={setOpen}>
<BaseModal.Header description={"Select a template below"}>
<span className="pr-2" data-testid="modal-title">
Get Started
</span>
{/* <IconComponent
name="Group"
className="h-6 w-6 stroke-2 text-primary "
aria-hidden="true"
/> */}
</BaseModal.Header>
<BaseModal.Content>
<div className=" grid h-full w-full grid-cols-3 gap-3 overflow-auto p-4 custom-scroll">
<NewFlowCardComponent />
{examples.map((example, idx) => {
return <UndrawCardComponent key={idx} flow={example} />;
})}
</div>
</BaseModal.Content>
</BaseModal>
);
}

View file

@ -1,113 +0,0 @@
import { useEffect } from "react";
import IconComponent from "../../../components/genericIconComponent";
import { Textarea } from "../../../components/ui/textarea";
import {
CHAT_INPUT_PLACEHOLDER,
CHAT_INPUT_PLACEHOLDER_SEND,
} from "../../../constants/constants";
import { chatInputType } from "../../../types/components";
import { classNames } from "../../../utils/utils";
export default function ChatInput({
lockChat,
chatValue,
sendMessage,
setChatValue,
inputRef,
noInput,
}: chatInputType): JSX.Element {
useEffect(() => {
if (!lockChat && inputRef.current) {
inputRef.current.focus();
}
}, [lockChat, inputRef]);
useEffect(() => {
if (inputRef.current) {
inputRef.current.style.height = "inherit"; // Reset the height
inputRef.current.style.height = `${inputRef.current.scrollHeight}px`; // Set it to the scrollHeight
}
}, [chatValue]);
return (
<div className="relative">
<Textarea
onKeyDown={(event) => {
if (event.key === "Enter" && !event.nativeEvent.isComposing && !lockChat && !event.shiftKey) {
sendMessage();
}
}}
rows={1}
ref={inputRef}
disabled={lockChat || noInput}
style={{
resize: "none",
bottom: `${inputRef?.current?.scrollHeight}px`,
maxHeight: "150px",
overflow: `${
inputRef.current && inputRef.current.scrollHeight > 150
? "auto"
: "hidden"
}`,
}}
value={
lockChat
? "Thinking..."
: typeof chatValue === "object" &&
Object.keys(chatValue)?.length === 0
? CHAT_INPUT_PLACEHOLDER
: chatValue
}
onChange={(event): void => {
setChatValue(event.target.value);
}}
className={classNames(
lockChat
? " form-modal-lock-true bg-input"
: noInput
? "form-modal-no-input bg-input"
: " form-modal-lock-false bg-background",
"form-modal-lockchat"
)}
placeholder={
noInput ? CHAT_INPUT_PLACEHOLDER : CHAT_INPUT_PLACEHOLDER_SEND
}
/>
<div className="form-modal-send-icon-position">
<button
className={classNames(
"form-modal-send-button",
noInput
? "bg-high-indigo text-background"
: chatValue === ""
? "text-primary"
: "bg-chat-send text-background"
)}
disabled={lockChat}
onClick={(): void => sendMessage()}
>
{lockChat ? (
<IconComponent
name="Lock"
className="form-modal-lock-icon"
aria-hidden="true"
/>
) : noInput ? (
<IconComponent
name="Sparkles"
className="form-modal-play-icon"
aria-hidden="true"
/>
) : (
<IconComponent
name="LucideSend"
className="form-modal-send-icon "
aria-hidden="true"
/>
)}
</button>
</div>
</div>
);
}

View file

@ -1,221 +0,0 @@
import Convert from "ansi-to-html";
import { useState } from "react";
import ReactMarkdown from "react-markdown";
import rehypeMathjax from "rehype-mathjax";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import MaleTechnology from "../../../assets/male-technologist.png";
import Robot from "../../../assets/robot.png";
import SanitizedHTMLWrapper from "../../../components/SanitizedHTMLWrapper";
import CodeTabsComponent from "../../../components/codeTabsComponent";
import IconComponent from "../../../components/genericIconComponent";
import { chatMessagePropsType } from "../../../types/components";
import { classNames } from "../../../utils/utils";
import FileCard from "../fileComponent";
export default function ChatMessage({
chat,
lockChat,
lastMessage,
}: chatMessagePropsType): JSX.Element {
const convert = new Convert({ newline: true });
const [hidden, setHidden] = useState(true);
const template = chat.template;
const [promptOpen, setPromptOpen] = useState(false);
return (
<div
className={classNames("form-modal-chat-position", chat.isSend ? "" : " ")}
>
<div className={classNames("form-modal-chatbot-icon ")}>
{!chat.isSend ? (
<div className="form-modal-chat-image">
<div className="form-modal-chat-bot-icon ">
<img
src={Robot}
className="form-modal-chat-icon-img"
alt="robot_image"
/>
</div>
</div>
) : (
<div className="form-modal-chat-image">
<div className="form-modal-chat-user-icon ">
<img
src={MaleTechnology}
className="form-modal-chat-icon-img"
alt="male_technology"
/>
</div>
</div>
)}
</div>
{!chat.isSend ? (
<div className="form-modal-chat-text-position">
<div className="form-modal-chat-text">
{hidden && chat.thought && chat.thought !== "" && (
<div
onClick={(): void => setHidden((prev) => !prev)}
className="form-modal-chat-icon-div"
>
<IconComponent
name="MessageSquare"
className="form-modal-chat-icon"
/>
</div>
)}
{chat.thought && chat.thought !== "" && !hidden && (
<SanitizedHTMLWrapper
className=" form-modal-chat-thought"
content={convert.toHtml(chat.thought)}
onClick={() => setHidden((prev) => !prev)}
/>
)}
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
<div className="w-full">
<div className="w-full dark:text-white">
<div className="w-full">
{chat.message.toString() === "" && lockChat ? (
<IconComponent
name="MoreHorizontal"
className="h-8 w-8 animate-pulse"
/>
) : (
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax]}
className="markdown prose min-w-full text-primary word-break-break-word
dark:prose-invert"
components={{
pre({ node, ...props }) {
return <>{props.children}</>;
},
code: ({
node,
inline,
className,
children,
...props
}) => {
if (children.length) {
if (children[0] === "▍") {
return (
<span className="form-modal-markdown-span">
</span>
);
}
children[0] = (children[0] as string).replace(
"`▍`",
"▍"
);
}
const match = /language-(\w+)/.exec(className || "");
return !inline ? (
<CodeTabsComponent
isMessage
tabs={[
{
name: (match && match[1]) || "",
mode: (match && match[1]) || "",
image:
"https://curl.se/logo/curl-symbol-transparent.png",
language: (match && match[1]) || "",
code: String(children).replace(/\n$/, ""),
},
]}
activeTab={"0"}
setActiveTab={() => {}}
/>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
}}
>
{chat.message.toString()}
</ReactMarkdown>
)}
</div>
{chat.files && (
<div className="my-2 w-full">
{chat.files.map((file, index) => {
return (
<div key={index} className="my-2 w-full">
<FileCard
fileName={"Generated File"}
fileType={file.data_type}
content={file.data}
/>
</div>
);
})}
</div>
)}
</div>
</div>
</div>
</div>
) : (
<div>
{template ? (
<>
<button
className="form-modal-initial-prompt-btn"
onClick={() => {
setPromptOpen((old) => !old);
}}
>
Display Prompt
<IconComponent
name="ChevronDown"
className={
"h-3 w-3 transition-all " + (promptOpen ? "rotate-180" : "")
}
/>
</button>
<span className="prose text-primary word-break-break-word dark:prose-invert">
{promptOpen
? template?.split("\n")?.map((line, index) => {
const regex = /{([^}]+)}/g;
let match;
let parts: Array<JSX.Element | string> = [];
let lastIndex = 0;
while ((match = regex.exec(line)) !== null) {
// Push text up to the match
if (match.index !== lastIndex) {
parts.push(line.substring(lastIndex, match.index));
}
// Push div with matched text
if (chat.message[match[1]]) {
parts.push(
<span className="chat-message-highlight">
{chat.message[match[1]]}
</span>
);
}
// Update last index
lastIndex = regex.lastIndex;
}
// Push text after the last match
if (lastIndex !== line.length) {
parts.push(line.substring(lastIndex));
}
return <p>{parts}</p>;
})
: chat.message[chat.chatKey]}
</span>
</>
) : (
<span>{chat.message[chat.chatKey]}</span>
)}
</div>
)}
</div>
);
}

View file

@ -1,650 +0,0 @@
import { useContext, useEffect, useRef, useState } from "react";
import { sendAllProps } from "../../types/api";
import { ChatMessageType } from "../../types/chat";
import { FlowType } from "../../types/flow";
import { classNames } from "../../utils/utils";
import ChatInput from "./chatInput";
import ChatMessage from "./chatMessage";
import _, { cloneDeep } from "lodash";
import AccordionComponent from "../../components/AccordionComponent";
import IconComponent from "../../components/genericIconComponent";
import ToggleShadComponent from "../../components/toggleShadComponent";
import { Badge } from "../../components/ui/badge";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../../components/ui/dialog";
import { Textarea } from "../../components/ui/textarea";
import {
CHAT_ERROR_ALERT,
INFO_MISSING_ALERT,
MSG_ERROR_ALERT,
} from "../../constants/alerts_constants";
import {
CHAT_FIRST_INITIAL_TEXT,
CHAT_FORM_DIALOG_SUBTITLE,
CHAT_SECOND_INITIAL_TEXT,
LANGFLOW_CHAT_TITLE,
} from "../../constants/constants";
import { BuildStatus } from "../../constants/enums";
import { AuthContext } from "../../contexts/authContext";
import { getBuildStatus } from "../../controllers/API";
import useAlertStore from "../../stores/alertStore";
import useFlowStore from "../../stores/flowStore";
import { FlowState } from "../../types/tabs";
import { validateNodes } from "../../utils/reactflowUtils";
export default function FormModal({
flow,
open,
setOpen,
}: {
open: boolean;
setOpen: (open: boolean) => void;
flow: FlowType;
}): JSX.Element {
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const updateBuildStatus = useFlowStore((state) => state.updateBuildStatus);
const flowState = useFlowStore((state) => state.flowState);
const setFlowState = useFlowStore((state) => state.setFlowState);
const [chatValue, setChatValue] = useState(() => {
try {
if (!flowState) {
throw new Error("flowState is undefined");
}
const inputKeys = flowState.input_keys;
const handleKeys = flowState.handle_keys;
const keyToUse = Object.keys(inputKeys!).find(
(key) => !handleKeys?.some((j) => j === key) && inputKeys![key] === ""
);
return inputKeys![keyToUse!];
} catch (error) {
console.error(error);
// return a sensible default or `undefined` if no default is possible
return undefined;
}
});
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
const template = useRef(flowState?.template ?? undefined);
const { accessToken } = useContext(AuthContext);
const setErrorData = useAlertStore((state) => state.setErrorData);
const ws = useRef<WebSocket | null>(null);
const [lockChat, setLockChat] = useState(false);
const isOpen = useRef(open);
const messagesRef = useRef<HTMLDivElement | null>(null);
const [chatKey, setChatKey] = useState(() => {
if (flowState?.input_keys) {
return Object.keys(flowState.input_keys!).find(
(key) =>
!flowState.handle_keys!.some((j) => j === key) &&
flowState.input_keys![key] === ""
);
}
// TODO: return a sensible default
return "";
});
useEffect(() => {
if (messagesRef.current) {
messagesRef.current.scrollTop = messagesRef.current.scrollHeight;
}
}, [chatHistory]);
useEffect(() => {
isOpen.current = open;
}, [open]);
var isStream = false;
const addChatHistory = (
message: string | Object,
isSend: boolean,
chatKey: string,
template?: string,
thought?: string,
files?: Array<any>
) => {
setChatHistory((old) => {
let newChat = _.cloneDeep(old);
if (files) {
newChat.push({ message, isSend, files, thought, chatKey });
} else if (thought) {
newChat.push({ message, isSend, thought, chatKey });
} else if (template) {
newChat.push({ message, isSend, chatKey, template });
} else {
newChat.push({ message, isSend, chatKey });
}
return newChat;
});
};
//add proper type signature for function
function updateLastMessage({
str,
thought,
prompt,
end = false,
files,
}: {
str?: string;
thought?: string;
prompt?: string;
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) {
newChat[newChat.length - 1].thought = thought;
}
if (files && newChat[newChat.length - 1]?.files) {
newChat[newChat.length - 1].files = files;
}
if (prompt && newChat[newChat.length - 2]?.template) {
newChat[newChat.length - 2].template = prompt;
}
return newChat;
});
}
function handleOnClose(event: CloseEvent): void {
if (isOpen.current) {
//check if the user has been logged out, if so close the chat when the user is redirected to the login page
if (window.location.href.includes("login")) {
setOpen(false);
ws.current?.close();
return;
}
getBuildStatus(flow.id)
.then((response) => {
if (response.data.built) {
connectWS();
} else {
setErrorData({
title: CHAT_ERROR_ALERT,
});
}
})
.catch((error) => {
setErrorData({
title: error.data?.detail ? error.data.detail : error.message,
});
});
setErrorData({ title: event.reason });
setTimeout(() => {
setLockChat(false);
}, 1000);
}
}
//TODO improve check of user authentication
function getWebSocketUrl(
chatId: string,
isDevelopment: boolean = false
): string {
const isSecureProtocol =
window.location.protocol === "https:" || window.location.port === "443";
const webSocketProtocol = isSecureProtocol ? "wss" : "ws";
const host = isDevelopment ? "localhost:7860" : window.location.host;
const chatEndpoint = `/api/v1/chat/${chatId}`;
return `${
isDevelopment ? "ws" : webSocketProtocol
}://${host}${chatEndpoint}?token=${encodeURIComponent(accessToken!)}`;
}
function handleWsMessage(data: any) {
if (Array.isArray(data) && data.length > 0) {
//set chat history
setChatHistory((_) => {
let newChatHistory: ChatMessageType[] = [];
for (let i = 0; i < data.length; i++) {
if (data[i].type === "prompt" && data[i].prompt) {
if (data[i - 1] && !data[i - 1].is_bot) {
data[i - 1].prompt = data[i].prompt;
template.current = data[i].prompt;
}
}
}
data = data.filter((item: any) => item.type !== "prompt");
data.forEach(
(chatItem: {
intermediate_steps?: string;
is_bot: boolean;
message: string;
prompt?: string;
type: string;
chatKey: string;
files?: Array<any>;
}) => {
if (chatItem.message) {
newChatHistory.push(
chatItem.files
? {
isSend: !chatItem.is_bot,
message: chatItem.message,
template: chatItem.prompt,
thought: chatItem.intermediate_steps,
files: chatItem.files,
chatKey: chatItem.chatKey,
}
: {
isSend: !chatItem.is_bot,
message: chatItem.message,
template: chatItem.prompt,
thought: chatItem.intermediate_steps,
chatKey: chatItem.chatKey,
}
);
}
}
);
return newChatHistory;
});
}
if (data.type === "start") {
addChatHistory("", false, chatKey!);
isStream = true;
}
if (data.type === "end") {
if (data.message) {
updateLastMessage({
str: data.message,
end: true,
prompt: template.current,
});
}
if (data.intermediate_steps) {
updateLastMessage({
str: data.message,
thought: data.intermediate_steps,
end: true,
});
}
if (data.files) {
updateLastMessage({
end: true,
files: data.files,
});
}
setLockChat(false);
isStream = false;
}
if (data.type == "prompt" && data.prompt) {
template.current = data.prompt;
}
if (data.type === "stream" && isStream) {
updateLastMessage({ str: data.message });
}
}
function connectWS(): void {
try {
const urlWs = getWebSocketUrl(
flow.id,
process.env.NODE_ENV === "development"
);
const newWs = new WebSocket(urlWs);
newWs.onopen = () => {
console.log("WebSocket connection established!");
};
newWs.onmessage = (event) => {
const data = JSON.parse(event.data);
handleWsMessage(data);
//get chat history
};
newWs.onclose = (event) => {
handleOnClose(event);
};
newWs.onerror = (ev) => {
console.log(ev);
connectWS();
};
ws.current = newWs;
} catch (error) {
if (flow.id === "") {
connectWS();
}
console.log(error);
}
}
useEffect(() => {
connectWS();
return () => {
console.log(ws);
if (ws.current) {
ws.current.close();
}
};
// do not add connectWS on dependencies array
}, [open]);
useEffect(() => {
return () => {
if (ws.current) {
console.log("closing ws");
ws.current.close();
}
};
}, []);
useEffect(() => {
if (
ws.current &&
(ws.current.readyState === ws.current.CLOSED ||
ws.current.readyState === ws.current.CLOSING)
) {
connectWS();
setLockChat(false);
}
// do not add connectWS on dependencies array
}, [lockChat]);
async function sendAll(data: sendAllProps): Promise<void> {
try {
if (ws) {
ws.current?.send(JSON.stringify(data));
}
} catch (error) {
setErrorData({
title: MSG_ERROR_ALERT,
list: [(error as { message: string }).message],
});
setChatValue(data.inputs);
connectWS();
}
}
useEffect(() => {
if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" });
}, [chatHistory]);
const ref = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (open && ref.current) {
ref.current.focus();
}
}, [open]);
function sendMessage(): void {
let nodeValidationErrors = validateNodes(nodes, edges);
const errors = nodeValidationErrors.flatMap((error) => error.errors);
if (errors.length === 0) {
setLockChat(true);
let inputs = flowState?.input_keys;
setChatValue("");
const message = inputs;
addChatHistory(message!, true, chatKey!, template.current);
sendAll({
...flow.data!,
inputs: inputs!,
chatHistory,
name: flow.name,
description: flow.description,
chatKey: chatKey!,
});
if (flowState && chatKey) {
setFlowState((old: FlowState | undefined) => {
let newFlowState = cloneDeep(old!);
newFlowState.input_keys![chatKey] = "";
return newFlowState;
});
}
} else {
setErrorData({
title: INFO_MISSING_ALERT,
list: errors,
});
const ids = nodeValidationErrors.map((error) => error.id);
updateBuildStatus(ids, BuildStatus.ERROR);
}
}
function clearChat(): void {
setChatHistory([]);
template.current = flowState?.template;
ws.current?.send(JSON.stringify({ clear_history: true }));
if (lockChat) setLockChat(false);
}
function handleOnCheckedChange(checked: boolean, i: string) {
if (checked === true) {
setChatKey(i);
setChatValue(flowState?.input_keys![i] ?? "");
} else {
setChatKey(null!);
setChatValue("");
}
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger hidden></DialogTrigger>
{flowState && flowState && (
<DialogContent className="min-w-[80vw]">
<DialogHeader>
<DialogTitle className="flex items-center">
<span className="pr-2">Chat</span>
<IconComponent
name="prompts"
className="h-6 w-6 pl-1 text-foreground"
aria-hidden="true"
/>
</DialogTitle>
<DialogDescription>{CHAT_FORM_DIALOG_SUBTITLE}</DialogDescription>
</DialogHeader>
<div className="flex-max-width mt-2 h-[80vh]">
<div className="form-modal-iv-size">
<div className="file-component-arrangement">
<IconComponent
name="Variable"
className=" file-component-variable"
/>
<span className="file-component-variables-span text-md">
Input Variables
</span>
</div>
<div className="file-component-variables-title">
<div className="file-component-variables-div">
<span className="text-sm font-medium text-primary">Name</span>
</div>
<div className="file-component-variables-div">
<span className="text-sm font-medium text-primary">
Chat Input
</span>
</div>
</div>
{flowState?.input_keys
? Object.keys(flowState?.input_keys!).map((key, index) => (
<div className="file-component-accordion-div" key={index}>
<AccordionComponent
trigger={
<div className="file-component-badge-div">
<Badge variant="gray" size="md">
{key}
</Badge>
<div
className="-mb-1"
onClick={(event) => {
event.stopPropagation();
}}
>
<ToggleShadComponent
enabled={chatKey === key}
setEnabled={(value) =>
handleOnCheckedChange(value, key)
}
size="small"
disabled={flowState.handle_keys!.some(
(t) => t === key
)}
/>
</div>
</div>
}
key={index}
keyValue={key}
>
<div className="file-component-tab-column">
{flowState?.handle_keys!.some((t) => t === key) && (
<div className="font-normal text-muted-foreground ">
Source: Component
</div>
)}
<Textarea
className="custom-scroll"
value={flowState?.input_keys![key]}
onChange={(e) => {
if (flowState) {
setFlowState((old: FlowState | undefined) => {
let newFlowState = cloneDeep(old!);
newFlowState.input_keys![key] =
e.target.value;
return newFlowState;
});
}
}}
disabled={chatKey === key}
placeholder="Enter text..."
></Textarea>
</div>
</AccordionComponent>
</div>
))
: null}
{flowState?.memory_keys!.map((key, index) => (
<div className="file-component-accordion-div" key={index}>
<AccordionComponent
trigger={
<div className="file-component-badge-div">
<Badge variant="gray" size="md">
{key}
</Badge>
<div className="-mb-1">
<ToggleShadComponent
enabled={chatKey === key}
setEnabled={() => {}}
size="small"
disabled={true}
/>
</div>
</div>
}
key={index}
keyValue={key}
>
<div className="file-component-tab-column">
<div className="font-normal text-muted-foreground ">
Source: Memory
</div>
</div>
</AccordionComponent>
</div>
))}
</div>
<div className="eraser-column-arrangement">
<div className="eraser-size">
<div className="eraser-position">
<button disabled={lockChat} onClick={() => clearChat()}>
<IconComponent
name="Eraser"
className={classNames(
"h-5 w-5",
lockChat
? "animate-pulse text-primary"
: "text-primary hover:text-gray-600"
)}
aria-hidden="true"
/>
</button>
</div>
<div ref={messagesRef} className="chat-message-div">
{chatHistory.length > 0 ? (
chatHistory.map((chat, index) => (
<ChatMessage
lockChat={lockChat}
chat={chat}
lastMessage={
chatHistory.length - 1 === index ? true : false
}
key={index}
updateChat={() => {}}
/>
))
) : (
<div className="chat-alert-box">
<span>
👋{" "}
<span className="langflow-chat-span">
{LANGFLOW_CHAT_TITLE}
</span>
</span>
<br />
<div className="langflow-chat-desc">
<span className="langflow-chat-desc-span">
{CHAT_FIRST_INITIAL_TEXT}{" "}
<span>
<IconComponent
name="MessageSquare"
className="mx-1 inline h-5 w-5 animate-bounce "
/>
</span>{" "}
{CHAT_SECOND_INITIAL_TEXT}
</span>
</div>
</div>
)}
<div ref={ref}></div>
</div>
<div className="langflow-chat-input-div">
<div className="langflow-chat-input">
<ChatInput
chatValue={chatValue}
noInput={!chatKey}
lockChat={lockChat}
sendMessage={sendMessage}
setChatValue={(value) => {
setChatValue(value);
if (flowState && chatKey) {
setFlowState((old: FlowState | undefined) => {
let newFlowState = cloneDeep(old!);
newFlowState.input_keys![chatKey] = value;
return newFlowState;
});
}
}}
inputRef={ref}
/>
</div>
</div>
</div>
</div>
</div>
</DialogContent>
)}
</Dialog>
);
}

View file

@ -141,6 +141,7 @@ export default function ShareModal({
});
});
};
console.log("ShareModal");
const handleUpdateComponent = () => {
handleShareComponent(true);

View file

@ -38,7 +38,6 @@ import {
import { getRandomName, isWrappedWithClass } from "../../../../utils/utils";
import ConnectionLineComponent from "../ConnectionLineComponent";
import SelectionMenu from "../SelectionMenuComponent";
import ExtraSidebar from "../extraSidebarComponent";
const nodeTypes = {
genericNode: GenericNode,
@ -59,6 +58,8 @@ export default function Page({
const templates = useTypesStore((state) => state.templates);
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
const reactFlowWrapper = useRef<HTMLDivElement>(null);
const [showCanvas, setSHowCanvas] = useState(Object.keys(templates).length > 0 &&
Object.keys(types).length > 0)
const reactFlowInstance = useFlowStore((state) => state.reactFlowInstance);
const setReactFlowInstance = useFlowStore(
@ -271,6 +272,10 @@ export default function Page({
};
}, []);
useEffect(() => {
setSHowCanvas(Object.keys(templates).length > 0 && Object.keys(types).length > 0)
}, [templates, types])
const onConnectMod = useCallback(
(params: Connection) => {
takeSnapshot();
@ -432,71 +437,62 @@ export default function Page({
}
return (
<div className="flex h-full overflow-hidden">
{!view && <ExtraSidebar />}
{/* Main area */}
<main className="flex flex-1">
{/* Primary column */}
<div className="h-full w-full">
<div className="h-full w-full" ref={reactFlowWrapper}>
{Object.keys(templates).length > 0 &&
Object.keys(types).length > 0 ? (
<div id="react-flow-id" className="h-full w-full">
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnectMod}
disableKeyboardA11y={true}
onInit={setReactFlowInstance}
nodeTypes={nodeTypes}
onEdgeUpdate={onEdgeUpdate}
onEdgeUpdateStart={onEdgeUpdateStart}
onEdgeUpdateEnd={onEdgeUpdateEnd}
onNodeDragStart={onNodeDragStart}
onNodeDragStop={onNodeDragStop}
onSelectionDragStart={onSelectionDragStart}
onSelectionEnd={onSelectionEnd}
onSelectionStart={onSelectionStart}
connectionLineComponent={ConnectionLineComponent}
onDragOver={onDragOver}
onMoveEnd={onMoveEnd}
onDrop={onDrop}
onSelectionChange={onSelectionChange}
deleteKeyCode={[]}
className="theme-attribution"
minZoom={0.01}
maxZoom={8}
zoomOnScroll={!view}
zoomOnPinch={!view}
panOnDrag={!view}
proOptions={{ hideAttribution: true }}
onPaneClick={onPaneClick}
>
<Background className="" />
{!view && (
<Controls
className="bg-muted fill-foreground stroke-foreground text-primary
<div className="h-full w-full" ref={reactFlowWrapper}>
{showCanvas ? (
<div id="react-flow-id" className="h-full w-full">
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnectMod}
disableKeyboardA11y={true}
onInit={setReactFlowInstance}
nodeTypes={nodeTypes}
onEdgeUpdate={onEdgeUpdate}
onEdgeUpdateStart={onEdgeUpdateStart}
onEdgeUpdateEnd={onEdgeUpdateEnd}
onNodeDragStart={onNodeDragStart}
onNodeDragStop={onNodeDragStop}
onSelectionDragStart={onSelectionDragStart}
onSelectionEnd={onSelectionEnd}
onSelectionStart={onSelectionStart}
connectionLineComponent={ConnectionLineComponent}
onDragOver={onDragOver}
onMoveEnd={onMoveEnd}
onDrop={onDrop}
onSelectionChange={onSelectionChange}
deleteKeyCode={[]}
className="theme-attribution"
minZoom={0.01}
maxZoom={8}
zoomOnScroll={!view}
zoomOnPinch={!view}
panOnDrag={!view}
proOptions={{ hideAttribution: true }}
onPaneClick={onPaneClick}
>
<Background className="" />
{!view && (
<Controls
className="bg-muted fill-foreground stroke-foreground text-primary
[&>button]:border-b-border hover:[&>button]:bg-border"
></Controls>
)}
<SelectionMenu
isVisible={selectionMenuVisible}
nodes={lastSelection?.nodes}
onClick={() => {
handleGroupNode();
}}
/>
</ReactFlow>
{!view && <FlowToolbar flow={flow} />}
</div>
) : (
<></>
></Controls>
)}
</div>
<SelectionMenu
isVisible={selectionMenuVisible}
nodes={lastSelection?.nodes}
onClick={() => {
handleGroupNode();
}}
/>
</ReactFlow>
</div>
</main>
) : (
<></>
)}
</div>
);
}

View file

@ -1,6 +1,7 @@
import { cloneDeep } from "lodash";
import { LinkIcon, SparklesIcon } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import AccordionComponent from "../../../../components/AccordionComponent";
import ShadTooltip from "../../../../components/ShadTooltipComponent";
import IconComponent from "../../../../components/genericIconComponent";
import { Input } from "../../../../components/ui/input";
@ -234,6 +235,20 @@ export default function ExtraSidebar(): JSX.Element {
[]
);
const getIcon = useMemo(() => {
return (SBSectionName: string) => {
if (nodeIconsLucide[SBSectionName]) {
return (
<IconComponent
name={SBSectionName}
strokeWidth={1.5}
className="w-[22px] text-primary"
/>
);
}
};
}, []);
return (
<div className="side-bar-arrangement">
<div className="side-bar-search-div-placement">
@ -310,7 +325,7 @@ export default function ExtraSidebar(): JSX.Element {
<SidebarDraggableComponent
sectionName={SBSectionName as string}
apiClass={dataFilter[SBSectionName][SBItemName]}
key={index}
key={index+ SBItemName}
onDragStart={(event) =>
onDragStart(event, {
//split type to remove type in nodes saved with same name removing it's

View file

@ -35,7 +35,6 @@ import ToolbarSelectItem from "./toolbarSelectItem";
export default function NodeToolbarComponent({
data,
deleteNode,
position,
setShowNode,
numberOfHandles,
showNode,
@ -79,6 +78,7 @@ export default function NodeToolbarComponent({
const setEdges = useFlowStore((state) => state.setEdges);
const unselectAll = useFlowStore((state) => state.unselectAll);
const saveComponent = useFlowsManagerStore((state) => state.saveComponent);
const getNodePosition = useFlowStore((state) => state.getNodePosition);
const flows = useFlowsManagerStore((state) => state.flows);
const version = useDarkStore((state) => state.version);
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
@ -146,7 +146,7 @@ export default function NodeToolbarComponent({
takeSnapshot();
expandGroupNode(
data.id,
updateFlowPosition(position, data.node?.flow!),
updateFlowPosition(getNodePosition(data.id), data.node?.flow!),
data.node!.template,
nodes,
edges,
@ -631,7 +631,7 @@ export default function NodeToolbarComponent({
)}
{hasCode && (
<div className="hidden">
<CodeAreaComponent
{openModal&& <CodeAreaComponent
open={openModal}
setOpen={setOpenModal}
readonly={
@ -646,7 +646,7 @@ export default function NodeToolbarComponent({
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
id={"code-input-node-toolbar-" + name}
/>
/>}
</div>
)}
</span>

View file

@ -4,8 +4,10 @@ import Header from "../../components/headerComponent";
import { useDarkStore } from "../../stores/darkStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import Page from "./components/PageComponent";
import ExtraSidebar from "./components/extraSidebarComponent";
import FlowToolbar from "../../components/chatComponent";
export default function FlowPage(): JSX.Element {
export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId
);
@ -17,12 +19,23 @@ export default function FlowPage(): JSX.Element {
useEffect(() => {
setCurrentFlowId(id!);
}, [id]);
return (
<>
<Header />
<div className="flow-page-positioning">
{currentFlow && <Page flow={currentFlow} />}
{currentFlow &&
<div className="flex h-full overflow-hidden">
{!view && <ExtraSidebar />}
<main className="flex flex-1">
{/* Primary column */}
<div className="h-full w-full">
<Page flow={currentFlow} />
</div>
{!view && <FlowToolbar />}
</main>
</div>
}
<a
target={"_blank"}
href="https://logspace.ai/"

View file

@ -2,12 +2,12 @@ import { Group, ToyBrick } from "lucide-react";
import { useEffect, useState } from "react";
import { Outlet, useLocation, useNavigate } from "react-router-dom";
import DropdownButton from "../../components/DropdownButtonComponent";
import NewFlowCardComponent from "../../components/NewFLowCard2";
import NewFlowCardComponent from "../../modals/NewFlowModal/components/NewFlowCardComponent";
import IconComponent from "../../components/genericIconComponent";
import PageLayout from "../../components/pageLayout";
import SidebarNav from "../../components/sidebarComponent";
import { Button } from "../../components/ui/button";
import UndrawCardComponent from "../../components/undrawCards";
import UndrawCardComponent from "../../modals/NewFlowModal/components/undrawCards";
import { CONSOLE_ERROR_MSG } from "../../constants/alerts_constants";
import {
MY_COLLECTION_DESC,
@ -17,8 +17,8 @@ import BaseModal from "../../modals/baseModal";
import useAlertStore from "../../stores/alertStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { downloadFlows } from "../../utils/reactflowUtils";
import NewFlowModal from "../../modals/NewFlowModal";
export default function HomePage(): JSX.Element {
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId
@ -29,7 +29,6 @@ export default function HomePage(): JSX.Element {
const location = useLocation();
const pathname = location.pathname;
const [openModal, setOpenModal] = useState(false);
const examples = useFlowsManagerStore((state) => state.examples);
const is_component = pathname === "/components";
const dropdownOptions = [
{
@ -119,26 +118,7 @@ export default function HomePage(): JSX.Element {
<Outlet />
</div>
</div>
<BaseModal size="three-cards" open={openModal} setOpen={setOpenModal}>
<BaseModal.Header description={"Select a template below"}>
<span className="pr-2" data-testid="modal-title">
Get Started
</span>
{/* <IconComponent
name="Group"
className="h-6 w-6 stroke-2 text-primary "
aria-hidden="true"
/> */}
</BaseModal.Header>
<BaseModal.Content>
<div className=" grid h-full w-full grid-cols-3 gap-3 overflow-auto p-4 custom-scroll">
<NewFlowCardComponent />
{examples.map((example, idx) => {
return <UndrawCardComponent key={idx} flow={example} />;
})}
</div>
</BaseModal.Content>
</BaseModal>
<NewFlowModal open={openModal} setOpen={setOpenModal} />
</PageLayout>
);
}

View file

@ -10,7 +10,6 @@ export default function DeleteAccountPage() {
// Implement your account deletion logic here
// For example, make an API call to delete the account
// Upon successful deletion, you can redirect the user to another page
console.log("Account deleted!");
// Implement the logic to redirect the user after account deletion.
// For example, use react-router-dom's useHistory hook.
};

View file

@ -70,6 +70,10 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
}
get().setFlowPool(newFlowPool);
},
getNodePosition: (nodeId: string) => {
const node = get().nodes.find((node) => node.id === nodeId);
return node?.position||{x:0,y:0};
},
updateFlowPool: (
nodeId: string,
data: FlowPoolObjectType | ChatOutputType | chatInputType,

View file

@ -56,7 +56,7 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
setFlows: (flows: FlowType[]) => {
set({
flows,
currentFlow: flows.find((flow) => flow.id === get().currentFlowId),
// currentFlow: flows.find((flow) => flow.id === get().currentFlowId),
});
},
currentFlow: undefined,

View file

@ -187,7 +187,7 @@
@apply flex w-full select-none items-center justify-between border-y border-y-input bg-background px-3 py-2;
}
.components-disclosure-arrangement {
@apply -mt-px flex w-full select-none items-center justify-between border-y border-y-input bg-muted px-3 py-2;
@apply -mt-px flex w-full select-none items-center justify-between border-y border-t-2 border-y-input bg-muted px-3 py-2;
}
.components-disclosure-arrangement-child {
/* different color than the non child */

View file

@ -1,5 +1,6 @@
import { ReactElement, ReactNode, SetStateAction } from "react";
import { ReactFlowJsonObject, XYPosition } from "reactflow";
import { InputOutput } from "../../constants/enums";
import { APIClassType, APITemplateType, TemplateVariableType } from "../api";
import { ChatMessageType } from "../chat";
import { FlowStyleType, FlowType, NodeDataType, NodeType } from "../flow/index";
@ -217,6 +218,8 @@ export type AccordionComponentType = {
open?: string[];
trigger?: string | ReactElement;
keyValue?: string;
openDisc?: boolean;
sideBar?: boolean;
};
export type Side = "top" | "right" | "bottom" | "left";
@ -498,7 +501,6 @@ export type fileCardPropsType = {
export type nodeToolbarPropsType = {
data: NodeDataType;
deleteNode: (idx: string) => void;
position: XYPosition;
setShowNode: (boolean: any) => void;
numberOfHandles: number;
showNode: boolean;
@ -563,12 +565,6 @@ export type chatMessagePropsType = {
) => void;
};
export type formModalPropsType = {
open: boolean;
setOpen: Function;
flow: FlowType;
};
export type genericModalPropsType = {
field_name?: string;
setValue: (value: string) => void;
@ -583,6 +579,18 @@ export type genericModalPropsType = {
readonly?: boolean;
};
export type newFlowModalPropsType = {
open: boolean;
setOpen: (open: boolean) => void;
};
export type IOModalPropsType = {
children: JSX.Element;
open: boolean;
setOpen: (open: boolean) => void;
disable?: boolean;
};
export type buttonBoxPropsType = {
onClick: () => void;
title: string;
@ -693,15 +701,21 @@ export type dropdownButtonPropsType = {
dropdownOptions?: boolean;
};
export type IOInputProps = {
inputType: string;
inputId: string;
export type IOFieldViewProps = {
type: InputOutput;
fieldType: string;
fieldId: string;
left?: boolean;
};
export type IOOutputProps = {
outputType: string;
outputId: string;
left?: boolean;
export type UndrawCardComponentProps = { flow: FlowType };
export type chatViewProps = {
sendMessage: (count?: number) => void;
chatValue: string;
setChatValue: (value: string) => void;
lockChat: boolean;
setLockChat: (lock: boolean) => void;
};
export type IOFileInputProps = {

View file

@ -131,4 +131,5 @@ export type FlowStoreType = {
data: FlowPoolObjectType | ChatOutputType | chatInputType,
buildId?: string
) => void;
getNodePosition: (nodeId: string) => { x: number; y: number };
};

View file

@ -1,6 +1,7 @@
import react from "@vitejs/plugin-react-swc";
import { defineConfig } from "vite";
import svgr from "vite-plugin-svgr";
import MillionCompiler from "@million/lint";
const apiRoutes = ["^/api/v1/", "/health"];
// Use environment variable to determine the target.