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:
parent
e564991040
commit
e61896bc1f
71 changed files with 3496 additions and 3399 deletions
4
poetry.lock
generated
4
poetry.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)}",
|
||||
|
|
|
|||
30
src/backend/base/langflow/components/inputs/JSONInput.py
Normal file
30
src/backend/base/langflow/components/inputs/JSONInput.py
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
0
src/backend/langflow/base/agents/__init__.py
Normal file
0
src/backend/langflow/base/agents/__init__.py
Normal file
70
src/backend/langflow/base/agents/agent.py
Normal file
70
src/backend/langflow/base/agents/agent.py
Normal 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")
|
||||
3
src/backend/langflow/base/models/__init__.py
Normal file
3
src/backend/langflow/base/models/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from .model import LCModelComponent
|
||||
|
||||
__all__ = ["LCModelComponent"]
|
||||
48
src/backend/langflow/base/models/model.py
Normal file
48
src/backend/langflow/base/models/model.py
Normal 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
|
||||
0
src/backend/langflow/components/models/base/__init__.py
Normal file
0
src/backend/langflow/components/models/base/__init__.py
Normal file
37
src/backend/langflow/components/tools/SearchAPITool.py
Normal file
37
src/backend/langflow/components/tools/SearchAPITool.py
Normal 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
|
||||
0
src/backend/langflow/components/tools/SearchApi.py
Normal file
0
src/backend/langflow/components/tools/SearchApi.py
Normal file
103
src/backend/langflow/services/deps.py
Normal file
103
src/backend/langflow/services/deps.py
Normal 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
|
||||
83
src/backend/langflow/services/factory.py
Normal file
83
src/backend/langflow/services/factory.py
Normal 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
|
||||
32
src/frontend/.eslintrc.json
Normal file
32
src/frontend/.eslintrc.json
Normal 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"
|
||||
}
|
||||
}
|
||||
4010
src/frontend/package-lock.json
generated
4010
src/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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],
|
||||
},
|
||||
}));
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
export default function LoadingSpinner({}) {
|
||||
return <></>;
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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`}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -14,3 +14,8 @@ export enum BuildStatus {
|
|||
INACTIVE = "INACTIVE",
|
||||
ERROR = "ERROR",
|
||||
}
|
||||
|
||||
export enum InputOutput {
|
||||
INPUT = "input",
|
||||
OUTPUT = "output",
|
||||
}
|
||||
|
|
@ -83,7 +83,7 @@ export function AuthProvider({ children }): React.ReactElement {
|
|||
useFlowsManagerStore.setState({ isLoading: false });
|
||||
}
|
||||
});
|
||||
}, [setUserData, setLoading, autoLogin, setIsAdmin]);
|
||||
}, [autoLogin]);
|
||||
|
||||
function getUser() {
|
||||
getLoggedUser()
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
109
src/frontend/src/modals/IOModal/components/IOFieldView/index.tsx
Normal file
109
src/frontend/src/modals/IOModal/components/IOFieldView/index.tsx
Normal 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();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
@ -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">
|
||||
|
|
@ -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,
|
||||
|
|
@ -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);
|
||||
|
|
@ -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}
|
||||
|
|
@ -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);
|
||||
|
|
@ -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();
|
||||
|
||||
32
src/frontend/src/modals/NewFlowModal/index.tsx
Normal file
32
src/frontend/src/modals/NewFlowModal/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -141,6 +141,7 @@ export default function ShareModal({
|
|||
});
|
||||
});
|
||||
};
|
||||
console.log("ShareModal");
|
||||
|
||||
const handleUpdateComponent = () => {
|
||||
handleShareComponent(true);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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/"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -131,4 +131,5 @@ export type FlowStoreType = {
|
|||
data: FlowPoolObjectType | ChatOutputType | chatInputType,
|
||||
buildId?: string
|
||||
) => void;
|
||||
getNodePosition: (nodeId: string) => { x: number; y: number };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue