merge branch chatImg

This commit is contained in:
cristhianzl 2024-06-07 12:11:40 -03:00
commit 9ce7ee31a9
60 changed files with 1056 additions and 511 deletions

View file

@ -168,9 +168,9 @@ async def build_vertex(
next_runnable_vertices, next_runnable_vertices,
top_level_vertices, top_level_vertices,
result_dict, result_dict,
log_message, params,
valid, valid,
log_type, artifacts,
vertex, vertex,
) = await graph.build_vertex( ) = await graph.build_vertex(
lock=lock, lock=lock,
@ -180,22 +180,22 @@ async def build_vertex(
inputs_dict=inputs.model_dump() if inputs else {}, inputs_dict=inputs.model_dump() if inputs else {},
files=files, files=files,
) )
log_obj = Log(message=vertex.artifacts_raw, type=vertex.artifacts_type)
result_data_response = ResultDataResponse(**result_dict.model_dump()) result_data_response = ResultDataResponse(**result_dict.model_dump())
except Exception as exc: except Exception as exc:
logger.exception(f"Error building vertex: {exc}") logger.exception(f"Error building vertex: {exc}")
log_message = format_exception_message(exc) params = format_exception_message(exc)
log_type = type(exc).__name__
valid = False valid = False
log_obj = Log(message=params, type="error")
result_data_response = ResultDataResponse(results={}) result_data_response = ResultDataResponse(results={})
log_object = Log(message=log_message, type=log_type) artifacts = {}
# If there's an error building the vertex # If there's an error building the vertex
# we need to clear the cache # we need to clear the cache
await chat_service.clear_cache(flow_id_str) await chat_service.clear_cache(flow_id_str)
result_data_response.logs.append(log_object) result_data_response.message = artifacts
result_data_response.logs.append(log_obj)
# Log the vertex build # Log the vertex build
if not vertex.will_stream: if not vertex.will_stream:
@ -204,8 +204,9 @@ async def build_vertex(
flow_id=flow_id_str, flow_id=flow_id_str,
vertex_id=vertex_id, vertex_id=vertex_id,
valid=valid, valid=valid,
logs=result_data_response.logs, params=params,
data=result_data_response, data=result_data_response,
artifacts=artifacts,
) )
timedelta = time.perf_counter() - start_time timedelta = time.perf_counter() - start_time
@ -231,6 +232,7 @@ async def build_vertex(
next_vertices_ids=next_runnable_vertices, next_vertices_ids=next_runnable_vertices,
top_level_vertices=top_level_vertices, top_level_vertices=top_level_vertices,
valid=valid, valid=valid,
params=params,
id=vertex.id, id=vertex.id,
data=result_data_response, data=result_data_response,
) )

View file

@ -2,6 +2,7 @@ from datetime import datetime, timezone
from enum import Enum from enum import Enum
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List, Optional, Union from typing import Any, Dict, List, Optional, Union
from typing_extensions import TypedDict
from uuid import UUID from uuid import UUID
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_serializer from pydantic import BaseModel, ConfigDict, Field, field_validator, model_serializer
@ -244,10 +245,16 @@ class VerticesOrderResponse(BaseModel):
vertices_to_run: List[str] vertices_to_run: List[str]
class Log(TypedDict):
message: Union[dict, str]
type: str
class ResultDataResponse(BaseModel): class ResultDataResponse(BaseModel):
results: Optional[Any] = Field(default_factory=dict) results: Optional[Any] = Field(default_factory=dict)
logs: List[Log | None] = Field(default_factory=list) logs: List[Log | None] = Field(default_factory=list)
messages: List[ChatOutputResponse | None] = Field(default_factory=list) message: Optional[Any] = Field(default_factory=dict)
artifacts: Optional[Any] = Field(default_factory=dict)
timedelta: Optional[float] = None timedelta: Optional[float] = None
duration: Optional[str] = None duration: Optional[str] = None
used_frozen_result: Optional[bool] = False used_frozen_result: Optional[bool] = False
@ -259,6 +266,8 @@ class VertexBuildResponse(BaseModel):
next_vertices_ids: Optional[List[str]] = None next_vertices_ids: Optional[List[str]] = None
top_level_vertices: Optional[List[str]] = None top_level_vertices: Optional[List[str]] = None
valid: bool valid: bool
params: Optional[Any] = Field(default_factory=dict)
"""JSON string of the params."""
data: ResultDataResponse data: ResultDataResponse
"""Mapping of vertex ids to result dict containing the param name and result value.""" """Mapping of vertex ids to result dict containing the param name and result value."""
timestamp: Optional[datetime] = Field(default_factory=lambda: datetime.now(timezone.utc)) timestamp: Optional[datetime] = Field(default_factory=lambda: datetime.now(timezone.utc))

View file

@ -1,10 +1,13 @@
import warnings
from typing import Optional, Union from typing import Optional, Union
from langchain_core.language_models.chat_models import BaseChatModel from langchain_core.language_models.chat_models import BaseChatModel
from langchain_core.language_models.llms import LLM from langchain_core.language_models.llms import LLM
from langchain_core.load import load
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
from langflow.custom import CustomComponent from langflow.custom import CustomComponent
from langflow.schema.schema import Record
class LCModelComponent(CustomComponent): class LCModelComponent(CustomComponent):
@ -82,7 +85,7 @@ class LCModelComponent(CustomComponent):
return status_message return status_message
def get_chat_result( def get_chat_result(
self, runnable: BaseChatModel, stream: bool, input_value: str, system_message: Optional[str] = None self, runnable: BaseChatModel, stream: bool, input_value: str | Record, system_message: Optional[str] = None
): ):
messages: list[Union[HumanMessage, SystemMessage]] = [] messages: list[Union[HumanMessage, SystemMessage]] = []
if not input_value and not system_message: if not input_value and not system_message:
@ -90,6 +93,15 @@ class LCModelComponent(CustomComponent):
if system_message: if system_message:
messages.append(SystemMessage(content=system_message)) messages.append(SystemMessage(content=system_message))
if input_value: if input_value:
if isinstance(input_value, Record):
with warnings.catch_warnings():
warnings.simplefilter("ignore")
if "prompt" in input_value:
prompt = load(input_value.prompt)
runnable = prompt | runnable
else:
messages.append(input_value.to_lc_message())
else:
messages.append(HumanMessage(content=input_value)) messages.append(HumanMessage(content=input_value))
if stream: if stream:
return runnable.stream(messages) return runnable.stream(messages)

View file

@ -1,9 +1,10 @@
import base64
from copy import deepcopy from copy import deepcopy
from langchain_core.documents import Document from langchain_core.documents import Document
from langflow.schema import Record from langflow.schema import Record
from langflow.services.deps import get_storage_service
def record_to_string(record: Record) -> str: def record_to_string(record: Record) -> str:
@ -19,7 +20,7 @@ def record_to_string(record: Record) -> str:
return record.get_text() return record.get_text()
def dict_values_to_string(d: dict) -> dict: async def dict_values_to_string(d: dict) -> dict:
""" """
Converts the values of a dictionary to strings. Converts the values of a dictionary to strings.
@ -36,16 +37,43 @@ def dict_values_to_string(d: dict) -> dict:
if isinstance(value, list): if isinstance(value, list):
for i, item in enumerate(value): for i, item in enumerate(value):
if isinstance(item, Record): if isinstance(item, Record):
d_copy[key][i] = record_to_string(item) d_copy[key][i] = item.to_lc_message()
elif isinstance(item, Document): elif isinstance(item, Document):
d_copy[key][i] = document_to_string(item) d_copy[key][i] = document_to_string(item)
elif isinstance(value, Record): elif isinstance(value, Record):
d_copy[key] = record_to_string(value) if "files" in value and value.files:
files = await get_file_paths(value.files)
value.files = files
d_copy[key] = value.to_lc_message()
elif isinstance(value, Document): elif isinstance(value, Document):
d_copy[key] = document_to_string(value) d_copy[key] = document_to_string(value)
return d_copy return d_copy
async def get_file_paths(files: list[str]):
storage_service = get_storage_service()
file_paths = []
for file in files:
flow_id, file_name = file.split("/")
file_paths.append(storage_service.build_full_path(flow_id=flow_id, file_name=file_name))
return file_paths
async def get_files(
file_paths: str,
convert_to_base64: bool = False,
):
storage_service = get_storage_service()
file_objects = []
for file_path in file_paths:
flow_id, file_name = file_path.split("/")
file_object = await storage_service.get_file(flow_id=flow_id, file_name=file_name)
if convert_to_base64:
file_object = base64.b64encode(file_object).decode("utf-8")
file_objects.append(file_object)
return file_objects
def document_to_string(document: Document) -> str: def document_to_string(document: Document) -> str:
""" """
Convert a document to a string. Convert a document to a string.

View file

@ -1,7 +1,9 @@
from langchain_core.prompts import PromptTemplate from langchain_core.prompts import ChatPromptTemplate
from langflow.base.prompts.utils import dict_values_to_string
from langflow.custom import CustomComponent from langflow.custom import CustomComponent
from langflow.field_typing import Prompt, TemplateField, Text from langflow.field_typing import Prompt, TemplateField, Text
from langflow.schema.schema import Record
class PromptComponent(CustomComponent): class PromptComponent(CustomComponent):
@ -15,19 +17,14 @@ class PromptComponent(CustomComponent):
"code": TemplateField(advanced=True), "code": TemplateField(advanced=True),
} }
def build( async def build(
self, self,
template: Prompt, template: Prompt,
**kwargs, **kwargs,
) -> Text: ) -> Record:
from langflow.base.prompts.utils import dict_values_to_string prompt_template = ChatPromptTemplate.from_template(Text(template))
kwargs = await dict_values_to_string(kwargs)
prompt_template = PromptTemplate.from_template(Text(template)) messages = list(kwargs.values())
kwargs = dict_values_to_string(kwargs) prompt = prompt_template + messages
kwargs = {k: "\n".join(v) if isinstance(v, list) else v for k, v in kwargs.items()} self.status = f'Prompt:\n"{template}"'
try: return Record(data={"prompt": prompt.to_json()})
formated_prompt = prompt_template.format(**kwargs)
except Exception as exc:
raise ValueError(f"Error formatting prompt: {exc}") from exc
self.status = f'Prompt:\n"{formated_prompt}"'
return formated_prompt

View file

@ -738,7 +738,9 @@ class Graph:
# Check the cache for the vertex # Check the cache for the vertex
cached_result = await chat_service.get_cache(key=vertex.id) cached_result = await chat_service.get_cache(key=vertex.id)
if isinstance(cached_result, CacheMiss): if isinstance(cached_result, CacheMiss):
await vertex.build(user_id=user_id, inputs=inputs_dict, fallback_to_env_vars=fallback_to_env_vars) await vertex.build(
user_id=user_id, inputs=inputs_dict, fallback_to_env_vars=fallback_to_env_vars, files=files
)
await chat_service.set_cache(key=vertex.id, data=vertex) await chat_service.set_cache(key=vertex.id, data=vertex)
else: else:
cached_vertex = cached_result["result"] cached_vertex = cached_result["result"]
@ -752,7 +754,9 @@ class Graph:
vertex.result.used_frozen_result = True vertex.result.used_frozen_result = True
else: else:
await vertex.build(user_id=user_id, inputs=inputs_dict, fallback_to_env_vars=fallback_to_env_vars) await vertex.build(
user_id=user_id, inputs=inputs_dict, fallback_to_env_vars=fallback_to_env_vars, files=files
)
if vertex.result is not None: if vertex.result is not None:
params = f"{vertex._built_object_repr()}{params}" params = f"{vertex._built_object_repr()}{params}"

View file

@ -2,6 +2,7 @@ from enum import Enum
from typing import Any, Generator, Union from typing import Any, Generator, Union
from langchain_core.documents import Document from langchain_core.documents import Document
from langflow.schema.schema import Record
from pydantic import BaseModel from pydantic import BaseModel
from langflow.interface.utils import extract_input_variables_from_prompt from langflow.interface.utils import extract_input_variables_from_prompt

View file

@ -628,9 +628,8 @@ class Vertex:
self._built_object, self.artifacts = result self._built_object, self.artifacts = result
elif len(result) == 3: elif len(result) == 3:
self._custom_component, self._built_object, self.artifacts = result self._custom_component, self._built_object, self.artifacts = result
self.artifacts_raw = self.artifacts.get("raw") self.artifacts_raw = self.artifacts.pop("raw", None)
self.artifacts_type = self.artifacts.get("type") or ArtifactType.UNKNOWN.value self.artifacts_type = self.artifacts.pop("type", None) or ArtifactType.UNKNOWN.value
else: else:
self._built_object = result self._built_object = result

View file

@ -111,12 +111,7 @@ class InterfaceVertex(Vertex):
message = self._built_object message = self._built_object
artifact_type = ArtifactType.STREAM if stream_url is not None else ArtifactType.OBJECT artifact_type = ArtifactType.STREAM if stream_url is not None else ArtifactType.OBJECT
artifacts = ChatOutputResponse( artifacts = ChatOutputResponse(
message=message, message=message, sender=sender, sender_name=sender_name, stream_url=stream_url, files=files
sender=sender,
sender_name=sender_name,
stream_url=stream_url,
files=files,
type=artifact_type.value,
) )
self.will_stream = stream_url is not None self.will_stream = stream_url is not None
@ -213,9 +208,9 @@ class InterfaceVertex(Vertex):
flow_id=self.graph.flow_id, flow_id=self.graph.flow_id,
vertex_id=self.id, vertex_id=self.id,
valid=True, valid=True,
logs=self._built_object_repr(), params=self._built_object_repr(),
data=self.result, data=self.result,
messages=self.artifacts, artifacts=self.artifacts,
) )
self._validate_built_object() self._validate_built_object()

View file

@ -20,7 +20,7 @@
"list": false, "list": false,
"show": true, "show": true,
"multiline": 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 = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\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 ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n",
"fileTypes": [], "fileTypes": [],
"file_path": "", "file_path": "",
"password": false, "password": false,

View file

@ -20,7 +20,7 @@
"list": false, "list": false,
"show": true, "show": true,
"multiline": 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 = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\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 ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n",
"fileTypes": [], "fileTypes": [],
"file_path": "", "file_path": "",
"password": false, "password": false,

View file

@ -20,7 +20,7 @@
"list": false, "list": false,
"show": true, "show": true,
"multiline": 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 = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\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 ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n",
"fileTypes": [], "fileTypes": [],
"file_path": "", "file_path": "",
"password": false, "password": false,

View file

@ -524,7 +524,7 @@
"list": false, "list": false,
"show": true, "show": true,
"multiline": 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 = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\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 ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n",
"fileTypes": [], "fileTypes": [],
"file_path": "", "file_path": "",
"password": false, "password": false,

View file

@ -20,7 +20,7 @@
"list": false, "list": false,
"show": true, "show": true,
"multiline": 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 = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\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 ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n",
"fileTypes": [], "fileTypes": [],
"file_path": "", "file_path": "",
"password": false, "password": false,
@ -130,7 +130,7 @@
"list": false, "list": false,
"show": true, "show": true,
"multiline": 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 = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\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 ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n",
"fileTypes": [], "fileTypes": [],
"file_path": "", "file_path": "",
"password": false, "password": false,

View file

@ -1034,7 +1034,7 @@
"list": false, "list": false,
"show": true, "show": true,
"multiline": 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 = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\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 ChatPromptTemplate\n\nfrom langflow.base.prompts.utils import dict_values_to_string\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\nfrom langflow.schema.schema import Record\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"Create a prompt template with dynamic variables.\"\n icon = \"prompts\"\n\n def build_config(self):\n return {\n \"template\": TemplateField(display_name=\"Template\"),\n \"code\": TemplateField(advanced=True),\n }\n\n async def build(\n self,\n template: Prompt,\n **kwargs,\n ) -> Record:\n prompt_template = ChatPromptTemplate.from_template(Text(template))\n kwargs = await dict_values_to_string(kwargs)\n messages = list(kwargs.values())\n prompt = prompt_template + messages\n self.status = f'Prompt:\\n\"{template}\"'\n return Record(data={\"prompt\": prompt.to_json()})\n",
"fileTypes": [], "fileTypes": [],
"file_path": "", "file_path": "",
"password": false, "password": false,

View file

@ -125,7 +125,6 @@ async def instantiate_custom_component(params, user_id, vertex, fallback_to_env_
custom_repr = build_result custom_repr = build_result
if not isinstance(custom_repr, str): if not isinstance(custom_repr, str):
custom_repr = str(custom_repr) custom_repr = str(custom_repr)
raw = custom_component.repr_value raw = custom_component.repr_value
if hasattr(raw, "data"): if hasattr(raw, "data"):
raw = raw.data raw = raw.data

View file

@ -1,11 +1,12 @@
import copy import copy
import json import json
from typing import Literal, Optional, cast from typing import Literal, Optional, cast
from typing_extensions import TypedDict
from langchain_core.documents import Document from langchain_core.documents import Document
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage from langchain_core.messages import AIMessage, BaseMessage, HumanMessage
from pydantic import BaseModel, model_validator from langchain_core.prompts.image import ImagePromptTemplate
from typing_extensions import TypedDict from pydantic import BaseModel, model_serializer, model_validator
class Record(BaseModel): class Record(BaseModel):
@ -30,6 +31,11 @@ class Record(BaseModel):
values["data"][key] = values[key] values["data"][key] = values[key]
return values return values
@model_serializer(mode="plain", when_used="json")
def serialize_model(self):
data = {k: v.to_json() if hasattr(v, "to_json") else v for k, v in self.data.items()}
return data
def get_text(self): def get_text(self):
""" """
Retrieves the text value from the data dictionary. Retrieves the text value from the data dictionary.
@ -103,7 +109,9 @@ class Record(BaseModel):
text = self.data.pop(self.text_key, self.default_value) text = self.data.pop(self.text_key, self.default_value)
return Document(page_content=text, metadata=self.data) return Document(page_content=text, metadata=self.data)
def to_lc_message(self) -> BaseMessage: def to_lc_message(
self,
) -> BaseMessage:
""" """
Converts the Record to a BaseMessage. Converts the Record to a BaseMessage.
@ -119,8 +127,22 @@ class Record(BaseModel):
raise ValueError(f"Missing required keys ('text', 'sender') in Record: {self.data}") raise ValueError(f"Missing required keys ('text', 'sender') in Record: {self.data}")
sender = self.data.get("sender", "Machine") sender = self.data.get("sender", "Machine")
text = self.data.get("text", "") text = self.data.get("text", "")
files = self.data.get("files", [])
if sender == "User": if sender == "User":
return HumanMessage(content=text) if files:
contents = [{"type": "text", "text": text}]
for file_path in files:
image_template = ImagePromptTemplate()
image_prompt_value = image_template.invoke(input={"path": file_path})
contents.append({"type": "image_url", "image_url": image_prompt_value.image_url})
human_message = HumanMessage(content=contents)
else:
human_message = HumanMessage(
content=[{"type": "text", "text": text}],
)
return human_message
return AIMessage(content=text) return AIMessage(content=text)
def __getattr__(self, key): def __getattr__(self, key):
@ -170,8 +192,14 @@ class Record(BaseModel):
def __str__(self) -> str: def __str__(self) -> str:
# return a JSON string representation of the Record atributes # return a JSON string representation of the Record atributes
try:
data = {k: v.to_json() if hasattr(v, "to_json") else v for k, v in self.data.items()}
return json.dumps(data, indent=4)
except Exception:
return str(self.data)
return json.dumps(self.data) def __contains__(self, key):
return key in self.data
INPUT_FIELD_NAME = "input_value" INPUT_FIELD_NAME = "input_value"

View file

@ -23,6 +23,9 @@ class CacheMiss:
def __repr__(self): def __repr__(self):
return "<CACHE_MISS>" return "<CACHE_MISS>"
def __bool__(self):
return False
def create_cache_folder(func): def create_cache_folder(func):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):

View file

@ -76,6 +76,7 @@ class MessageModel(BaseModel):
session_id: str session_id: str
message: str message: str
files: list[str] = [] files: list[str] = []
artifacts: dict
class Config: class Config:
from_attributes = True from_attributes = True
@ -87,6 +88,12 @@ class MessageModel(BaseModel):
return json.loads(v) return json.loads(v)
return v return v
@field_validator("artifacts", mode="before")
def validate_target_args(cls, v):
if isinstance(v, str):
return json.loads(v)
return v
@classmethod @classmethod
def from_record(cls, record: "Record", flow_id: Optional[str] = None): def from_record(cls, record: "Record", flow_id: Optional[str] = None):
# first check if the record has all the required fields # first check if the record has all the required fields
@ -107,6 +114,12 @@ class MessageModel(BaseModel):
class MessageModelResponse(MessageModel): class MessageModelResponse(MessageModel):
index: Optional[int] = Field(default=None) index: Optional[int] = Field(default=None)
@field_validator("artifacts", mode="before")
def serialize_artifacts(v):
if isinstance(v, str):
return json.loads(v)
return v
@field_validator("index", mode="before") @field_validator("index", mode="before")
def validate_id(cls, v): def validate_id(cls, v):
if isinstance(v, float): if isinstance(v, float):
@ -129,15 +142,16 @@ class VertexBuildModel(BaseModel):
id: Optional[str] = Field(default=None, alias="id") id: Optional[str] = Field(default=None, alias="id")
flow_id: str flow_id: str
valid: bool valid: bool
logs: Any params: Any
data: dict data: dict
artifacts: dict
timestamp: datetime = Field(default_factory=datetime.now) timestamp: datetime = Field(default_factory=datetime.now)
class Config: class Config:
from_attributes = True from_attributes = True
populate_by_name = True populate_by_name = True
@field_serializer("data") @field_serializer("data", "artifacts")
def serialize_dict(v): def serialize_dict(v):
if isinstance(v, dict): if isinstance(v, dict):
# check if the value of each key is a BaseModel or a list of BaseModels # check if the value of each key is a BaseModel or a list of BaseModels
@ -151,8 +165,8 @@ class VertexBuildModel(BaseModel):
return v.model_dump_json() return v.model_dump_json()
return v return v
@field_validator("logs", mode="before") @field_validator("params", mode="before")
def validate_logs(cls, v): def validate_params(cls, v):
if isinstance(v, str): if isinstance(v, str):
try: try:
return json.loads(v) return json.loads(v)
@ -160,7 +174,7 @@ class VertexBuildModel(BaseModel):
return v return v
return v return v
@field_serializer("logs") @field_serializer("params")
def serialize_params(v): def serialize_params(v):
if isinstance(v, list) and all(isinstance(i, BaseModel) for i in v): if isinstance(v, list) and all(isinstance(i, BaseModel) for i in v):
return json.dumps([i.model_dump() for i in v]) return json.dumps([i.model_dump() for i in v])
@ -172,11 +186,17 @@ class VertexBuildModel(BaseModel):
return json.loads(v) return json.loads(v)
return v return v
@field_validator("artifacts", mode="before")
def validate_artifacts(cls, v):
if isinstance(v, str):
return json.loads(v)
elif isinstance(v, BaseModel):
return v.model_dump()
return v
class VertexBuildResponseModel(VertexBuildModel): class VertexBuildResponseModel(VertexBuildModel):
messages: list[MessageModel] = [] @field_serializer("data", "artifacts")
@field_serializer("data")
def serialize_dict(v): def serialize_dict(v):
return v return v

View file

@ -147,9 +147,9 @@ async def log_vertex_build(
flow_id: str, flow_id: str,
vertex_id: str, vertex_id: str,
valid: bool, valid: bool,
logs: Any, params: Any,
data: "ResultDataResponse", data: "ResultDataResponse",
messages: Optional[dict] = None, artifacts: Optional[dict] = None,
): ):
try: try:
monitor_service = get_monitor_service() monitor_service = get_monitor_service()
@ -158,9 +158,9 @@ async def log_vertex_build(
"flow_id": flow_id, "flow_id": flow_id,
"id": vertex_id, "id": vertex_id,
"valid": valid, "valid": valid,
"logs": logs, "params": params,
"data": data.model_dump(), "data": data.model_dump(),
"messages": messages or {}, "artifacts": artifacts or {},
"timestamp": monitor_service.get_timestamp(), "timestamp": monitor_service.get_timestamp(),
} }
monitor_service.add_row(table_name="vertex_builds", data=row) monitor_service.add_row(table_name="vertex_builds", data=row)

View file

@ -90,9 +90,9 @@ async def build_vertex(
flow_id=flow_id, flow_id=flow_id,
vertex_id=vertex_id, vertex_id=vertex_id,
valid=valid, valid=valid,
logs=params, params=params,
data=result_dict, data=result_dict,
messages=artifacts, artifacts=artifacts,
) )
# Emit the vertex build response # Emit the vertex build response

View file

@ -55,10 +55,10 @@ export default function GenericNode({
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const isDark = useDarkStore((state) => state.dark); const isDark = useDarkStore((state) => state.dark);
const buildStatus = useFlowStore( const buildStatus = useFlowStore(
(state) => state.flowBuildStatus[data.id]?.status, (state) => state.flowBuildStatus[data.id]?.status
); );
const lastRunTime = useFlowStore( const lastRunTime = useFlowStore(
(state) => state.flowBuildStatus[data.id]?.timestamp, (state) => state.flowBuildStatus[data.id]?.timestamp
); );
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot); const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
@ -66,7 +66,7 @@ export default function GenericNode({
const [nodeName, setNodeName] = useState(data.node!.display_name); const [nodeName, setNodeName] = useState(data.node!.display_name);
const [inputDescription, setInputDescription] = useState(false); const [inputDescription, setInputDescription] = useState(false);
const [nodeDescription, setNodeDescription] = useState( const [nodeDescription, setNodeDescription] = useState(
data.node?.description!, data.node?.description!
); );
const [isOutdated, setIsOutdated] = useState(false); const [isOutdated, setIsOutdated] = useState(false);
const [validationStatus, setValidationStatus] = const [validationStatus, setValidationStatus] =
@ -84,7 +84,7 @@ export default function GenericNode({
data.node!, data.node!,
setNode, setNode,
setIsOutdated, setIsOutdated,
updateNodeInternals, updateNodeInternals
); );
const name = nodeIconsLucide[data.type] ? data.type : types[data.type]; const name = nodeIconsLucide[data.type] ? data.type : types[data.type];
@ -115,12 +115,12 @@ export default function GenericNode({
selected: boolean, selected: boolean,
showNode: boolean, showNode: boolean,
buildStatus: BuildStatus | undefined, buildStatus: BuildStatus | undefined,
validationStatus: VertexBuildTypeAPI | null, validationStatus: VertexBuildTypeAPI | null
) => { ) => {
const specificClassFromBuildStatus = getSpecificClassFromBuildStatus( const specificClassFromBuildStatus = getSpecificClassFromBuildStatus(
buildStatus, buildStatus,
validationStatus, validationStatus,
isDark, isDark
); );
const baseBorderClass = getBaseBorderClass(selected); const baseBorderClass = getBaseBorderClass(selected);
@ -129,7 +129,7 @@ export default function GenericNode({
baseBorderClass, baseBorderClass,
nodeSizeClass, nodeSizeClass,
"generic-node-div group/node", "generic-node-div group/node",
specificClassFromBuildStatus, specificClassFromBuildStatus
); );
return names; return names;
}; };
@ -170,7 +170,7 @@ export default function GenericNode({
showNode, showNode,
isEmoji, isEmoji,
nodeIconFragment, nodeIconFragment,
checkNodeIconFragment, checkNodeIconFragment
); );
function countHandles(): void { function countHandles(): void {
@ -247,7 +247,7 @@ export default function GenericNode({
selected, selected,
showNode, showNode,
buildStatus, buildStatus,
validationStatus, validationStatus
)} )}
> >
{data.node?.beta && showNode && ( {data.node?.beta && showNode && (
@ -378,7 +378,7 @@ export default function GenericNode({
} }
title={getFieldTitle( title={getFieldTitle(
data.node?.template!, data.node?.template!,
templateField, templateField
)} )}
info={data.node?.template[templateField].info} info={data.node?.template[templateField].info}
name={templateField} name={templateField}
@ -406,7 +406,7 @@ export default function GenericNode({
proxy={data.node?.template[templateField].proxy} proxy={data.node?.template[templateField].proxy}
showNode={showNode} showNode={showNode}
/> />
), )
)} )}
<ParameterComponent <ParameterComponent
key={scapedJSONStringfy({ key={scapedJSONStringfy({
@ -552,7 +552,7 @@ export default function GenericNode({
!data.node?.description) && !data.node?.description) &&
nameEditable nameEditable
? "font-light italic" ? "font-light italic"
: "", : ""
)} )}
onClick={(e) => { onClick={(e) => {
setInputDescription(true); setInputDescription(true);
@ -614,13 +614,13 @@ export default function GenericNode({
} }
title={getFieldTitle( title={getFieldTitle(
data.node?.template!, data.node?.template!,
templateField, templateField
)} )}
info={data.node?.template[templateField].info} info={data.node?.template[templateField].info}
name={templateField} name={templateField}
tooltipTitle={ tooltipTitle={
data.node?.template[templateField].input_types?.join( data.node?.template[templateField].input_types?.join(
"\n", "\n"
) ?? data.node?.template[templateField].type ) ?? data.node?.template[templateField].type
} }
required={data.node!.template[templateField].required} required={data.node!.template[templateField].required}
@ -647,7 +647,7 @@ export default function GenericNode({
<div <div
className={classNames( className={classNames(
Object.keys(data.node!.template).length < 1 ? "hidden" : "", Object.keys(data.node!.template).length < 1 ? "hidden" : "",
"flex-max-width justify-center", "flex-max-width justify-center"
)} )}
> >
{" "} {" "}

View file

@ -32,7 +32,7 @@ const SideBarFoldersButtonsComponent = ({
const [foldersNames, setFoldersNames] = useState({}); const [foldersNames, setFoldersNames] = useState({});
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot); const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
const [editFolders, setEditFolderName] = useState( const [editFolders, setEditFolderName] = useState(
folders.map((obj) => ({ name: obj.name, edit: false })), folders.map((obj) => ({ name: obj.name, edit: false }))
); );
const uploadFolder = useFolderStore((state) => state.uploadFolder); const uploadFolder = useFolderStore((state) => state.uploadFolder);
const currentFolder = pathname.split("/"); const currentFolder = pathname.split("/");
@ -59,7 +59,7 @@ const SideBarFoldersButtonsComponent = ({
const { dragOver, dragEnter, dragLeave, onDrop } = useFileDrop( const { dragOver, dragEnter, dragLeave, onDrop } = useFileDrop(
folderId, folderId,
handleFolderChange, handleFolderChange
); );
const handleUploadFlowsToFolder = () => { const handleUploadFlowsToFolder = () => {
@ -87,7 +87,7 @@ const SideBarFoldersButtonsComponent = ({
addFolder({ name: "New Folder", parent_id: null, description: "" }).then( addFolder({ name: "New Folder", parent_id: null, description: "" }).then(
(res) => { (res) => {
getFoldersApi(true); getFoldersApi(true);
}, }
); );
} }
@ -133,7 +133,7 @@ const SideBarFoldersButtonsComponent = ({
<> <>
{folders.map((item, index) => { {folders.map((item, index) => {
const editFolderName = editFolders?.filter( const editFolderName = editFolders?.filter(
(folder) => folder.name === item.name, (folder) => folder.name === item.name
)[0]; )[0];
return ( return (
<div <div
@ -149,7 +149,7 @@ const SideBarFoldersButtonsComponent = ({
? "border border-border bg-muted hover:bg-muted" ? "border border-border bg-muted hover:bg-muted"
: "border hover:bg-transparent lg:border-transparent lg:hover:border-border", : "border hover:bg-transparent lg:border-transparent lg:hover:border-border",
"group flex w-full shrink-0 cursor-pointer gap-2 opacity-100 lg:min-w-full", "group flex w-full shrink-0 cursor-pointer gap-2 opacity-100 lg:min-w-full",
folderIdDragging === item.id! ? "bg-border" : "", folderIdDragging === item.id! ? "bg-border" : ""
)} )}
onClick={() => handleChangeFolder!(item.id!)} onClick={() => handleChangeFolder!(item.id!)}
> >
@ -219,7 +219,7 @@ const SideBarFoldersButtonsComponent = ({
folders.map((obj) => ({ folders.map((obj) => ({
name: obj.name, name: obj.name,
edit: false, edit: false,
})), }))
); );
} }
if (e.key === "Enter") { if (e.key === "Enter") {
@ -252,10 +252,10 @@ const SideBarFoldersButtonsComponent = ({
}; };
const updatedFolder = await updateFolder( const updatedFolder = await updateFolder(
body, body,
item.id!, item.id!
); );
const updateFolders = folders.filter( const updateFolders = folders.filter(
(f) => f.name !== item.name, (f) => f.name !== item.name
); );
setFolders([...updateFolders, updatedFolder]); setFolders([...updateFolders, updatedFolder]);
setFoldersNames({}); setFoldersNames({});
@ -263,7 +263,7 @@ const SideBarFoldersButtonsComponent = ({
folders.map((obj) => ({ folders.map((obj) => ({
name: obj.name, name: obj.name,
edit: false, edit: false,
})), }))
); );
} else { } else {
setFoldersNames((old) => ({ setFoldersNames((old) => ({

View file

@ -63,8 +63,8 @@ export default function TableNodeCellRender({
...id, ...id,
proxy: templateData.proxy, proxy: templateData.proxy,
} }
: id, : id
), )
) ?? false; ) ?? false;
function getCellType() { function getCellType() {
switch (templateData.type) { switch (templateData.type) {
@ -135,7 +135,7 @@ export default function TableNodeCellRender({
<div <div
className={classNames( className={classNames(
"max-h-48 w-full overflow-auto custom-scroll", "max-h-48 w-full overflow-auto custom-scroll",
templateValue?.length > 1 ? "my-3" : "", templateValue?.length > 1 ? "my-3" : ""
)} )}
> >
<KeypairListComponent <KeypairListComponent

View file

@ -13,6 +13,7 @@ import ForwardedIconComponent from "../genericIconComponent";
import { Alert, AlertDescription, AlertTitle } from "../ui/alert"; import { Alert, AlertDescription, AlertTitle } from "../ui/alert";
import ResetColumns from "./components/ResetColumns"; import ResetColumns from "./components/ResetColumns";
import resetGrid from "./utils/reset-grid-columns"; import resetGrid from "./utils/reset-grid-columns";
import { useParams } from "react-router-dom";
interface TableComponentProps extends AgGridReactProps { interface TableComponentProps extends AgGridReactProps {
columnDefs: NonNullable<AgGridReactProps["columnDefs"]>; columnDefs: NonNullable<AgGridReactProps["columnDefs"]>;

View file

@ -62,7 +62,7 @@ export async function sendAll(data: sendAllProps) {
} }
export async function postValidateCode( export async function postValidateCode(
code: string, code: string
): Promise<AxiosResponse<errorsTypeAPI>> { ): Promise<AxiosResponse<errorsTypeAPI>> {
return await api.post(`${BASE_URL_API}validate/code`, { code }); return await api.post(`${BASE_URL_API}validate/code`, { code });
} }
@ -77,7 +77,7 @@ export async function postValidateCode(
export async function postValidatePrompt( export async function postValidatePrompt(
name: string, name: string,
template: string, template: string,
frontend_node: APIClassType, frontend_node: APIClassType
): Promise<AxiosResponse<PromptTypeAPI>> { ): Promise<AxiosResponse<PromptTypeAPI>> {
return api.post(`${BASE_URL_API}validate/prompt`, { return api.post(`${BASE_URL_API}validate/prompt`, {
name, name,
@ -150,7 +150,7 @@ export async function saveFlowToDatabase(newFlow: {
* @throws Will throw an error if the update fails. * @throws Will throw an error if the update fails.
*/ */
export async function updateFlowInDatabase( export async function updateFlowInDatabase(
updatedFlow: FlowType, updatedFlow: FlowType
): Promise<FlowType> { ): Promise<FlowType> {
try { try {
const response = await api.patch(`${BASE_URL_API}flows/${updatedFlow.id}`, { const response = await api.patch(`${BASE_URL_API}flows/${updatedFlow.id}`, {
@ -328,7 +328,7 @@ export async function getHealth() {
* *
*/ */
export async function getBuildStatus( export async function getBuildStatus(
flowId: string, flowId: string
): Promise<AxiosResponse<BuildStatusTypeAPI>> { ): Promise<AxiosResponse<BuildStatusTypeAPI>> {
return await api.get(`${BASE_URL_API}build/${flowId}/status`); return await api.get(`${BASE_URL_API}build/${flowId}/status`);
} }
@ -341,7 +341,7 @@ export async function getBuildStatus(
* *
*/ */
export async function postBuildInit( export async function postBuildInit(
flow: FlowType, flow: FlowType
): Promise<AxiosResponse<InitTypeAPI>> { ): Promise<AxiosResponse<InitTypeAPI>> {
return await api.post(`${BASE_URL_API}build/init/${flow.id}`, flow); return await api.post(`${BASE_URL_API}build/init/${flow.id}`, flow);
} }
@ -357,7 +357,7 @@ export async function postBuildInit(
*/ */
export async function uploadFile( export async function uploadFile(
file: File, file: File,
id: string, id: string
): Promise<AxiosResponse<UploadFileTypeAPI>> { ): Promise<AxiosResponse<UploadFileTypeAPI>> {
const formData = new FormData(); const formData = new FormData();
formData.append("file", file); formData.append("file", file);
@ -366,7 +366,7 @@ export async function uploadFile(
export async function postCustomComponent( export async function postCustomComponent(
code: string, code: string,
apiClass: APIClassType, apiClass: APIClassType
): Promise<AxiosResponse<APIClassType>> { ): Promise<AxiosResponse<APIClassType>> {
// let template = apiClass.template; // let template = apiClass.template;
return await api.post(`${BASE_URL_API}custom_component`, { return await api.post(`${BASE_URL_API}custom_component`, {
@ -379,7 +379,7 @@ export async function postCustomComponentUpdate(
code: string, code: string,
template: APITemplateType, template: APITemplateType,
field: string, field: string,
field_value: any, field_value: any
): Promise<AxiosResponse<APIClassType>> { ): Promise<AxiosResponse<APIClassType>> {
return await api.post(`${BASE_URL_API}custom_component/update`, { return await api.post(`${BASE_URL_API}custom_component/update`, {
code, code,
@ -401,7 +401,7 @@ export async function onLogin(user: LoginType) {
headers: { headers: {
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
}, },
}, }
); );
if (response.status === 200) { if (response.status === 200) {
@ -463,11 +463,11 @@ export async function addUser(user: UserInputType): Promise<Array<Users>> {
export async function getUsersPage( export async function getUsersPage(
skip: number, skip: number,
limit: number, limit: number
): Promise<Array<Users>> { ): Promise<Array<Users>> {
try { try {
const res = await api.get( const res = await api.get(
`${BASE_URL_API}users/?skip=${skip}&limit=${limit}`, `${BASE_URL_API}users/?skip=${skip}&limit=${limit}`
); );
if (res.status === 200) { if (res.status === 200) {
return res.data; return res.data;
@ -504,7 +504,7 @@ export async function resetPassword(user_id: string, user: resetPasswordType) {
try { try {
const res = await api.patch( const res = await api.patch(
`${BASE_URL_API}users/${user_id}/reset-password`, `${BASE_URL_API}users/${user_id}/reset-password`,
user, user
); );
if (res.status === 200) { if (res.status === 200) {
return res.data; return res.data;
@ -578,7 +578,7 @@ export async function saveFlowStore(
last_tested_version?: string; last_tested_version?: string;
}, },
tags: string[], tags: string[],
publicFlow = false, publicFlow = false
): Promise<FlowType> { ): Promise<FlowType> {
try { try {
const response = await api.post(`${BASE_URL_API}store/components/`, { const response = await api.post(`${BASE_URL_API}store/components/`, {
@ -707,7 +707,7 @@ export async function postStoreComponents(component: Component) {
export async function getComponent(component_id: string) { export async function getComponent(component_id: string) {
try { try {
const res = await api.get( const res = await api.get(
`${BASE_URL_API}store/components/${component_id}`, `${BASE_URL_API}store/components/${component_id}`
); );
if (res.status === 200) { if (res.status === 200) {
return res.data; return res.data;
@ -722,7 +722,7 @@ export async function searchComponent(
page?: number | null, page?: number | null,
limit?: number | null, limit?: number | null,
status?: string | null, status?: string | null,
tags?: string[], tags?: string[]
): Promise<StoreComponentResponse | undefined> { ): Promise<StoreComponentResponse | undefined> {
try { try {
let url = `${BASE_URL_API}store/components/`; let url = `${BASE_URL_API}store/components/`;
@ -834,7 +834,7 @@ export async function updateFlowStore(
}, },
tags: string[], tags: string[],
publicFlow = false, publicFlow = false,
id: string, id: string
): Promise<FlowType> { ): Promise<FlowType> {
try { try {
const response = await api.patch(`${BASE_URL_API}store/components/${id}`, { const response = await api.patch(`${BASE_URL_API}store/components/${id}`, {
@ -918,7 +918,7 @@ export async function deleteGlobalVariable(id: string) {
export async function updateGlobalVariable( export async function updateGlobalVariable(
name: string, name: string,
value: string, value: string,
id: string, id: string
) { ) {
try { try {
const response = api.patch(`${BASE_URL_API}variables/${id}`, { const response = api.patch(`${BASE_URL_API}variables/${id}`, {
@ -937,7 +937,7 @@ export async function getVerticesOrder(
startNodeId?: string | null, startNodeId?: string | null,
stopNodeId?: string | null, stopNodeId?: string | null,
nodes?: Node[], nodes?: Node[],
Edges?: Edge[], Edges?: Edge[]
): Promise<AxiosResponse<VerticesOrderTypeAPI>> { ): Promise<AxiosResponse<VerticesOrderTypeAPI>> {
// nodeId is optional and is a query parameter // nodeId is optional and is a query parameter
// if nodeId is not provided, the API will return all vertices // if nodeId is not provided, the API will return all vertices
@ -957,7 +957,7 @@ export async function getVerticesOrder(
return await api.post( return await api.post(
`${BASE_URL_API}build/${flowId}/vertices`, `${BASE_URL_API}build/${flowId}/vertices`,
data, data,
config, config
); );
} }
@ -965,18 +965,16 @@ export async function postBuildVertex(
flowId: string, flowId: string,
vertexId: string, vertexId: string,
input_value: string, input_value: string,
files?: string[], files?: string[]
): Promise<AxiosResponse<VertexBuildTypeAPI>> { ): Promise<AxiosResponse<VertexBuildTypeAPI>> {
// input_value is optional and is a query parameter // input_value is optional and is a query parameter
const data = input_value const data = { inputs: { input_value: input_value ?? "" } };
? { inputs: { input_value: input_value } }
: undefined;
if (data && files) { if (data && files) {
data["files"] = files; data["files"] = files;
} }
return await api.post( return await api.post(
`${BASE_URL_API}build/${flowId}/vertices/${vertexId}`, `${BASE_URL_API}build/${flowId}/vertices/${vertexId}`,
data, data
); );
} }
@ -1000,7 +998,7 @@ export async function getFlowPool({
} }
export async function deleteFlowPool( export async function deleteFlowPool(
flowId: string, flowId: string
): Promise<AxiosResponse<any>> { ): Promise<AxiosResponse<any>> {
const config = {}; const config = {};
config["params"] = { flow_id: flowId }; config["params"] = { flow_id: flowId };
@ -1014,7 +1012,7 @@ export async function deleteFlowPool(
* @returns A promise that resolves to an array of AxiosResponse objects representing the delete responses. * @returns A promise that resolves to an array of AxiosResponse objects representing the delete responses.
*/ */
export async function multipleDeleteFlowsComponents( export async function multipleDeleteFlowsComponents(
flowIds: string[], flowIds: string[]
): Promise<AxiosResponse<any>[]> { ): Promise<AxiosResponse<any>[]> {
const batches: string[][] = []; const batches: string[][] = [];
@ -1037,7 +1035,7 @@ export async function multipleDeleteFlowsComponents(
// Execute all delete requests // Execute all delete requests
const responses: Promise<AxiosResponse<any>>[] = batches.map((batch) => const responses: Promise<AxiosResponse<any>>[] = batches.map((batch) =>
deleteBatch(batch), deleteBatch(batch)
); );
// Return the responses after all requests are completed // Return the responses after all requests are completed
@ -1047,7 +1045,7 @@ export async function multipleDeleteFlowsComponents(
export async function getTransactionTable( export async function getTransactionTable(
id: string, id: string,
mode: "intersection" | "union", mode: "intersection" | "union",
params = {}, params = {}
): Promise<{ rows: Array<object>; columns: Array<ColDef | ColGroupDef> }> { ): Promise<{ rows: Array<object>; columns: Array<ColDef | ColGroupDef> }> {
const config = {}; const config = {};
config["params"] = { flow_id: id }; config["params"] = { flow_id: id };
@ -1063,7 +1061,7 @@ export async function getMessagesTable(
mode: "intersection" | "union", mode: "intersection" | "union",
id?: string, id?: string,
excludedFields?: string[], excludedFields?: string[],
params = {}, params = {}
): Promise<{ rows: Array<Message>; columns: Array<ColDef | ColGroupDef> }> { ): Promise<{ rows: Array<Message>; columns: Array<ColDef | ColGroupDef> }> {
const config = {}; const config = {};
if (id) { if (id) {

View file

@ -0,0 +1,52 @@
import IconComponent from "../../../../../../../components/genericIconComponent";
import { Case } from "../../../../../../../shared/components/caseComponent";
import { classNames } from "../../../../../../../utils/utils";
const ButtonSendWrapper = ({
send,
lockChat,
noInput,
saveLoading,
chatValue,
}) => {
return (
<button
className={classNames(
"form-modal-send-button",
noInput
? "bg-high-indigo text-background"
: chatValue === ""
? "text-primary"
: "bg-chat-send text-background",
)}
disabled={lockChat || saveLoading}
onClick={(): void => send()}
>
<Case condition={lockChat || saveLoading}>
<IconComponent
name="Lock"
className="form-modal-lock-icon"
aria-hidden="true"
/>
</Case>
<Case condition={noInput}>
<IconComponent
name="Zap"
className="form-modal-play-icon"
aria-hidden="true"
/>
</Case>
<Case condition={!(lockChat || saveLoading) && !noInput}>
<IconComponent
name="LucideSend"
className="form-modal-send-icon "
aria-hidden="true"
/>
</Case>
</button>
);
};
export default ButtonSendWrapper;

View file

@ -0,0 +1,81 @@
import { Textarea } from "../../../../../../../components/ui/textarea";
import { classNames } from "../../../../../../../utils/utils";
const TextAreaWrapper = ({
checkSendingOk,
send,
lockChat,
noInput,
saveLoading,
chatValue,
setChatValue,
CHAT_INPUT_PLACEHOLDER,
CHAT_INPUT_PLACEHOLDER_SEND,
inputRef,
setInputFocus,
files,
isDragging,
}) => {
const getPlaceholderText = (
isDragging: boolean,
noInput: boolean,
): string => {
if (isDragging) {
return "Drop here";
} else if (noInput) {
return CHAT_INPUT_PLACEHOLDER;
} else {
return CHAT_INPUT_PLACEHOLDER_SEND;
}
};
const lockClass =
lockChat || saveLoading
? "form-modal-lock-true bg-input"
: noInput
? "form-modal-no-input bg-input"
: "form-modal-lock-false bg-background";
const fileClass =
files.length > 0
? "rounded-b-lg ring-0 focus:ring-0 focus:border-2 rounded-t-none border-t-0 border-border focus:border-t-0 focus:border-ring"
: "rounded-md border-t border-border focus:ring-0 focus:border-2 focus:border-ring";
const additionalClassNames = "form-modal-lockchat pl-14";
return (
<Textarea
onFocus={(e) => {
setInputFocus(true);
e.target.style.borderTopWidth = "0";
}}
onBlur={() => setInputFocus(false)}
onKeyDown={(event) => {
if (checkSendingOk(event)) {
send();
}
}}
rows={1}
ref={inputRef}
disabled={lockChat || noInput || saveLoading}
style={{
resize: "none",
bottom: `${inputRef?.current?.scrollHeight}px`,
maxHeight: "150px",
overflow: `${
inputRef.current && inputRef.current.scrollHeight > 150
? "auto"
: "hidden"
}`,
}}
value={lockChat ? "Thinking..." : saveLoading ? "Saving..." : chatValue}
onChange={(event): void => {
setChatValue(event.target.value);
}}
className={classNames(lockClass, fileClass, additionalClassNames)}
placeholder={getPlaceholderText(isDragging, noInput)}
/>
);
};
export default TextAreaWrapper;

View file

@ -0,0 +1,29 @@
import ForwardedIconComponent from "../../../../../../../components/genericIconComponent";
import { Button } from "../../../../../../../components/ui/button";
const UploadFileButton = ({
fileInputRef,
handleFileChange,
handleButtonClick,
}) => {
return (
<div>
<input
type="file"
ref={fileInputRef}
style={{ display: "none" }}
onChange={handleFileChange}
/>
<Button
className="font-bold text-white transition-all hover:text-muted-foreground"
onClick={handleButtonClick}
variant="none"
size="none"
>
<ForwardedIconComponent name="PaperclipIcon" />
</Button>
</div>
);
};
export default UploadFileButton;

View file

@ -0,0 +1,7 @@
export const getClassNamesFilePreview = (inputFocus) => {
return `flex w-full items-center gap-4 rounded-t-lg bg-background px-14 py-5 overflow-auto custom-scroll ${
inputFocus
? "border border-b-0 border-ring border-2"
: "border border-b-0 border-border"
}`;
};

View file

@ -0,0 +1,14 @@
import { useEffect } from "react";
const useAutoResizeTextArea = (value, inputRef) => {
useEffect(() => {
if (inputRef.current && inputRef.current.scrollHeight! !== 0) {
inputRef.current.style!.height = "inherit"; // Reset the height
inputRef.current.style!.height = `${inputRef.current.scrollHeight!}px`; // Set it to the scrollHeight
}
}, [value]);
return inputRef;
};
export default useAutoResizeTextArea;

View file

@ -0,0 +1,55 @@
import ShortUniqueId from "short-unique-id";
import useFileUpload from "./use-file-upload";
const useDragAndDrop = (setIsDragging, setFiles, currentFlowId) => {
const dragOver = (e) => {
e.preventDefault();
if (e.dataTransfer.types.some((type) => type === "Files")) {
setIsDragging(true);
}
};
const dragEnter = (e) => {
if (e.dataTransfer.types.some((type) => type === "Files")) {
setIsDragging(true);
}
e.preventDefault();
};
const dragLeave = (e) => {
e.preventDefault();
setIsDragging(false);
};
const onDrop = (e) => {
e.preventDefault();
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
handleFiles(e.dataTransfer.files, setFiles, currentFlowId);
e.dataTransfer.clearData();
}
setIsDragging(false);
};
return {
dragOver,
dragEnter,
dragLeave,
onDrop,
};
};
const handleFiles = (files, setFiles, currentFlowId) => {
if (files) {
const uid = new ShortUniqueId({ length: 3 });
const id = uid();
const type = files[0].type.split("/")[0];
const blob = files[0];
setFiles((prevFiles) => [
...prevFiles,
{ file: blob, loading: true, error: false, id, type },
]);
useFileUpload(blob, currentFlowId, setFiles, id);
}
};
export default useDragAndDrop;

View file

@ -0,0 +1,27 @@
import { uploadFile } from "../../../../../../controllers/API";
const useFileUpload = (blob, currentFlowId, setFiles, id) => {
uploadFile(blob, currentFlowId)
.then((res) => {
setFiles((prev) => {
const newFiles = [...prev];
const updatedIndex = newFiles.findIndex((file) => file.id === id);
newFiles[updatedIndex].loading = false;
newFiles[updatedIndex].path = res.data.file_path;
return newFiles;
});
})
.catch(() => {
setFiles((prev) => {
const newFiles = [...prev];
const updatedIndex = newFiles.findIndex((file) => file.id === id);
newFiles[updatedIndex].loading = false;
newFiles[updatedIndex].error = true;
return newFiles;
});
});
return null;
};
export default useFileUpload;

View file

@ -0,0 +1,13 @@
import { useEffect } from "react";
const useFocusOnUnlock = (lockChat, inputRef) => {
useEffect(() => {
if (!lockChat && inputRef.current) {
inputRef.current.focus();
}
}, [lockChat, inputRef]);
return inputRef;
};
export default useFocusOnUnlock;

View file

@ -0,0 +1,33 @@
import ShortUniqueId from "short-unique-id";
import useFileUpload from "./use-file-upload";
export const useHandleFileChange = (setFiles, currentFlowId) => {
const handleFileChange = async (
event: React.ChangeEvent<HTMLInputElement>,
) => {
const fileInput = event.target;
const file = fileInput.files?.[0];
if (file) {
const uid = new ShortUniqueId({ length: 10 }); // Increase the length to ensure uniqueness
const id = uid();
const type = file.type.split("/")[0];
const blob = file;
setFiles((prevFiles) => [
...prevFiles,
{ file: blob, loading: true, error: false, id, type },
]);
useFileUpload(blob, currentFlowId, setFiles, id);
}
// Clear the file input value to ensure the change event is triggered even for the same file
fileInput.value = "";
};
return {
handleFileChange,
};
};
export default useHandleFileChange;

View file

@ -0,0 +1,35 @@
import { useEffect } from "react";
import ShortUniqueId from "short-unique-id";
import useFileUpload from "./use-file-upload";
const useUpload = (uploadFile, currentFlowId, setFiles) => {
useEffect(() => {
const handlePaste = (event: ClipboardEvent): void => {
const items = event.clipboardData?.items;
if (items) {
for (let i = 0; i < items.length; i++) {
const type = items[0].type.split("/")[0];
const uid = new ShortUniqueId({ length: 3 });
const blob = items[i].getAsFile();
if (blob) {
const id = uid();
setFiles((prevFiles) => [
...prevFiles,
{ file: blob, loading: true, error: false, id, type },
]);
useFileUpload(blob, currentFlowId, setFiles, id);
}
}
}
};
document.addEventListener("paste", handlePaste);
return () => {
document.removeEventListener("paste", handlePaste);
};
}, [uploadFile, currentFlowId]);
return null;
};
export default useUpload;

View file

@ -1,7 +1,4 @@
import { useEffect, useState } from "react"; import { useRef, useState } from "react";
import ShortUniqueId from "short-unique-id";
import IconComponent from "../../../../../components/genericIconComponent";
import { Textarea } from "../../../../../components/ui/textarea";
import { import {
CHAT_INPUT_PLACEHOLDER, CHAT_INPUT_PLACEHOLDER,
CHAT_INPUT_PLACEHOLDER_SEND, CHAT_INPUT_PLACEHOLDER_SEND,
@ -9,11 +6,18 @@ import {
import { uploadFile } from "../../../../../controllers/API"; import { uploadFile } from "../../../../../controllers/API";
import useFlowsManagerStore from "../../../../../stores/flowsManagerStore"; import useFlowsManagerStore from "../../../../../stores/flowsManagerStore";
import { import {
ChatInputType,
FilePreviewType, FilePreviewType,
chatInputType,
} from "../../../../../types/components"; } from "../../../../../types/components";
import { classNames } from "../../../../../utils/utils";
import FilePreview from "../filePreviewChat"; import FilePreview from "../filePreviewChat";
import ButtonSendWrapper from "./components/buttonSendWrapper";
import TextAreaWrapper from "./components/textAreaWrapper";
import UploadFileButton from "./components/uploadFileButton";
import { getClassNamesFilePreview } from "./helpers/get-class-file-preview";
import useAutoResizeTextArea from "./hooks/use-auto-resize-text-area";
import useFocusOnUnlock from "./hooks/use-focus-unlock";
import useHandleFileChange from "./hooks/use-handle-file-change";
import useUpload from "./hooks/use-upload";
export default function ChatInput({ export default function ChatInput({
lockChat, lockChat,
chatValue, chatValue,
@ -21,165 +25,83 @@ export default function ChatInput({
setChatValue, setChatValue,
inputRef, inputRef,
noInput, noInput,
}: chatInputType): JSX.Element { files,
setFiles,
isDragging,
}: ChatInputType): JSX.Element {
const [repeat, setRepeat] = useState(1); const [repeat, setRepeat] = useState(1);
const saveLoading = useFlowsManagerStore((state) => state.saveLoading); const saveLoading = useFlowsManagerStore((state) => state.saveLoading);
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId); const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
const uid = new ShortUniqueId({ length: 3 }); const [inputFocus, setInputFocus] = useState<boolean>(false);
const [files, setFiles] = useState<FilePreviewType[]>([]); const fileInputRef = useRef<HTMLInputElement>(null);
useEffect(() => { useFocusOnUnlock(lockChat, inputRef);
if (!lockChat && inputRef.current) { useAutoResizeTextArea(chatValue, inputRef);
inputRef.current.focus(); useUpload(uploadFile, currentFlowId, setFiles);
} const { handleFileChange } = useHandleFileChange(setFiles, currentFlowId);
}, [lockChat, inputRef]);
useEffect(() => { const send = () => {
if (inputRef.current && inputRef.current.scrollHeight !== 0) {
inputRef.current.style.height = "inherit"; // Reset the height
inputRef.current.style.height = `${inputRef.current.scrollHeight}px`; // Set it to the scrollHeight
}
}, [chatValue]);
//listen to ctrl-v to paste images
useEffect(() => {
const handlePaste = (event: ClipboardEvent): void => {
const items = event.clipboardData?.items;
if (items) {
for (let i = 0; i < items.length; i++) {
if (items[i].type.indexOf("image") !== -1) {
const blob = items[i].getAsFile();
if (blob) {
const id = uid();
setFiles([
...files,
{ file: blob, loading: true, error: false, id },
]);
uploadFile(blob, currentFlowId)
.then((res) => {
setFiles((prev) => {
const newFiles = [...prev];
const updatedIndex = newFiles.findIndex(
(file) => file.id === id,
);
newFiles[updatedIndex].loading = false;
newFiles[updatedIndex].path = res.data.file_path;
return newFiles;
});
})
.catch((err) => {
setFiles((prev) => {
const newFiles = [...prev];
const updatedIndex = newFiles.findIndex(
(file) => file.id === id,
);
newFiles[updatedIndex].loading = false;
newFiles[updatedIndex].error = true;
return newFiles;
});
});
}
}
}
}
};
document.addEventListener("paste", handlePaste);
return () => {
document.removeEventListener("paste", handlePaste);
};
}, []);
function send() {
sendMessage({ sendMessage({
repeat, repeat,
files: files.map((file) => file.path ?? "").filter((file) => file !== ""), files: files.map((file) => file.path ?? "").filter((file) => file !== ""),
}); });
setFiles([]); setFiles([]);
} };
const checkSendingOk = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
return ( return (
<div className="flex w-full flex-col-reverse">
<div className="relative w-full">
<Textarea
onKeyDown={(event) => {
if (
event.key === "Enter" && event.key === "Enter" &&
!lockChat && !lockChat &&
!saveLoading && !saveLoading &&
!event.shiftKey && !event.shiftKey &&
!event.nativeEvent.isComposing !event.nativeEvent.isComposing
) { );
send(); };
}
}}
rows={1}
ref={inputRef}
disabled={lockChat || noInput || saveLoading}
style={{
resize: "none",
bottom: `${inputRef?.current?.scrollHeight}px`,
maxHeight: "150px",
overflow: `${
inputRef.current && inputRef.current.scrollHeight > 150
? "auto"
: "hidden"
}`,
}}
value={
lockChat ? "Thinking..." : saveLoading ? "Saving..." : chatValue
}
onChange={(event): void => {
setChatValue(event.target.value);
}}
className={classNames(
lockChat || saveLoading
? " form-modal-lock-true bg-input"
: noInput
? "form-modal-no-input bg-input"
: " form-modal-lock-false bg-background",
"form-modal-lockchat", const classNameFilePreview = getClassNamesFilePreview(inputFocus);
)}
placeholder={ const handleButtonClick = () => {
noInput ? CHAT_INPUT_PLACEHOLDER : CHAT_INPUT_PLACEHOLDER_SEND fileInputRef.current!.click();
} };
return (
<div className="flex w-full flex-col-reverse">
<div className="relative w-full">
<TextAreaWrapper
checkSendingOk={checkSendingOk}
send={send}
lockChat={lockChat}
noInput={noInput}
saveLoading={saveLoading}
chatValue={chatValue}
setChatValue={setChatValue}
CHAT_INPUT_PLACEHOLDER={CHAT_INPUT_PLACEHOLDER}
CHAT_INPUT_PLACEHOLDER_SEND={CHAT_INPUT_PLACEHOLDER_SEND}
inputRef={inputRef}
setInputFocus={setInputFocus}
files={files}
isDragging={isDragging}
/> />
<div className="form-modal-send-icon-position"> <div className="form-modal-send-icon-position">
<button <ButtonSendWrapper
className={classNames( send={send}
"form-modal-send-button", lockChat={lockChat}
noInput noInput={noInput}
? "bg-high-indigo text-background" saveLoading={saveLoading}
: chatValue === "" chatValue={chatValue}
? "text-primary"
: "bg-chat-send text-background",
)}
disabled={lockChat || saveLoading}
onClick={(): void => send()}
>
{lockChat || saveLoading ? (
<IconComponent
name="Lock"
className="form-modal-lock-icon"
aria-hidden="true"
/> />
) : noInput ? ( </div>
<IconComponent
name="Zap" <div className="absolute bottom-2 left-4">
className="form-modal-play-icon" <UploadFileButton
aria-hidden="true" fileInputRef={fileInputRef}
handleFileChange={handleFileChange}
handleButtonClick={handleButtonClick}
/> />
) : (
<IconComponent
name="LucideSend"
className="form-modal-send-icon "
aria-hidden="true"
/>
)}
</button>
</div> </div>
</div> </div>
<div className="flex w-full gap-2 pb-2"> {files.length > 0 && (
<div className={classNameFilePreview}>
{files.map((file) => ( {files.map((file) => (
<FilePreview <FilePreview
error={file.error} error={file.error}
@ -187,33 +109,15 @@ export default function ChatInput({
loading={file.loading} loading={file.loading}
key={file.id} key={file.id}
onDelete={() => { onDelete={() => {
setFiles((prev) => prev.filter((f) => f.id !== file.id)); setFiles((prev: FilePreviewType[]) =>
prev.filter((f) => f.id !== file.id)
);
// TODO: delete file on backend // TODO: delete file on backend
}} }}
/> />
))} ))}
</div> </div>
{/* )}
<Popover>
<PopoverTrigger asChild>
<Button variant="primary" className="h-13 px-4">
<IconComponent name="Repeat" className="" aria-hidden="true" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-fit">
<div className="flex flex-col items-center justify-center gap-2">
<span className="text-sm">Repetitions: </span>
<Input
onChange={(e) => {
handleChange(parseInt(e.target.value));
}}
className="w-16"
type="number"
min={0}
/>
</div>
</PopoverContent>
</Popover> */}
</div> </div>
); );
} }

View file

@ -0,0 +1,35 @@
import { useState } from "react";
import ForwardedIconComponent from "../../../../../../../components/genericIconComponent";
import formatFileName from "../../../filePreviewChat/utils/format-file-name";
import FileCard from "../../../fileComponent";
export default function FileCardWrapper({
index,
name,
type,
path,
}: {
index: number;
name: string;
type: string;
path: string;
}) {
const [show, setShow] = useState<boolean>(true);
return (
<div key={index} className="flex flex-col gap-2">
<span
onClick={() => setShow(!show)}
className="flex cursor-pointer gap-2 text-sm text-muted-foreground"
>
{formatFileName(name, 50)}
<ForwardedIconComponent name={show ? "ChevronDown" : "ChevronRight"} />
</span>
<FileCard
showFile={show}
fileName={name}
fileType={type}
content={path}
/>
</div>
);
}

View file

@ -7,13 +7,17 @@ import remarkMath from "remark-math";
import MaleTechnology from "../../../../../assets/male-technologist.png"; import MaleTechnology from "../../../../../assets/male-technologist.png";
import Robot from "../../../../../assets/robot.png"; import Robot from "../../../../../assets/robot.png";
import CodeTabsComponent from "../../../../../components/codeTabsComponent"; import CodeTabsComponent from "../../../../../components/codeTabsComponent";
import IconComponent from "../../../../../components/genericIconComponent"; import IconComponent, {
ForwardedIconComponent,
} from "../../../../../components/genericIconComponent";
import SanitizedHTMLWrapper from "../../../../../components/sanitizedHTMLWrapper"; import SanitizedHTMLWrapper from "../../../../../components/sanitizedHTMLWrapper";
import useAlertStore from "../../../../../stores/alertStore"; import useAlertStore from "../../../../../stores/alertStore";
import useFlowStore from "../../../../../stores/flowStore"; import useFlowStore from "../../../../../stores/flowStore";
import { chatMessagePropsType } from "../../../../../types/components"; import { chatMessagePropsType } from "../../../../../types/components";
import { classNames, cn } from "../../../../../utils/utils"; import { classNames, cn } from "../../../../../utils/utils";
import FileCard from "../fileComponent"; import FileCard from "../fileComponent";
import formatFileName from "../filePreviewChat/utils/format-file-name";
import FileCardWrapper from "./components/fileCardWrapper";
export default function ChatMessage({ export default function ChatMessage({
chat, chat,
@ -22,6 +26,7 @@ export default function ChatMessage({
updateChat, updateChat,
setLockChat, setLockChat,
}: chatMessagePropsType): JSX.Element { }: chatMessagePropsType): JSX.Element {
const [showFile, setShowFile] = useState<boolean>(true);
const convert = new Convert({ newline: true }); const convert = new Convert({ newline: true });
const [hidden, setHidden] = useState(true); const [hidden, setHidden] = useState(true);
const template = chat.template; const template = chat.template;
@ -308,6 +313,7 @@ dark:prose-invert"
</span> </span>
</> </>
) : ( ) : (
<div className="flex flex-col">
<span <span
className="prose text-primary word-break-break-word dark:prose-invert" className="prose text-primary word-break-break-word dark:prose-invert"
data-testid={ data-testid={
@ -316,26 +322,26 @@ dark:prose-invert"
> >
{chatMessage} {chatMessage}
</span> </span>
{chat.files && (
<div className="my-2 flex flex-col gap-5">
{chat.files.map((file, index) => {
return (
<FileCardWrapper
index={index}
name={file.name}
type={file.type}
path={file.path}
/>
);
})}
</div>
)}
</div>
)} )}
</div> </div>
)} )}
</div> </div>
<div id={lastMessage ? "last-chat-message" : ""}></div> <div id={lastMessage ? "last-chat-message" : ""}></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={file.name}
fileType={file.type}
content={file.path}
/>
</div>
);
})}
</div>
)}
</> </>
); );
} }

View file

@ -0,0 +1,25 @@
import ForwardedIconComponent from "../../../../../../../components/genericIconComponent";
export default function DownloadButton({
isHovered,
handleDownload,
}: {
isHovered: boolean;
handleDownload: () => void;
}): JSX.Element | undefined {
if (isHovered) {
return (
<div
className={`absolute right-1 top-1 rounded-bl-lg bg-muted text-sm font-bold text-foreground `}
>
<button className="px-2 py-1 text-ring " onClick={handleDownload}>
<ForwardedIconComponent
name="DownloadCloud"
className="h-5 w-5 text-current hover:scale-110"
/>
</button>
</div>
);
}
return undefined;
}

View file

@ -1,8 +1,12 @@
import { useState } from "react"; import { useState } from "react";
import IconComponent from "../../../../../components/genericIconComponent"; import { ForwardedIconComponent } from "../../../../../components/genericIconComponent";
import { BACKEND_URL, BASE_URL_API } from "../../../../../constants/constants"; import { BACKEND_URL, BASE_URL_API } from "../../../../../constants/constants";
import useFlowsManagerStore from "../../../../../stores/flowsManagerStore"; import useFlowsManagerStore from "../../../../../stores/flowsManagerStore";
import { fileCardPropsType } from "../../../../../types/components"; import { fileCardPropsType } from "../../../../../types/components";
import formatFileName from "../filePreviewChat/utils/format-file-name";
import DownloadButton from "./components/downloadButton/downloadButton";
import getClasses from "./utils/get-classes";
import handleDownload from "./utils/handle-download";
const imgTypes = new Set(["png", "jpg"]); const imgTypes = new Set(["png", "jpg"]);
@ -10,10 +14,8 @@ export default function FileCard({
fileName, fileName,
content, content,
fileType, fileType,
}: fileCardPropsType): JSX.Element { showFile = true,
const handleDownload = (): void => { }: fileCardPropsType): JSX.Element | undefined {
//TODO: update download function
};
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId); const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
function handleMouseEnter(): void { function handleMouseEnter(): void {
@ -23,64 +25,57 @@ export default function FileCard({
setIsHovered(false); setIsHovered(false);
} }
const fileWrapperClasses = getClasses(isHovered);
const imgSrc = `${BACKEND_URL.slice(
0,
BACKEND_URL.length - 1
)}${BASE_URL_API}files/images/${content}`;
if (showFile) {
if (imgTypes.has(fileType)) { if (imgTypes.has(fileType)) {
return ( return (
<div <div
className="relative h-1/4 w-1/4" className="inline-block w-full rounded-lg transition-all"
onMouseEnter={handleMouseEnter} onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave} onMouseLeave={handleMouseLeave}
style={{ display: "inline-block" }}
> >
<div className="relative w-[50%] rounded-lg border border-border">
<img <img
src={`${BACKEND_URL.slice( src={imgSrc}
0,
BACKEND_URL.length - 1,
)}${BASE_URL_API}files/images/${content}`}
alt="generated image" alt="generated image"
className="h-full w-full rounded-lg" className="m-0 h-auto w-auto rounded-lg border border-border p-0 transition-all"
/> />
{isHovered && ( <DownloadButton
<div className={`file-card-modal-image-div `}> isHovered={isHovered}
<button handleDownload={() => handleDownload({ fileName, content })}
className="file-card-modal-image-button "
onClick={handleDownload}
>
<IconComponent
name="DownloadCloud"
className="h-5 w-5 text-current hover:scale-110"
/> />
</button>
</div> </div>
)}
</div> </div>
); );
} }
return ( return (
<button onClick={handleDownload} className="file-card-modal-button"> <div
<div className="file-card-modal-div"> className={fileWrapperClasses}
{" "} onClick={() => handleDownload({ fileName, content })}
{imgTypes.has(fileType) ? ( onMouseEnter={handleMouseEnter}
<img onMouseLeave={handleMouseLeave}
src={`${BACKEND_URL.slice( >
0, <div className="ml-3 flex h-full w-full items-center gap-2 text-sm">
BACKEND_URL.length - 1, <ForwardedIconComponent name="File" className="h-8 w-8" />
)}${BASE_URL_API}files/images/${content}`} <div className="flex flex-col">
alt="" <span className="font-bold">{formatFileName(fileName, 20)}</span>
className="h-8 w-8" <span>File</span>
/>
) : (
<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> </div>
<IconComponent </div>
name="DownloadCloud" <DownloadButton
className="ml-auto h-6 w-6 text-current" isHovered={isHovered}
handleDownload={() => handleDownload({ fileName, content })}
/> />
</div> </div>
</button>
); );
}
return undefined;
} }

View file

@ -0,0 +1,5 @@
export default function getClasses(isHovered: boolean): string {
return `relative ${false ? "h-20 w-20" : "h-20 w-80"} cursor-pointer rounded-lg border border-ring bg-muted shadow transition duration-300 hover:drop-shadow-lg ${
isHovered ? "shadow-md" : ""
}`;
}

View file

@ -0,0 +1,43 @@
import {
BACKEND_URL,
BASE_URL_API,
} from "../../../../../../constants/constants";
let isDownloading = false;
export default async function handleDownload({
fileName,
content,
}: {
fileName: string;
content: string;
}): Promise<void> {
if (isDownloading) return;
try {
isDownloading = true;
const response = await fetch(
`${BACKEND_URL.slice(0, BACKEND_URL.length - 1)}${BASE_URL_API}files/download/${content}`,
);
if (!response.ok) {
throw new Error("Network response was not ok");
}
const blob = await response.blob();
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", fileName); // Set the filename
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url); // Clean up the URL object
} catch (error) {
console.error("Failed to download file:", error);
} finally {
isDownloading = false;
}
}

View file

@ -1,6 +1,9 @@
import { useState } from "react"; import { useState } from "react";
import IconComponent from "../../../../../components/genericIconComponent"; import IconComponent, {
import LoadingComponent from "../../../../../components/loadingComponent"; ForwardedIconComponent,
} from "../../../../../components/genericIconComponent";
import { Skeleton } from "../../../../../components/ui/skeleton";
import formatFileName from "./utils/format-file-name";
export default function FilePreview({ export default function FilePreview({
error, error,
@ -13,35 +16,92 @@ export default function FilePreview({
error: boolean; error: boolean;
onDelete: () => void; onDelete: () => void;
}) { }) {
const isImage = file.type.toLowerCase().includes("image");
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
return ( return (
<div className="relative inline-block w-56"> <div className="relative inline-block">
{loading && <LoadingComponent remSize={5} />} {loading ? (
{error && <div>Error...</div>} isImage ? (
<div className="flex h-20 w-20 items-center justify-center rounded-md border border-ring bg-background ">
<svg
aria-hidden="true"
className={`h-10 w-10 animate-spin fill-black text-muted`}
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="currentColor"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentFill"
/>
</svg>
</div>
) : (
<div <div
className={`relative overflow-hidden rounded-md bg-background p-4 transition duration-300 ${ className={`relative ${
isImage ? "h-20 w-20" : "h-20 w-80"
} cursor-wait rounded-lg border border-ring bg-background transition duration-300 ${
isHovered ? "shadow-md" : ""
}`}
>
<div className="ml-3 flex h-full w-full items-center gap-2 text-sm">
<Skeleton className="h-10 w-10 rounded-lg" />
<div className="flex flex-col gap-1">
<Skeleton className="h-3 w-48" />
<Skeleton className="h-3 w-10" />
</div>
</div>
</div>
)
) : error ? (
<div>Error...</div>
) : (
<div
className={`relative mt-2 ${
isImage ? "h-20 w-20" : "h-20 w-80"
} cursor-pointer rounded-lg border border-ring bg-background transition duration-300 ${
isHovered ? "shadow-md" : "" isHovered ? "shadow-md" : ""
}`} }`}
onMouseEnter={() => setIsHovered(true)} onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)} onMouseLeave={() => setIsHovered(false)}
> >
{isImage ? (
<img <img
src={URL.createObjectURL(file)} src={URL.createObjectURL(file)}
alt="file" alt="file"
className="block h-auto w-full" className="block h-full w-full rounded-md border border-border"
/> />
) : (
<div className="ml-3 flex h-full w-full items-center gap-2 text-sm">
<ForwardedIconComponent name="File" className="h-8 w-8" />
<div className="flex flex-col">
<span className="font-bold">{formatFileName(file.name)}</span>
<span>File</span>
</div>
</div>
)}
{isHovered && ( {isHovered && (
<div className="absolute inset-0 flex items-center justify-center bg-black bg-opacity-30">
<div <div
className="cursor-pointer rounded-full bg-white bg-opacity-80 p-2" className={`absolute ${
isImage ? "bottom-16 left-16" : "bottom-16 left-[19em]"
} flex h-5 w-5 items-center justify-center`}
>
<div
className="flex h-7 w-7 cursor-pointer items-center justify-center rounded-full bg-gray-200 p-2 transition-all"
onClick={onDelete} onClick={onDelete}
> >
<IconComponent name="trash" className="stroke-red-500" /> <IconComponent name="X" className="stroke-slate-950 stroke-2" />
</div> </div>
</div> </div>
)} )}
</div> </div>
)}
</div> </div>
); );
} }

View file

@ -0,0 +1,14 @@
export default function formatFileName(
name: string,
numberToTruncate: number = 25,
): string {
if (name[numberToTruncate] === undefined) {
return name;
}
const fileExtension = name.split(".").pop(); // Get the file extension
const baseName = name.slice(0, name.lastIndexOf(".")); // Get the base name without the extension
if (baseName.length > 6) {
return `${baseName.slice(0, numberToTruncate)}...${fileExtension}`;
}
return name;
}

View file

@ -11,9 +11,10 @@ import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { VertexBuildTypeAPI, sendAllProps } from "../../../../types/api"; import { VertexBuildTypeAPI, sendAllProps } from "../../../../types/api";
import { ChatMessageType, ChatOutputType } from "../../../../types/chat"; import { ChatMessageType, ChatOutputType } from "../../../../types/chat";
import { chatViewProps } from "../../../../types/components"; import { FilePreviewType, chatViewProps } from "../../../../types/components";
import { classNames } from "../../../../utils/utils"; import { classNames } from "../../../../utils/utils";
import ChatInput from "./chatInput"; import ChatInput from "./chatInput";
import useDragAndDrop from "./chatInput/hooks/use-drag-and-drop";
import ChatMessage from "./chatMessage"; import ChatMessage from "./chatMessage";
export default function ChatView({ export default function ChatView({
@ -61,13 +62,12 @@ export default function ChatView({
const chatMessages: ChatMessageType[] = chatOutputResponses const chatMessages: ChatMessageType[] = chatOutputResponses
.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp)) .sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp))
// //
.filter( .filter((output) => output.data.message)
(output) => output.data.messages && output.data.messages.length > 0,
)
.map((output, index) => { .map((output, index) => {
try { try {
const { sender, message, sender_name, stream_url, files } = output const { sender, message, sender_name, stream_url, files } = output
.data.messages[0] as ChatOutputType; .data.message as ChatOutputType;
const is_ai = sender === "Machine" || sender === null; const is_ai = sender === "Machine" || sender === null;
return { return {
isSend: !is_ai, isSend: !is_ai,
@ -131,7 +131,7 @@ export default function ChatView({
function updateChat( function updateChat(
chat: ChatMessageType, chat: ChatMessageType,
message: string, message: string,
stream_url?: string, stream_url?: string
) { ) {
// if (message === "") return; // if (message === "") return;
chat.message = message; chat.message = message;
@ -154,9 +154,23 @@ export default function ChatView({
// return newChatHistory; // return newChatHistory;
// }); // });
} }
const [files, setFiles] = useState<FilePreviewType[]>([]);
const [isDragging, setIsDragging] = useState(false);
const { dragOver, dragEnter, dragLeave, onDrop } = useDragAndDrop(
setIsDragging,
setFiles,
currentFlowId
);
return ( return (
<div className="eraser-column-arrangement"> <div
className="eraser-column-arrangement"
onDragOver={dragOver}
onDragEnter={dragEnter}
onDragLeave={dragLeave}
onDrop={onDrop}
>
<div className="eraser-size"> <div className="eraser-size">
<div className="eraser-position"> <div className="eraser-position">
<Button <Button
@ -258,6 +272,9 @@ export default function ChatView({
setChatValue(value); setChatValue(value);
}} }}
inputRef={ref} inputRef={ref}
files={files}
setFiles={setFiles}
isDragging={isDragging}
/> />
</div> </div>
</div> </div>

View file

@ -21,6 +21,7 @@ import { Button } from "../../components/ui/button";
import { modalHeaderType } from "../../types/components"; import { modalHeaderType } from "../../types/components";
import { cn } from "../../utils/utils"; import { cn } from "../../utils/utils";
import { switchCaseModalSize } from "./helpers/switch-case-size"; import { switchCaseModalSize } from "./helpers/switch-case-size";
import * as Form from "@radix-ui/react-form";
type ContentProps = { children: ReactNode }; type ContentProps = { children: ReactNode };
type HeaderProps = { children: ReactNode; description: string }; type HeaderProps = { children: ReactNode; description: string };

View file

@ -8,7 +8,7 @@ const useColumnDefs = (
myData: any, myData: any,
handleOnNewValue: (newValue: any, name: string) => void, handleOnNewValue: (newValue: any, name: string) => void,
changeAdvanced: (n: string) => void, changeAdvanced: (n: string) => void,
open: boolean, open: boolean
) => { ) => {
const columnDefs: ColDef[] = useMemo( const columnDefs: ColDef[] = useMemo(
() => [ () => [
@ -81,7 +81,7 @@ const useColumnDefs = (
cellClass: "no-border", cellClass: "no-border",
}, },
], ],
[open, myData], [open, myData]
); );
return columnDefs; return columnDefs;

View file

@ -22,7 +22,7 @@ const EditNodeModal = forwardRef(
setOpen: (open: boolean) => void; setOpen: (open: boolean) => void;
data: NodeDataType; data: NodeDataType;
}, },
ref, ref
) => { ) => {
const myData = useRef(data); const myData = useRef(data);
@ -43,7 +43,7 @@ const EditNodeModal = forwardRef(
data, data,
handleOnNewValue, handleOnNewValue,
changeAdvanced, changeAdvanced,
open, open
); );
const [gridApi, setGridApi] = useState<GridApi | null>(null); const [gridApi, setGridApi] = useState<GridApi | null>(null);
@ -107,7 +107,7 @@ const EditNodeModal = forwardRef(
<BaseModal.Footer submit={{ label: "Save Changes" }} /> <BaseModal.Footer submit={{ label: "Save Changes" }} />
</BaseModal> </BaseModal>
); );
}, }
); );
export default EditNodeModal; export default EditNodeModal;

View file

@ -40,22 +40,22 @@ export default function ComponentsComponent({
const allFlows = useFlowsManagerStore((state) => state.allFlows); const allFlows = useFlowsManagerStore((state) => state.allFlows);
const flowsFromFolder = useFolderStore( const flowsFromFolder = useFolderStore(
(state) => state.selectedFolder?.flows, (state) => state.selectedFolder?.flows
); );
const setSuccessData = useAlertStore((state) => state.setSuccessData); const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const [openDelete, setOpenDelete] = useState(false); const [openDelete, setOpenDelete] = useState(false);
const searchFlowsComponents = useFlowsManagerStore( const searchFlowsComponents = useFlowsManagerStore(
(state) => state.searchFlowsComponents, (state) => state.searchFlowsComponents
); );
const setSelectedFlowsComponentsCards = useFlowsManagerStore( const setSelectedFlowsComponentsCards = useFlowsManagerStore(
(state) => state.setSelectedFlowsComponentsCards, (state) => state.setSelectedFlowsComponentsCards
); );
const selectedFlowsComponentsCards = useFlowsManagerStore( const selectedFlowsComponentsCards = useFlowsManagerStore(
(state) => state.selectedFlowsComponentsCards, (state) => state.selectedFlowsComponentsCards
); );
const [handleFileDrop] = useFileDrop(uploadFlow, type)!; const [handleFileDrop] = useFileDrop(uploadFlow, type)!;
@ -109,7 +109,7 @@ export default function ComponentsComponent({
const { handleSelectAll } = useSelectAll( const { handleSelectAll } = useSelectAll(
flowsFromFolder, flowsFromFolder,
getValues, getValues,
setValue, setValue
); );
const { handleDuplicate } = useDuplicateFlows( const { handleDuplicate } = useDuplicateFlows(
@ -124,7 +124,7 @@ export default function ComponentsComponent({
setSuccessData, setSuccessData,
setSelectedFlowsComponentsCards, setSelectedFlowsComponentsCards,
handleSelectAll, handleSelectAll,
cardTypes, cardTypes
); );
const version = useDarkStore((state) => state.version); const version = useDarkStore((state) => state.version);
@ -138,7 +138,7 @@ export default function ComponentsComponent({
setSuccessData, setSuccessData,
setSelectedFlowsComponentsCards, setSelectedFlowsComponentsCards,
handleSelectAll, handleSelectAll,
cardTypes, cardTypes
); );
const { handleSelectOptionsChange } = useSelectOptionsChange( const { handleSelectOptionsChange } = useSelectOptionsChange(
@ -146,7 +146,7 @@ export default function ComponentsComponent({
setErrorData, setErrorData,
setOpenDelete, setOpenDelete,
handleDuplicate, handleDuplicate,
handleExport, handleExport
); );
const { handleDeleteMultiple } = useDeleteMultipleFlows( const { handleDeleteMultiple } = useDeleteMultipleFlows(
@ -158,21 +158,21 @@ export default function ComponentsComponent({
myCollectionId, myCollectionId,
getFolderById, getFolderById,
setSuccessData, setSuccessData,
setErrorData, setErrorData
); );
useSelectedFlows(entireFormValues, setSelectedFlowsComponentsCards); useSelectedFlows(entireFormValues, setSelectedFlowsComponentsCards);
const descriptionModal = useDescriptionModal( const descriptionModal = useDescriptionModal(
selectedFlowsComponentsCards, selectedFlowsComponentsCards,
type, type
); );
const getTotalRowsCount = () => { const getTotalRowsCount = () => {
if (type === "all") return allFlows?.length; if (type === "all") return allFlows?.length;
return allFlows?.filter( return allFlows?.filter(
(f) => (f.is_component ?? false) === (type === "component"), (f) => (f.is_component ?? false) === (type === "component")
)?.length; )?.length;
}; };

View file

@ -1,9 +1,12 @@
import { useState } from "react"; import { useState } from "react";
import IconComponent from "../../../../components/genericIconComponent"; import IconComponent, {
ForwardedIconComponent,
} from "../../../../components/genericIconComponent";
import ShadTooltip from "../../../../components/shadTooltipComponent"; import ShadTooltip from "../../../../components/shadTooltipComponent";
import { Button } from "../../../../components/ui/button"; import { Button } from "../../../../components/ui/button";
import { Checkbox } from "../../../../components/ui/checkbox"; import { Checkbox } from "../../../../components/ui/checkbox";
import { cn } from "../../../../utils/utils"; import { cn } from "../../../../utils/utils";
import { Button } from "../../../../components/ui/button";
type HeaderComponentProps = { type HeaderComponentProps = {
handleSelectAll: (select) => void; handleSelectAll: (select) => void;

View file

@ -25,7 +25,7 @@ export default function ApiKeysPage() {
userData, userData,
setLoadingKeys, setLoadingKeys,
keysList, keysList,
setUserId, setUserId
); );
function resetFilter() { function resetFilter() {
@ -36,7 +36,7 @@ export default function ApiKeysPage() {
selectedRows, selectedRows,
resetFilter, resetFilter,
setSuccessData, setSuccessData,
setErrorData, setErrorData
); );
const columnDefs = getColumnDefs(); const columnDefs = getColumnDefs();

View file

@ -16,18 +16,14 @@ import {
import { BuildStatus } from "../constants/enums"; import { BuildStatus } from "../constants/enums";
import { getFlowPool } from "../controllers/API"; import { getFlowPool } from "../controllers/API";
import { VertexBuildTypeAPI } from "../types/api"; import { VertexBuildTypeAPI } from "../types/api";
import { ChatInputType, ChatOutputType } from "../types/chat";
import { import {
NodeDataType, NodeDataType,
NodeType, NodeType,
sourceHandleType, sourceHandleType,
targetHandleType, targetHandleType,
} from "../types/flow"; } from "../types/flow";
import { import { FlowStoreType, VertexLayerElementType } from "../types/zustand/flow";
ChatOutputType,
FlowStoreType,
VertexLayerElementType,
chatInputType,
} from "../types/zustand/flow";
import { buildVertices } from "../utils/buildUtils"; import { buildVertices } from "../utils/buildUtils";
import { import {
checkChatInput, checkChatInput,
@ -77,8 +73,8 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
}, },
updateFlowPool: ( updateFlowPool: (
nodeId: string, nodeId: string,
data: VertexBuildTypeAPI | ChatOutputType | chatInputType, data: VertexBuildTypeAPI | ChatOutputType | ChatInputType,
buildId?: string, buildId?: string
) => { ) => {
let newFlowPool = cloneDeep({ ...get().flowPool }); let newFlowPool = cloneDeep({ ...get().flowPool });
if (!newFlowPool[nodeId]) { if (!newFlowPool[nodeId]) {
@ -94,9 +90,9 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
} }
//update data results //update data results
else { else {
newFlowPool[nodeId][index].data.messages[0] = data as newFlowPool[nodeId][index].data.message = data as
| ChatOutputType | ChatOutputType
| chatInputType; | ChatInputType;
} }
} }
get().setFlowPool(newFlowPool); get().setFlowPool(newFlowPool);
@ -171,7 +167,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
flowsManager.autoSaveCurrentFlow( flowsManager.autoSaveCurrentFlow(
newChange, newChange,
newEdges, newEdges,
get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }, get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }
); );
} }
}, },
@ -187,7 +183,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
flowsManager.autoSaveCurrentFlow( flowsManager.autoSaveCurrentFlow(
get().nodes, get().nodes,
newChange, newChange,
get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }, get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }
); );
} }
}, },
@ -205,7 +201,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
return newChange; return newChange;
} }
return node; return node;
}), })
); );
}, },
getNode: (id: string) => { getNode: (id: string) => {
@ -216,8 +212,8 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
get().nodes.filter((node) => get().nodes.filter((node) =>
typeof nodeId === "string" typeof nodeId === "string"
? node.id !== nodeId ? node.id !== nodeId
: !nodeId.includes(node.id), : !nodeId.includes(node.id)
), )
); );
}, },
deleteEdge: (edgeId) => { deleteEdge: (edgeId) => {
@ -225,8 +221,8 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
get().edges.filter((edge) => get().edges.filter((edge) =>
typeof edgeId === "string" typeof edgeId === "string"
? edge.id !== edgeId ? edge.id !== edgeId
: !edgeId.includes(edge.id), : !edgeId.includes(edge.id)
), )
); );
}, },
paste: (selection, position) => { paste: (selection, position) => {
@ -292,7 +288,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
let source = idsMap[edge.source]; let source = idsMap[edge.source];
let target = idsMap[edge.target]; let target = idsMap[edge.target];
const sourceHandleObject: sourceHandleType = scapeJSONParse( const sourceHandleObject: sourceHandleType = scapeJSONParse(
edge.sourceHandle!, edge.sourceHandle!
); );
let sourceHandle = scapedJSONStringfy({ let sourceHandle = scapedJSONStringfy({
...sourceHandleObject, ...sourceHandleObject,
@ -302,7 +298,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
edge.data.sourceHandle = sourceHandleObject; edge.data.sourceHandle = sourceHandleObject;
const targetHandleObject: targetHandleType = scapeJSONParse( const targetHandleObject: targetHandleType = scapeJSONParse(
edge.targetHandle!, edge.targetHandle!
); );
let targetHandle = scapedJSONStringfy({ let targetHandle = scapedJSONStringfy({
...targetHandleObject, ...targetHandleObject,
@ -323,7 +319,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
className: "stroke-gray-900 ", className: "stroke-gray-900 ",
selected: false, selected: false,
}, },
newEdges.map((edge) => ({ ...edge, selected: false })), newEdges.map((edge) => ({ ...edge, selected: false }))
); );
}); });
get().setEdges(newEdges); get().setEdges(newEdges);
@ -342,10 +338,10 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
}); });
const newNodes = get().nodes.filter( const newNodes = get().nodes.filter(
(node) => !nodesIdsSelected.includes(node.id), (node) => !nodesIdsSelected.includes(node.id)
); );
const newEdges = get().edges.filter( const newEdges = get().edges.filter(
(edge) => !edgesIdsSelected.includes(edge.id), (edge) => !edgesIdsSelected.includes(edge.id)
); );
set({ nodes: newNodes, edges: newEdges }); set({ nodes: newNodes, edges: newEdges });
@ -403,7 +399,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
style: { stroke: "#555" }, style: { stroke: "#555" },
className: "stroke-foreground stroke-connection", className: "stroke-foreground stroke-connection",
}, },
oldEdges, oldEdges
); );
return newEdges; return newEdges;
@ -413,7 +409,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
.autoSaveCurrentFlow( .autoSaveCurrentFlow(
get().nodes, get().nodes,
newEdges, newEdges,
get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }, get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }
); );
}, },
unselectAll: () => { unselectAll: () => {
@ -448,7 +444,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
function validateSubgraph(nodes: string[]) { function validateSubgraph(nodes: string[]) {
const errorsObjs = validateNodes( const errorsObjs = validateNodes(
get().nodes.filter((node) => nodes.includes(node.id)), get().nodes.filter((node) => nodes.includes(node.id)),
get().edges, get().edges
); );
const errors = errorsObjs.map((obj) => obj.errors).flat(); const errors = errorsObjs.map((obj) => obj.errors).flat();
@ -467,13 +463,13 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
function handleBuildUpdate( function handleBuildUpdate(
vertexBuildData: VertexBuildTypeAPI, vertexBuildData: VertexBuildTypeAPI,
status: BuildStatus, status: BuildStatus,
runId: string, runId: string
) { ) {
if (vertexBuildData && vertexBuildData.inactivated_vertices) { if (vertexBuildData && vertexBuildData.inactivated_vertices) {
get().removeFromVerticesBuild(vertexBuildData.inactivated_vertices); get().removeFromVerticesBuild(vertexBuildData.inactivated_vertices);
get().updateBuildStatus( get().updateBuildStatus(
vertexBuildData.inactivated_vertices, vertexBuildData.inactivated_vertices,
BuildStatus.INACTIVE, BuildStatus.INACTIVE
); );
} }
@ -489,15 +485,14 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
// next_vertices_ids should be next_vertices_ids without the inactivated vertices // next_vertices_ids should be next_vertices_ids without the inactivated vertices
const next_vertices_ids = vertexBuildData.next_vertices_ids.filter( const next_vertices_ids = vertexBuildData.next_vertices_ids.filter(
(id) => !vertexBuildData.inactivated_vertices?.includes(id), (id) => !vertexBuildData.inactivated_vertices?.includes(id)
); );
const top_level_vertices = vertexBuildData.top_level_vertices.filter( const top_level_vertices = vertexBuildData.top_level_vertices.filter(
(vertex) => (vertex) => !vertexBuildData.inactivated_vertices?.includes(vertex)
!vertexBuildData.inactivated_vertices?.includes(vertex.id),
); );
const nextVertices: VertexLayerElementType[] = zip( const nextVertices: VertexLayerElementType[] = zip(
next_vertices_ids, next_vertices_ids,
top_level_vertices, top_level_vertices
).map(([id, reference]) => ({ id: id!, reference })); ).map(([id, reference]) => ({ id: id!, reference }));
const newLayers = [ const newLayers = [
@ -518,8 +513,8 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
} }
get().addDataToFlowPool( get().addDataToFlowPool(
{ ...vertexBuildData, buildId: runId }, { ...vertexBuildData, run_id: runId },
vertexBuildData.id, vertexBuildData.id
); );
useFlowStore.getState().updateBuildStatus([vertexBuildData.id], status); useFlowStore.getState().updateBuildStatus([vertexBuildData.id], status);
@ -528,7 +523,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
const newFlowBuildStatus = { ...get().flowBuildStatus }; const newFlowBuildStatus = { ...get().flowBuildStatus };
// filter out the vertices that are not status // filter out the vertices that are not status
const verticesToUpdate = verticesIds?.filter( const verticesToUpdate = verticesIds?.filter(
(id) => newFlowBuildStatus[id]?.status !== BuildStatus.BUILT, (id) => newFlowBuildStatus[id]?.status !== BuildStatus.BUILT
); );
if (verticesToUpdate) { if (verticesToUpdate) {
@ -598,7 +593,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
verticesLayers: VertexLayerElementType[][]; verticesLayers: VertexLayerElementType[][];
runId: string; runId: string;
verticesToRun: string[]; verticesToRun: string[];
} | null, } | null
) => { ) => {
set({ verticesBuild: vertices }); set({ verticesBuild: vertices });
}, },
@ -623,7 +618,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
// that are going to be built // that are going to be built
verticesIds: get().verticesBuild!.verticesIds.filter( verticesIds: get().verticesBuild!.verticesIds.filter(
// keep the vertices that are not in the list of vertices to remove // keep the vertices that are not in the list of vertices to remove
(vertex) => !vertices.includes(vertex), (vertex) => !vertices.includes(vertex)
), ),
}, },
}); });

View file

@ -1,5 +1,5 @@
import { Edge, Node, Viewport } from "reactflow"; import { Edge, Node, Viewport } from "reactflow";
import { ChatOutputType, chatInputType } from "../chat"; import { ChatInputType, ChatOutputType } from "../chat";
import { FlowType } from "../flow"; import { FlowType } from "../flow";
//kind and class are just representative names to represent the actual structure of the object received by the API //kind and class are just representative names to represent the actual structure of the object received by the API
export type APIDataType = { [key: string]: APIKindType }; export type APIDataType = { [key: string]: APIKindType };
@ -154,15 +154,15 @@ export type VertexBuildTypeAPI = {
data: VertexDataTypeAPI; data: VertexDataTypeAPI;
timestamp: string; timestamp: string;
params: any; params: any;
messages: ChatOutputType[] | chatInputType[]; messages: ChatOutputType[] | ChatInputType[];
}; };
// data is the object received by the API // data is the object received by the API
// it has results, artifacts, timedelta, duration // it has results, artifacts, timedelta, duration
export type VertexDataTypeAPI = { export type VertexDataTypeAPI = {
results: { [key: string]: string }; results: { [key: string]: string };
logs: any[]; logs: { message: any; type: string }[];
messages: ChatOutputType[] | chatInputType[]; message: ChatOutputType | ChatInputType;
inactive?: boolean; inactive?: boolean;
timedelta?: number; timedelta?: number;
duration?: string; duration?: string;

View file

@ -22,15 +22,19 @@ export type ChatOutputType = {
files?: Array<{ path: string; type: string; name: string }>; files?: Array<{ path: string; type: string; name: string }>;
}; };
export type chatInputType = { export type ChatInputType = {
result: string; message: string;
sender: string;
sender_name: string;
stream_url?: string;
files?: Array<{ path: string; type: string; name: string }>;
}; };
export type FlowPoolObjectType = { export type FlowPoolObjectType = {
timestamp: string; timestamp: string;
valid: boolean; valid: boolean;
// list of chat outputs or list of chat inputs // list of chat outputs or list of chat inputs
messages: Array<ChatOutputType | chatInputType> | []; messages: Array<ChatOutputType | ChatInputType> | [];
data: { artifacts?: any; results: any | ChatOutputType | chatInputType }; data: { artifacts: any; results: any | ChatOutputType | ChatInputType };
id: string; id: string;
}; };

View file

@ -487,7 +487,12 @@ export type headerFlowsType = {
style?: FlowStyleType; style?: FlowStyleType;
}; };
export type chatInputType = { export type ChatInputType = {
isDragging: boolean;
files: FilePreviewType[];
setFiles: (
files: FilePreviewType[] | ((prev: FilePreviewType[]) => FilePreviewType[])
) => void;
chatValue: string; chatValue: string;
inputRef: { inputRef: {
current: any; current: any;
@ -526,6 +531,7 @@ export type fileCardPropsType = {
fileName: string; fileName: string;
content: string; content: string;
fileType: string; fileType: string;
showFile?: boolean;
}; };
export type nodeToolbarPropsType = { export type nodeToolbarPropsType = {

View file

@ -9,28 +9,16 @@ import {
} from "reactflow"; } from "reactflow";
import { BuildStatus } from "../../../constants/enums"; import { BuildStatus } from "../../../constants/enums";
import { VertexBuildTypeAPI } from "../../api"; import { VertexBuildTypeAPI } from "../../api";
import { ChatInputType, ChatOutputType } from "../../chat";
import { FlowState } from "../../tabs"; import { FlowState } from "../../tabs";
export type chatInputType = {
result: string;
files?: string[];
};
export type ChatOutputType = {
message: string;
sender: string;
sender_name: string;
stream_url?: string;
files?: string[];
};
export type FlowPoolObjectType = { export type FlowPoolObjectType = {
timestamp: string; timestamp: string;
valid: boolean; valid: boolean;
messages: Array<ChatOutputType | chatInputType> | []; messages: Array<ChatOutputType | ChatInputType> | [];
data: { data: {
artifacts: any | ChatOutputType | chatInputType; artifacts: any | ChatOutputType | ChatInputType;
results: any | ChatOutputType | chatInputType; results: any | ChatOutputType | ChatInputType;
}; };
duration?: string; duration?: string;
progress?: number; progress?: number;
@ -45,8 +33,8 @@ export type FlowPoolObjectTypeNew = {
timestamp: string; timestamp: string;
valid: boolean; valid: boolean;
data: { data: {
logs?: any | ChatOutputType | chatInputType; logs?: any | ChatOutputType | ChatInputType;
results: any | ChatOutputType | chatInputType; results: any | ChatOutputType | ChatInputType;
}; };
duration?: string; duration?: string;
progress?: number; progress?: number;
@ -98,7 +86,7 @@ export type FlowStoreType = {
state: state:
| FlowState | FlowState
| undefined | undefined
| ((oldState: FlowState | undefined) => FlowState), | ((oldState: FlowState | undefined) => FlowState)
) => void; ) => void;
nodes: Node[]; nodes: Node[];
edges: Edge[]; edges: Edge[];
@ -106,11 +94,11 @@ export type FlowStoreType = {
onEdgesChange: OnEdgesChange; onEdgesChange: OnEdgesChange;
setNodes: ( setNodes: (
update: Node[] | ((oldState: Node[]) => Node[]), update: Node[] | ((oldState: Node[]) => Node[]),
skipSave?: boolean, skipSave?: boolean
) => void; ) => void;
setEdges: ( setEdges: (
update: Edge[] | ((oldState: Edge[]) => Edge[]), update: Edge[] | ((oldState: Edge[]) => Edge[]),
skipSave?: boolean, skipSave?: boolean
) => void; ) => void;
setNode: (id: string, update: Node | ((oldState: Node) => Node)) => void; setNode: (id: string, update: Node | ((oldState: Node) => Node)) => void;
getNode: (id: string) => Node | undefined; getNode: (id: string) => Node | undefined;
@ -118,12 +106,12 @@ export type FlowStoreType = {
deleteEdge: (edgeId: string | Array<string>) => void; deleteEdge: (edgeId: string | Array<string>) => void;
paste: ( paste: (
selection: { nodes: any; edges: any }, selection: { nodes: any; edges: any },
position: { x: number; y: number; paneX?: number; paneY?: number }, position: { x: number; y: number; paneX?: number; paneY?: number }
) => void; ) => void;
lastCopiedSelection: { nodes: any; edges: any } | null; lastCopiedSelection: { nodes: any; edges: any } | null;
setLastCopiedSelection: ( setLastCopiedSelection: (
newSelection: { nodes: any; edges: any } | null, newSelection: { nodes: any; edges: any } | null,
isCrop?: boolean, isCrop?: boolean
) => void; ) => void;
cleanFlow: () => void; cleanFlow: () => void;
setFilterEdge: (newState) => void; setFilterEdge: (newState) => void;
@ -150,7 +138,7 @@ export type FlowStoreType = {
verticesLayers: VertexLayerElementType[][]; verticesLayers: VertexLayerElementType[][];
runId: string; runId: string;
verticesToRun: string[]; verticesToRun: string[];
} | null, } | null
) => void; ) => void;
addToVerticesBuild: (vertices: string[]) => void; addToVerticesBuild: (vertices: string[]) => void;
removeFromVerticesBuild: (vertices: string[]) => void; removeFromVerticesBuild: (vertices: string[]) => void;
@ -167,8 +155,8 @@ export type FlowStoreType = {
}; };
updateFlowPool: ( updateFlowPool: (
nodeId: string, nodeId: string,
data: VertexBuildTypeAPI | ChatOutputType | chatInputType, data: VertexBuildTypeAPI | ChatOutputType | ChatInputType,
buildId?: string, buildId?: string
) => void; ) => void;
getNodePosition: (nodeId: string) => { x: number; y: number }; getNodePosition: (nodeId: string) => { x: number; y: number };
}; };

View file

@ -1,4 +1,4 @@
export type chatInputType = { export type ChatInputType = {
result: string; result: string;
}; };
@ -12,7 +12,7 @@ export type FlowPoolObjectType = {
timestamp: string; timestamp: string;
valid: boolean; valid: boolean;
params: any; params: any;
data: { artifacts: any; results: any | ChatOutputType | chatInputType }; data: { artifacts: any; results: any | ChatOutputType | ChatInputType };
id: string; id: string;
}; };

View file

@ -93,6 +93,7 @@ import {
Package2, Package2,
Palette, Palette,
Paperclip, Paperclip,
PaperclipIcon,
Pencil, Pencil,
PencilLine, PencilLine,
Pin, Pin,
@ -533,6 +534,7 @@ export const nodeIconsLucide: iconsType = {
FolderPlusIcon, FolderPlusIcon,
FolderIcon, FolderIcon,
Discord: FaDiscord, Discord: FaDiscord,
PaperclipIcon,
RotateCcw, RotateCcw,
Settings, Settings,
Streamlit, Streamlit,