merge branch chatImg
This commit is contained in:
commit
9ce7ee31a9
60 changed files with 1056 additions and 511 deletions
|
|
@ -4,7 +4,7 @@ You can run Langflow in `--backend-only` mode to expose your Langflow app as an
|
|||
|
||||
Start langflow in backend-only mode with `python3 -m langflow run --backend-only`.
|
||||
|
||||
The terminal prints ` Welcome to ⛓ Langflow `, and a blank window opens at `http://127.0.0.1:7864/all`.
|
||||
The terminal prints ` Welcome to ⛓ Langflow `, and a blank window opens at `http://127.0.0.1:7864/all`.
|
||||
Langflow will now serve requests to its API without the frontend running.
|
||||
|
||||
## Prerequisites
|
||||
|
|
@ -42,7 +42,7 @@ Note the flow ID of `ef7e0554-69e5-4e3e-ab29-ee83bcd8d9ef`. You can find this ID
|
|||
|
||||
1. Stop Langflow with Ctrl+C.
|
||||
2. Start langflow in backend-only mode with `python3 -m langflow run --backend-only`.
|
||||
The terminal prints ` Welcome to ⛓ Langflow `, and a blank window opens at `http://127.0.0.1:7864/all`.
|
||||
The terminal prints ` Welcome to ⛓ Langflow `, and a blank window opens at `http://127.0.0.1:7864/all`.
|
||||
Langflow will now serve requests to its API.
|
||||
3. Run the curl code you copied from the UI.
|
||||
You should get a result like this:
|
||||
|
|
|
|||
|
|
@ -168,9 +168,9 @@ async def build_vertex(
|
|||
next_runnable_vertices,
|
||||
top_level_vertices,
|
||||
result_dict,
|
||||
log_message,
|
||||
params,
|
||||
valid,
|
||||
log_type,
|
||||
artifacts,
|
||||
vertex,
|
||||
) = await graph.build_vertex(
|
||||
lock=lock,
|
||||
|
|
@ -180,22 +180,22 @@ async def build_vertex(
|
|||
inputs_dict=inputs.model_dump() if inputs else {},
|
||||
files=files,
|
||||
)
|
||||
|
||||
log_obj = Log(message=vertex.artifacts_raw, type=vertex.artifacts_type)
|
||||
result_data_response = ResultDataResponse(**result_dict.model_dump())
|
||||
|
||||
except Exception as exc:
|
||||
logger.exception(f"Error building vertex: {exc}")
|
||||
log_message = format_exception_message(exc)
|
||||
log_type = type(exc).__name__
|
||||
params = format_exception_message(exc)
|
||||
valid = False
|
||||
log_obj = Log(message=params, type="error")
|
||||
result_data_response = ResultDataResponse(results={})
|
||||
log_object = Log(message=log_message, type=log_type)
|
||||
|
||||
artifacts = {}
|
||||
# If there's an error building the vertex
|
||||
# we need to clear the cache
|
||||
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
|
||||
if not vertex.will_stream:
|
||||
|
|
@ -204,8 +204,9 @@ async def build_vertex(
|
|||
flow_id=flow_id_str,
|
||||
vertex_id=vertex_id,
|
||||
valid=valid,
|
||||
logs=result_data_response.logs,
|
||||
params=params,
|
||||
data=result_data_response,
|
||||
artifacts=artifacts,
|
||||
)
|
||||
|
||||
timedelta = time.perf_counter() - start_time
|
||||
|
|
@ -231,6 +232,7 @@ async def build_vertex(
|
|||
next_vertices_ids=next_runnable_vertices,
|
||||
top_level_vertices=top_level_vertices,
|
||||
valid=valid,
|
||||
params=params,
|
||||
id=vertex.id,
|
||||
data=result_data_response,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from datetime import datetime, timezone
|
|||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
from typing_extensions import TypedDict
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_serializer
|
||||
|
|
@ -244,10 +245,16 @@ class VerticesOrderResponse(BaseModel):
|
|||
vertices_to_run: List[str]
|
||||
|
||||
|
||||
class Log(TypedDict):
|
||||
message: Union[dict, str]
|
||||
type: str
|
||||
|
||||
|
||||
class ResultDataResponse(BaseModel):
|
||||
results: Optional[Any] = Field(default_factory=dict)
|
||||
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
|
||||
duration: Optional[str] = None
|
||||
used_frozen_result: Optional[bool] = False
|
||||
|
|
@ -259,6 +266,8 @@ class VertexBuildResponse(BaseModel):
|
|||
next_vertices_ids: Optional[List[str]] = None
|
||||
top_level_vertices: Optional[List[str]] = None
|
||||
valid: bool
|
||||
params: Optional[Any] = Field(default_factory=dict)
|
||||
"""JSON string of the params."""
|
||||
data: ResultDataResponse
|
||||
"""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))
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
import warnings
|
||||
from typing import Optional, Union
|
||||
|
||||
from langchain_core.language_models.chat_models import BaseChatModel
|
||||
from langchain_core.language_models.llms import LLM
|
||||
from langchain_core.load import load
|
||||
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
|
||||
|
||||
from langflow.custom import CustomComponent
|
||||
from langflow.schema.schema import Record
|
||||
|
||||
|
||||
class LCModelComponent(CustomComponent):
|
||||
|
|
@ -82,7 +85,7 @@ class LCModelComponent(CustomComponent):
|
|||
return status_message
|
||||
|
||||
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]] = []
|
||||
if not input_value and not system_message:
|
||||
|
|
@ -90,7 +93,16 @@ class LCModelComponent(CustomComponent):
|
|||
if system_message:
|
||||
messages.append(SystemMessage(content=system_message))
|
||||
if input_value:
|
||||
messages.append(HumanMessage(content=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))
|
||||
if stream:
|
||||
return runnable.stream(messages)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import base64
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
from langchain_core.documents import Document
|
||||
|
||||
from langflow.schema import Record
|
||||
from langflow.services.deps import get_storage_service
|
||||
|
||||
|
||||
def record_to_string(record: Record) -> str:
|
||||
|
|
@ -19,7 +20,7 @@ def record_to_string(record: Record) -> str:
|
|||
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.
|
||||
|
||||
|
|
@ -36,16 +37,43 @@ def dict_values_to_string(d: dict) -> dict:
|
|||
if isinstance(value, list):
|
||||
for i, item in enumerate(value):
|
||||
if isinstance(item, Record):
|
||||
d_copy[key][i] = record_to_string(item)
|
||||
d_copy[key][i] = item.to_lc_message()
|
||||
elif isinstance(item, Document):
|
||||
d_copy[key][i] = document_to_string(item)
|
||||
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):
|
||||
d_copy[key] = document_to_string(value)
|
||||
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:
|
||||
"""
|
||||
Convert a document to a string.
|
||||
|
|
|
|||
|
|
@ -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.field_typing import Prompt, TemplateField, Text
|
||||
from langflow.schema.schema import Record
|
||||
|
||||
|
||||
class PromptComponent(CustomComponent):
|
||||
|
|
@ -15,19 +17,14 @@ class PromptComponent(CustomComponent):
|
|||
"code": TemplateField(advanced=True),
|
||||
}
|
||||
|
||||
def build(
|
||||
async def build(
|
||||
self,
|
||||
template: Prompt,
|
||||
**kwargs,
|
||||
) -> Text:
|
||||
from langflow.base.prompts.utils import dict_values_to_string
|
||||
|
||||
prompt_template = PromptTemplate.from_template(Text(template))
|
||||
kwargs = dict_values_to_string(kwargs)
|
||||
kwargs = {k: "\n".join(v) if isinstance(v, list) else v for k, v in kwargs.items()}
|
||||
try:
|
||||
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
|
||||
) -> Record:
|
||||
prompt_template = ChatPromptTemplate.from_template(Text(template))
|
||||
kwargs = await dict_values_to_string(kwargs)
|
||||
messages = list(kwargs.values())
|
||||
prompt = prompt_template + messages
|
||||
self.status = f'Prompt:\n"{template}"'
|
||||
return Record(data={"prompt": prompt.to_json()})
|
||||
|
|
|
|||
|
|
@ -738,7 +738,9 @@ class Graph:
|
|||
# Check the cache for the vertex
|
||||
cached_result = await chat_service.get_cache(key=vertex.id)
|
||||
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)
|
||||
else:
|
||||
cached_vertex = cached_result["result"]
|
||||
|
|
@ -752,7 +754,9 @@ class Graph:
|
|||
vertex.result.used_frozen_result = True
|
||||
|
||||
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:
|
||||
params = f"{vertex._built_object_repr()}{params}"
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from enum import Enum
|
|||
from typing import Any, Generator, Union
|
||||
|
||||
from langchain_core.documents import Document
|
||||
from langflow.schema.schema import Record
|
||||
from pydantic import BaseModel
|
||||
|
||||
from langflow.interface.utils import extract_input_variables_from_prompt
|
||||
|
|
|
|||
|
|
@ -628,9 +628,8 @@ class Vertex:
|
|||
self._built_object, self.artifacts = result
|
||||
elif len(result) == 3:
|
||||
self._custom_component, self._built_object, self.artifacts = result
|
||||
self.artifacts_raw = self.artifacts.get("raw")
|
||||
self.artifacts_type = self.artifacts.get("type") or ArtifactType.UNKNOWN.value
|
||||
|
||||
self.artifacts_raw = self.artifacts.pop("raw", None)
|
||||
self.artifacts_type = self.artifacts.pop("type", None) or ArtifactType.UNKNOWN.value
|
||||
else:
|
||||
self._built_object = result
|
||||
|
||||
|
|
|
|||
|
|
@ -111,12 +111,7 @@ class InterfaceVertex(Vertex):
|
|||
message = self._built_object
|
||||
artifact_type = ArtifactType.STREAM if stream_url is not None else ArtifactType.OBJECT
|
||||
artifacts = ChatOutputResponse(
|
||||
message=message,
|
||||
sender=sender,
|
||||
sender_name=sender_name,
|
||||
stream_url=stream_url,
|
||||
files=files,
|
||||
type=artifact_type.value,
|
||||
message=message, sender=sender, sender_name=sender_name, stream_url=stream_url, files=files
|
||||
)
|
||||
|
||||
self.will_stream = stream_url is not None
|
||||
|
|
@ -213,9 +208,9 @@ class InterfaceVertex(Vertex):
|
|||
flow_id=self.graph.flow_id,
|
||||
vertex_id=self.id,
|
||||
valid=True,
|
||||
logs=self._built_object_repr(),
|
||||
params=self._built_object_repr(),
|
||||
data=self.result,
|
||||
messages=self.artifacts,
|
||||
artifacts=self.artifacts,
|
||||
)
|
||||
|
||||
self._validate_built_object()
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
"list": false,
|
||||
"show": true,
|
||||
"multiline": true,
|
||||
"value": "from langchain_core.prompts import PromptTemplate\n\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"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": [],
|
||||
"file_path": "",
|
||||
"password": false,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
"list": false,
|
||||
"show": true,
|
||||
"multiline": true,
|
||||
"value": "from langchain_core.prompts import PromptTemplate\n\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"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": [],
|
||||
"file_path": "",
|
||||
"password": false,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
"list": false,
|
||||
"show": true,
|
||||
"multiline": true,
|
||||
"value": "from langchain_core.prompts import PromptTemplate\n\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"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": [],
|
||||
"file_path": "",
|
||||
"password": false,
|
||||
|
|
|
|||
|
|
@ -524,7 +524,7 @@
|
|||
"list": false,
|
||||
"show": true,
|
||||
"multiline": true,
|
||||
"value": "from langchain_core.prompts import PromptTemplate\n\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"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": [],
|
||||
"file_path": "",
|
||||
"password": false,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
"list": false,
|
||||
"show": true,
|
||||
"multiline": true,
|
||||
"value": "from langchain_core.prompts import PromptTemplate\n\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"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": [],
|
||||
"file_path": "",
|
||||
"password": false,
|
||||
|
|
@ -130,7 +130,7 @@
|
|||
"list": false,
|
||||
"show": true,
|
||||
"multiline": true,
|
||||
"value": "from langchain_core.prompts import PromptTemplate\n\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"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": [],
|
||||
"file_path": "",
|
||||
"password": false,
|
||||
|
|
|
|||
|
|
@ -1034,7 +1034,7 @@
|
|||
"list": false,
|
||||
"show": true,
|
||||
"multiline": true,
|
||||
"value": "from langchain_core.prompts import PromptTemplate\n\nfrom langflow.custom import CustomComponent\nfrom langflow.field_typing import Prompt, TemplateField, Text\n\n\nclass PromptComponent(CustomComponent):\n display_name: str = \"Prompt\"\n description: str = \"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": [],
|
||||
"file_path": "",
|
||||
"password": false,
|
||||
|
|
|
|||
|
|
@ -125,7 +125,6 @@ async def instantiate_custom_component(params, user_id, vertex, fallback_to_env_
|
|||
custom_repr = build_result
|
||||
if not isinstance(custom_repr, str):
|
||||
custom_repr = str(custom_repr)
|
||||
|
||||
raw = custom_component.repr_value
|
||||
if hasattr(raw, "data"):
|
||||
raw = raw.data
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import copy
|
||||
import json
|
||||
from typing import Literal, Optional, cast
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from langchain_core.documents import Document
|
||||
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage
|
||||
from pydantic import BaseModel, model_validator
|
||||
from typing_extensions import TypedDict
|
||||
from langchain_core.prompts.image import ImagePromptTemplate
|
||||
from pydantic import BaseModel, model_serializer, model_validator
|
||||
|
||||
|
||||
class Record(BaseModel):
|
||||
|
|
@ -30,6 +31,11 @@ class Record(BaseModel):
|
|||
values["data"][key] = values[key]
|
||||
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):
|
||||
"""
|
||||
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)
|
||||
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.
|
||||
|
||||
|
|
@ -119,8 +127,22 @@ class Record(BaseModel):
|
|||
raise ValueError(f"Missing required keys ('text', 'sender') in Record: {self.data}")
|
||||
sender = self.data.get("sender", "Machine")
|
||||
text = self.data.get("text", "")
|
||||
files = self.data.get("files", [])
|
||||
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)
|
||||
|
||||
def __getattr__(self, key):
|
||||
|
|
@ -170,8 +192,14 @@ class Record(BaseModel):
|
|||
|
||||
def __str__(self) -> str:
|
||||
# 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"
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ class CacheMiss:
|
|||
def __repr__(self):
|
||||
return "<CACHE_MISS>"
|
||||
|
||||
def __bool__(self):
|
||||
return False
|
||||
|
||||
|
||||
def create_cache_folder(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ class MessageModel(BaseModel):
|
|||
session_id: str
|
||||
message: str
|
||||
files: list[str] = []
|
||||
artifacts: dict
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
|
@ -87,6 +88,12 @@ class MessageModel(BaseModel):
|
|||
return json.loads(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
|
||||
def from_record(cls, record: "Record", flow_id: Optional[str] = None):
|
||||
# first check if the record has all the required fields
|
||||
|
|
@ -107,6 +114,12 @@ class MessageModel(BaseModel):
|
|||
class MessageModelResponse(MessageModel):
|
||||
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")
|
||||
def validate_id(cls, v):
|
||||
if isinstance(v, float):
|
||||
|
|
@ -129,15 +142,16 @@ class VertexBuildModel(BaseModel):
|
|||
id: Optional[str] = Field(default=None, alias="id")
|
||||
flow_id: str
|
||||
valid: bool
|
||||
logs: Any
|
||||
params: Any
|
||||
data: dict
|
||||
artifacts: dict
|
||||
timestamp: datetime = Field(default_factory=datetime.now)
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
populate_by_name = True
|
||||
|
||||
@field_serializer("data")
|
||||
@field_serializer("data", "artifacts")
|
||||
def serialize_dict(v):
|
||||
if isinstance(v, dict):
|
||||
# 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
|
||||
|
||||
@field_validator("logs", mode="before")
|
||||
def validate_logs(cls, v):
|
||||
@field_validator("params", mode="before")
|
||||
def validate_params(cls, v):
|
||||
if isinstance(v, str):
|
||||
try:
|
||||
return json.loads(v)
|
||||
|
|
@ -160,7 +174,7 @@ class VertexBuildModel(BaseModel):
|
|||
return v
|
||||
return v
|
||||
|
||||
@field_serializer("logs")
|
||||
@field_serializer("params")
|
||||
def serialize_params(v):
|
||||
if isinstance(v, list) and all(isinstance(i, BaseModel) 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 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):
|
||||
messages: list[MessageModel] = []
|
||||
|
||||
@field_serializer("data")
|
||||
@field_serializer("data", "artifacts")
|
||||
def serialize_dict(v):
|
||||
return v
|
||||
|
||||
|
|
|
|||
|
|
@ -147,9 +147,9 @@ async def log_vertex_build(
|
|||
flow_id: str,
|
||||
vertex_id: str,
|
||||
valid: bool,
|
||||
logs: Any,
|
||||
params: Any,
|
||||
data: "ResultDataResponse",
|
||||
messages: Optional[dict] = None,
|
||||
artifacts: Optional[dict] = None,
|
||||
):
|
||||
try:
|
||||
monitor_service = get_monitor_service()
|
||||
|
|
@ -158,9 +158,9 @@ async def log_vertex_build(
|
|||
"flow_id": flow_id,
|
||||
"id": vertex_id,
|
||||
"valid": valid,
|
||||
"logs": logs,
|
||||
"params": params,
|
||||
"data": data.model_dump(),
|
||||
"messages": messages or {},
|
||||
"artifacts": artifacts or {},
|
||||
"timestamp": monitor_service.get_timestamp(),
|
||||
}
|
||||
monitor_service.add_row(table_name="vertex_builds", data=row)
|
||||
|
|
|
|||
|
|
@ -90,9 +90,9 @@ async def build_vertex(
|
|||
flow_id=flow_id,
|
||||
vertex_id=vertex_id,
|
||||
valid=valid,
|
||||
logs=params,
|
||||
params=params,
|
||||
data=result_dict,
|
||||
messages=artifacts,
|
||||
artifacts=artifacts,
|
||||
)
|
||||
|
||||
# Emit the vertex build response
|
||||
|
|
|
|||
|
|
@ -55,10 +55,10 @@ export default function GenericNode({
|
|||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const isDark = useDarkStore((state) => state.dark);
|
||||
const buildStatus = useFlowStore(
|
||||
(state) => state.flowBuildStatus[data.id]?.status,
|
||||
(state) => state.flowBuildStatus[data.id]?.status
|
||||
);
|
||||
const lastRunTime = useFlowStore(
|
||||
(state) => state.flowBuildStatus[data.id]?.timestamp,
|
||||
(state) => state.flowBuildStatus[data.id]?.timestamp
|
||||
);
|
||||
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ export default function GenericNode({
|
|||
const [nodeName, setNodeName] = useState(data.node!.display_name);
|
||||
const [inputDescription, setInputDescription] = useState(false);
|
||||
const [nodeDescription, setNodeDescription] = useState(
|
||||
data.node?.description!,
|
||||
data.node?.description!
|
||||
);
|
||||
const [isOutdated, setIsOutdated] = useState(false);
|
||||
const [validationStatus, setValidationStatus] =
|
||||
|
|
@ -84,7 +84,7 @@ export default function GenericNode({
|
|||
data.node!,
|
||||
setNode,
|
||||
setIsOutdated,
|
||||
updateNodeInternals,
|
||||
updateNodeInternals
|
||||
);
|
||||
|
||||
const name = nodeIconsLucide[data.type] ? data.type : types[data.type];
|
||||
|
|
@ -115,12 +115,12 @@ export default function GenericNode({
|
|||
selected: boolean,
|
||||
showNode: boolean,
|
||||
buildStatus: BuildStatus | undefined,
|
||||
validationStatus: VertexBuildTypeAPI | null,
|
||||
validationStatus: VertexBuildTypeAPI | null
|
||||
) => {
|
||||
const specificClassFromBuildStatus = getSpecificClassFromBuildStatus(
|
||||
buildStatus,
|
||||
validationStatus,
|
||||
isDark,
|
||||
isDark
|
||||
);
|
||||
|
||||
const baseBorderClass = getBaseBorderClass(selected);
|
||||
|
|
@ -129,7 +129,7 @@ export default function GenericNode({
|
|||
baseBorderClass,
|
||||
nodeSizeClass,
|
||||
"generic-node-div group/node",
|
||||
specificClassFromBuildStatus,
|
||||
specificClassFromBuildStatus
|
||||
);
|
||||
return names;
|
||||
};
|
||||
|
|
@ -170,7 +170,7 @@ export default function GenericNode({
|
|||
showNode,
|
||||
isEmoji,
|
||||
nodeIconFragment,
|
||||
checkNodeIconFragment,
|
||||
checkNodeIconFragment
|
||||
);
|
||||
|
||||
function countHandles(): void {
|
||||
|
|
@ -247,7 +247,7 @@ export default function GenericNode({
|
|||
selected,
|
||||
showNode,
|
||||
buildStatus,
|
||||
validationStatus,
|
||||
validationStatus
|
||||
)}
|
||||
>
|
||||
{data.node?.beta && showNode && (
|
||||
|
|
@ -378,7 +378,7 @@ export default function GenericNode({
|
|||
}
|
||||
title={getFieldTitle(
|
||||
data.node?.template!,
|
||||
templateField,
|
||||
templateField
|
||||
)}
|
||||
info={data.node?.template[templateField].info}
|
||||
name={templateField}
|
||||
|
|
@ -406,7 +406,7 @@ export default function GenericNode({
|
|||
proxy={data.node?.template[templateField].proxy}
|
||||
showNode={showNode}
|
||||
/>
|
||||
),
|
||||
)
|
||||
)}
|
||||
<ParameterComponent
|
||||
key={scapedJSONStringfy({
|
||||
|
|
@ -552,7 +552,7 @@ export default function GenericNode({
|
|||
!data.node?.description) &&
|
||||
nameEditable
|
||||
? "font-light italic"
|
||||
: "",
|
||||
: ""
|
||||
)}
|
||||
onClick={(e) => {
|
||||
setInputDescription(true);
|
||||
|
|
@ -614,13 +614,13 @@ export default function GenericNode({
|
|||
}
|
||||
title={getFieldTitle(
|
||||
data.node?.template!,
|
||||
templateField,
|
||||
templateField
|
||||
)}
|
||||
info={data.node?.template[templateField].info}
|
||||
name={templateField}
|
||||
tooltipTitle={
|
||||
data.node?.template[templateField].input_types?.join(
|
||||
"\n",
|
||||
"\n"
|
||||
) ?? data.node?.template[templateField].type
|
||||
}
|
||||
required={data.node!.template[templateField].required}
|
||||
|
|
@ -647,7 +647,7 @@ export default function GenericNode({
|
|||
<div
|
||||
className={classNames(
|
||||
Object.keys(data.node!.template).length < 1 ? "hidden" : "",
|
||||
"flex-max-width justify-center",
|
||||
"flex-max-width justify-center"
|
||||
)}
|
||||
>
|
||||
{" "}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ const SideBarFoldersButtonsComponent = ({
|
|||
const [foldersNames, setFoldersNames] = useState({});
|
||||
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||
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 currentFolder = pathname.split("/");
|
||||
|
|
@ -59,7 +59,7 @@ const SideBarFoldersButtonsComponent = ({
|
|||
|
||||
const { dragOver, dragEnter, dragLeave, onDrop } = useFileDrop(
|
||||
folderId,
|
||||
handleFolderChange,
|
||||
handleFolderChange
|
||||
);
|
||||
|
||||
const handleUploadFlowsToFolder = () => {
|
||||
|
|
@ -87,7 +87,7 @@ const SideBarFoldersButtonsComponent = ({
|
|||
addFolder({ name: "New Folder", parent_id: null, description: "" }).then(
|
||||
(res) => {
|
||||
getFoldersApi(true);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -133,7 +133,7 @@ const SideBarFoldersButtonsComponent = ({
|
|||
<>
|
||||
{folders.map((item, index) => {
|
||||
const editFolderName = editFolders?.filter(
|
||||
(folder) => folder.name === item.name,
|
||||
(folder) => folder.name === item.name
|
||||
)[0];
|
||||
return (
|
||||
<div
|
||||
|
|
@ -149,7 +149,7 @@ const SideBarFoldersButtonsComponent = ({
|
|||
? "border border-border bg-muted hover:bg-muted"
|
||||
: "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",
|
||||
folderIdDragging === item.id! ? "bg-border" : "",
|
||||
folderIdDragging === item.id! ? "bg-border" : ""
|
||||
)}
|
||||
onClick={() => handleChangeFolder!(item.id!)}
|
||||
>
|
||||
|
|
@ -219,7 +219,7 @@ const SideBarFoldersButtonsComponent = ({
|
|||
folders.map((obj) => ({
|
||||
name: obj.name,
|
||||
edit: false,
|
||||
})),
|
||||
}))
|
||||
);
|
||||
}
|
||||
if (e.key === "Enter") {
|
||||
|
|
@ -252,10 +252,10 @@ const SideBarFoldersButtonsComponent = ({
|
|||
};
|
||||
const updatedFolder = await updateFolder(
|
||||
body,
|
||||
item.id!,
|
||||
item.id!
|
||||
);
|
||||
const updateFolders = folders.filter(
|
||||
(f) => f.name !== item.name,
|
||||
(f) => f.name !== item.name
|
||||
);
|
||||
setFolders([...updateFolders, updatedFolder]);
|
||||
setFoldersNames({});
|
||||
|
|
@ -263,7 +263,7 @@ const SideBarFoldersButtonsComponent = ({
|
|||
folders.map((obj) => ({
|
||||
name: obj.name,
|
||||
edit: false,
|
||||
})),
|
||||
}))
|
||||
);
|
||||
} else {
|
||||
setFoldersNames((old) => ({
|
||||
|
|
|
|||
|
|
@ -63,8 +63,8 @@ export default function TableNodeCellRender({
|
|||
...id,
|
||||
proxy: templateData.proxy,
|
||||
}
|
||||
: id,
|
||||
),
|
||||
: id
|
||||
)
|
||||
) ?? false;
|
||||
function getCellType() {
|
||||
switch (templateData.type) {
|
||||
|
|
@ -135,7 +135,7 @@ export default function TableNodeCellRender({
|
|||
<div
|
||||
className={classNames(
|
||||
"max-h-48 w-full overflow-auto custom-scroll",
|
||||
templateValue?.length > 1 ? "my-3" : "",
|
||||
templateValue?.length > 1 ? "my-3" : ""
|
||||
)}
|
||||
>
|
||||
<KeypairListComponent
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import ForwardedIconComponent from "../genericIconComponent";
|
|||
import { Alert, AlertDescription, AlertTitle } from "../ui/alert";
|
||||
import ResetColumns from "./components/ResetColumns";
|
||||
import resetGrid from "./utils/reset-grid-columns";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
interface TableComponentProps extends AgGridReactProps {
|
||||
columnDefs: NonNullable<AgGridReactProps["columnDefs"]>;
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ export async function sendAll(data: sendAllProps) {
|
|||
}
|
||||
|
||||
export async function postValidateCode(
|
||||
code: string,
|
||||
code: string
|
||||
): Promise<AxiosResponse<errorsTypeAPI>> {
|
||||
return await api.post(`${BASE_URL_API}validate/code`, { code });
|
||||
}
|
||||
|
|
@ -77,7 +77,7 @@ export async function postValidateCode(
|
|||
export async function postValidatePrompt(
|
||||
name: string,
|
||||
template: string,
|
||||
frontend_node: APIClassType,
|
||||
frontend_node: APIClassType
|
||||
): Promise<AxiosResponse<PromptTypeAPI>> {
|
||||
return api.post(`${BASE_URL_API}validate/prompt`, {
|
||||
name,
|
||||
|
|
@ -150,7 +150,7 @@ export async function saveFlowToDatabase(newFlow: {
|
|||
* @throws Will throw an error if the update fails.
|
||||
*/
|
||||
export async function updateFlowInDatabase(
|
||||
updatedFlow: FlowType,
|
||||
updatedFlow: FlowType
|
||||
): Promise<FlowType> {
|
||||
try {
|
||||
const response = await api.patch(`${BASE_URL_API}flows/${updatedFlow.id}`, {
|
||||
|
|
@ -328,7 +328,7 @@ export async function getHealth() {
|
|||
*
|
||||
*/
|
||||
export async function getBuildStatus(
|
||||
flowId: string,
|
||||
flowId: string
|
||||
): Promise<AxiosResponse<BuildStatusTypeAPI>> {
|
||||
return await api.get(`${BASE_URL_API}build/${flowId}/status`);
|
||||
}
|
||||
|
|
@ -341,7 +341,7 @@ export async function getBuildStatus(
|
|||
*
|
||||
*/
|
||||
export async function postBuildInit(
|
||||
flow: FlowType,
|
||||
flow: FlowType
|
||||
): Promise<AxiosResponse<InitTypeAPI>> {
|
||||
return await api.post(`${BASE_URL_API}build/init/${flow.id}`, flow);
|
||||
}
|
||||
|
|
@ -357,7 +357,7 @@ export async function postBuildInit(
|
|||
*/
|
||||
export async function uploadFile(
|
||||
file: File,
|
||||
id: string,
|
||||
id: string
|
||||
): Promise<AxiosResponse<UploadFileTypeAPI>> {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
|
@ -366,7 +366,7 @@ export async function uploadFile(
|
|||
|
||||
export async function postCustomComponent(
|
||||
code: string,
|
||||
apiClass: APIClassType,
|
||||
apiClass: APIClassType
|
||||
): Promise<AxiosResponse<APIClassType>> {
|
||||
// let template = apiClass.template;
|
||||
return await api.post(`${BASE_URL_API}custom_component`, {
|
||||
|
|
@ -379,7 +379,7 @@ export async function postCustomComponentUpdate(
|
|||
code: string,
|
||||
template: APITemplateType,
|
||||
field: string,
|
||||
field_value: any,
|
||||
field_value: any
|
||||
): Promise<AxiosResponse<APIClassType>> {
|
||||
return await api.post(`${BASE_URL_API}custom_component/update`, {
|
||||
code,
|
||||
|
|
@ -401,7 +401,7 @@ export async function onLogin(user: LoginType) {
|
|||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (response.status === 200) {
|
||||
|
|
@ -463,11 +463,11 @@ export async function addUser(user: UserInputType): Promise<Array<Users>> {
|
|||
|
||||
export async function getUsersPage(
|
||||
skip: number,
|
||||
limit: number,
|
||||
limit: number
|
||||
): Promise<Array<Users>> {
|
||||
try {
|
||||
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) {
|
||||
return res.data;
|
||||
|
|
@ -504,7 +504,7 @@ export async function resetPassword(user_id: string, user: resetPasswordType) {
|
|||
try {
|
||||
const res = await api.patch(
|
||||
`${BASE_URL_API}users/${user_id}/reset-password`,
|
||||
user,
|
||||
user
|
||||
);
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
|
|
@ -578,7 +578,7 @@ export async function saveFlowStore(
|
|||
last_tested_version?: string;
|
||||
},
|
||||
tags: string[],
|
||||
publicFlow = false,
|
||||
publicFlow = false
|
||||
): Promise<FlowType> {
|
||||
try {
|
||||
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) {
|
||||
try {
|
||||
const res = await api.get(
|
||||
`${BASE_URL_API}store/components/${component_id}`,
|
||||
`${BASE_URL_API}store/components/${component_id}`
|
||||
);
|
||||
if (res.status === 200) {
|
||||
return res.data;
|
||||
|
|
@ -722,7 +722,7 @@ export async function searchComponent(
|
|||
page?: number | null,
|
||||
limit?: number | null,
|
||||
status?: string | null,
|
||||
tags?: string[],
|
||||
tags?: string[]
|
||||
): Promise<StoreComponentResponse | undefined> {
|
||||
try {
|
||||
let url = `${BASE_URL_API}store/components/`;
|
||||
|
|
@ -834,7 +834,7 @@ export async function updateFlowStore(
|
|||
},
|
||||
tags: string[],
|
||||
publicFlow = false,
|
||||
id: string,
|
||||
id: string
|
||||
): Promise<FlowType> {
|
||||
try {
|
||||
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(
|
||||
name: string,
|
||||
value: string,
|
||||
id: string,
|
||||
id: string
|
||||
) {
|
||||
try {
|
||||
const response = api.patch(`${BASE_URL_API}variables/${id}`, {
|
||||
|
|
@ -937,7 +937,7 @@ export async function getVerticesOrder(
|
|||
startNodeId?: string | null,
|
||||
stopNodeId?: string | null,
|
||||
nodes?: Node[],
|
||||
Edges?: Edge[],
|
||||
Edges?: Edge[]
|
||||
): Promise<AxiosResponse<VerticesOrderTypeAPI>> {
|
||||
// nodeId is optional and is a query parameter
|
||||
// if nodeId is not provided, the API will return all vertices
|
||||
|
|
@ -957,7 +957,7 @@ export async function getVerticesOrder(
|
|||
return await api.post(
|
||||
`${BASE_URL_API}build/${flowId}/vertices`,
|
||||
data,
|
||||
config,
|
||||
config
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -965,18 +965,16 @@ export async function postBuildVertex(
|
|||
flowId: string,
|
||||
vertexId: string,
|
||||
input_value: string,
|
||||
files?: string[],
|
||||
files?: string[]
|
||||
): Promise<AxiosResponse<VertexBuildTypeAPI>> {
|
||||
// input_value is optional and is a query parameter
|
||||
const data = input_value
|
||||
? { inputs: { input_value: input_value } }
|
||||
: undefined;
|
||||
const data = { inputs: { input_value: input_value ?? "" } };
|
||||
if (data && files) {
|
||||
data["files"] = files;
|
||||
}
|
||||
return await api.post(
|
||||
`${BASE_URL_API}build/${flowId}/vertices/${vertexId}`,
|
||||
data,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1000,7 +998,7 @@ export async function getFlowPool({
|
|||
}
|
||||
|
||||
export async function deleteFlowPool(
|
||||
flowId: string,
|
||||
flowId: string
|
||||
): Promise<AxiosResponse<any>> {
|
||||
const config = {};
|
||||
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.
|
||||
*/
|
||||
export async function multipleDeleteFlowsComponents(
|
||||
flowIds: string[],
|
||||
flowIds: string[]
|
||||
): Promise<AxiosResponse<any>[]> {
|
||||
const batches: string[][] = [];
|
||||
|
||||
|
|
@ -1037,7 +1035,7 @@ export async function multipleDeleteFlowsComponents(
|
|||
|
||||
// Execute all delete requests
|
||||
const responses: Promise<AxiosResponse<any>>[] = batches.map((batch) =>
|
||||
deleteBatch(batch),
|
||||
deleteBatch(batch)
|
||||
);
|
||||
|
||||
// Return the responses after all requests are completed
|
||||
|
|
@ -1047,7 +1045,7 @@ export async function multipleDeleteFlowsComponents(
|
|||
export async function getTransactionTable(
|
||||
id: string,
|
||||
mode: "intersection" | "union",
|
||||
params = {},
|
||||
params = {}
|
||||
): Promise<{ rows: Array<object>; columns: Array<ColDef | ColGroupDef> }> {
|
||||
const config = {};
|
||||
config["params"] = { flow_id: id };
|
||||
|
|
@ -1063,7 +1061,7 @@ export async function getMessagesTable(
|
|||
mode: "intersection" | "union",
|
||||
id?: string,
|
||||
excludedFields?: string[],
|
||||
params = {},
|
||||
params = {}
|
||||
): Promise<{ rows: Array<Message>; columns: Array<ColDef | ColGroupDef> }> {
|
||||
const config = {};
|
||||
if (id) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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"
|
||||
}`;
|
||||
};
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -1,7 +1,4 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import IconComponent from "../../../../../components/genericIconComponent";
|
||||
import { Textarea } from "../../../../../components/ui/textarea";
|
||||
import { useRef, useState } from "react";
|
||||
import {
|
||||
CHAT_INPUT_PLACEHOLDER,
|
||||
CHAT_INPUT_PLACEHOLDER_SEND,
|
||||
|
|
@ -9,11 +6,18 @@ import {
|
|||
import { uploadFile } from "../../../../../controllers/API";
|
||||
import useFlowsManagerStore from "../../../../../stores/flowsManagerStore";
|
||||
import {
|
||||
ChatInputType,
|
||||
FilePreviewType,
|
||||
chatInputType,
|
||||
} from "../../../../../types/components";
|
||||
import { classNames } from "../../../../../utils/utils";
|
||||
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({
|
||||
lockChat,
|
||||
chatValue,
|
||||
|
|
@ -21,199 +25,99 @@ export default function ChatInput({
|
|||
setChatValue,
|
||||
inputRef,
|
||||
noInput,
|
||||
}: chatInputType): JSX.Element {
|
||||
files,
|
||||
setFiles,
|
||||
isDragging,
|
||||
}: ChatInputType): JSX.Element {
|
||||
const [repeat, setRepeat] = useState(1);
|
||||
const saveLoading = useFlowsManagerStore((state) => state.saveLoading);
|
||||
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
||||
const uid = new ShortUniqueId({ length: 3 });
|
||||
const [files, setFiles] = useState<FilePreviewType[]>([]);
|
||||
const [inputFocus, setInputFocus] = useState<boolean>(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!lockChat && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
}, [lockChat, inputRef]);
|
||||
useFocusOnUnlock(lockChat, inputRef);
|
||||
useAutoResizeTextArea(chatValue, inputRef);
|
||||
useUpload(uploadFile, currentFlowId, setFiles);
|
||||
const { handleFileChange } = useHandleFileChange(setFiles, currentFlowId);
|
||||
|
||||
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
|
||||
}
|
||||
}, [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() {
|
||||
const send = () => {
|
||||
sendMessage({
|
||||
repeat,
|
||||
files: files.map((file) => file.path ?? "").filter((file) => file !== ""),
|
||||
});
|
||||
setFiles([]);
|
||||
}
|
||||
};
|
||||
|
||||
const checkSendingOk = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
return (
|
||||
event.key === "Enter" &&
|
||||
!lockChat &&
|
||||
!saveLoading &&
|
||||
!event.shiftKey &&
|
||||
!event.nativeEvent.isComposing
|
||||
);
|
||||
};
|
||||
|
||||
const classNameFilePreview = getClassNamesFilePreview(inputFocus);
|
||||
|
||||
const handleButtonClick = () => {
|
||||
fileInputRef.current!.click();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col-reverse">
|
||||
<div className="relative w-full">
|
||||
<Textarea
|
||||
onKeyDown={(event) => {
|
||||
if (
|
||||
event.key === "Enter" &&
|
||||
!lockChat &&
|
||||
!saveLoading &&
|
||||
!event.shiftKey &&
|
||||
!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",
|
||||
)}
|
||||
placeholder={
|
||||
noInput ? CHAT_INPUT_PLACEHOLDER : CHAT_INPUT_PLACEHOLDER_SEND
|
||||
}
|
||||
<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">
|
||||
<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()}
|
||||
>
|
||||
{lockChat || saveLoading ? (
|
||||
<IconComponent
|
||||
name="Lock"
|
||||
className="form-modal-lock-icon"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : noInput ? (
|
||||
<IconComponent
|
||||
name="Zap"
|
||||
className="form-modal-play-icon"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : (
|
||||
<IconComponent
|
||||
name="LucideSend"
|
||||
className="form-modal-send-icon "
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
<ButtonSendWrapper
|
||||
send={send}
|
||||
lockChat={lockChat}
|
||||
noInput={noInput}
|
||||
saveLoading={saveLoading}
|
||||
chatValue={chatValue}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="absolute bottom-2 left-4">
|
||||
<UploadFileButton
|
||||
fileInputRef={fileInputRef}
|
||||
handleFileChange={handleFileChange}
|
||||
handleButtonClick={handleButtonClick}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-full gap-2 pb-2">
|
||||
{files.map((file) => (
|
||||
<FilePreview
|
||||
error={file.error}
|
||||
file={file.file}
|
||||
loading={file.loading}
|
||||
key={file.id}
|
||||
onDelete={() => {
|
||||
setFiles((prev) => prev.filter((f) => f.id !== file.id));
|
||||
// TODO: delete file on backend
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</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));
|
||||
{files.length > 0 && (
|
||||
<div className={classNameFilePreview}>
|
||||
{files.map((file) => (
|
||||
<FilePreview
|
||||
error={file.error}
|
||||
file={file.file}
|
||||
loading={file.loading}
|
||||
key={file.id}
|
||||
onDelete={() => {
|
||||
setFiles((prev: FilePreviewType[]) =>
|
||||
prev.filter((f) => f.id !== file.id)
|
||||
);
|
||||
// TODO: delete file on backend
|
||||
}}
|
||||
className="w-16"
|
||||
type="number"
|
||||
min={0}
|
||||
/>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover> */}
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -7,13 +7,17 @@ import remarkMath from "remark-math";
|
|||
import MaleTechnology from "../../../../../assets/male-technologist.png";
|
||||
import Robot from "../../../../../assets/robot.png";
|
||||
import CodeTabsComponent from "../../../../../components/codeTabsComponent";
|
||||
import IconComponent from "../../../../../components/genericIconComponent";
|
||||
import IconComponent, {
|
||||
ForwardedIconComponent,
|
||||
} from "../../../../../components/genericIconComponent";
|
||||
import SanitizedHTMLWrapper from "../../../../../components/sanitizedHTMLWrapper";
|
||||
import useAlertStore from "../../../../../stores/alertStore";
|
||||
import useFlowStore from "../../../../../stores/flowStore";
|
||||
import { chatMessagePropsType } from "../../../../../types/components";
|
||||
import { classNames, cn } from "../../../../../utils/utils";
|
||||
import FileCard from "../fileComponent";
|
||||
import formatFileName from "../filePreviewChat/utils/format-file-name";
|
||||
import FileCardWrapper from "./components/fileCardWrapper";
|
||||
|
||||
export default function ChatMessage({
|
||||
chat,
|
||||
|
|
@ -22,6 +26,7 @@ export default function ChatMessage({
|
|||
updateChat,
|
||||
setLockChat,
|
||||
}: chatMessagePropsType): JSX.Element {
|
||||
const [showFile, setShowFile] = useState<boolean>(true);
|
||||
const convert = new Convert({ newline: true });
|
||||
const [hidden, setHidden] = useState(true);
|
||||
const template = chat.template;
|
||||
|
|
@ -308,34 +313,35 @@ dark:prose-invert"
|
|||
</span>
|
||||
</>
|
||||
) : (
|
||||
<span
|
||||
className="prose text-primary word-break-break-word dark:prose-invert"
|
||||
data-testid={
|
||||
"chat-message-" + chat.sender_name + "-" + chatMessage
|
||||
}
|
||||
>
|
||||
{chatMessage}
|
||||
</span>
|
||||
<div className="flex flex-col">
|
||||
<span
|
||||
className="prose text-primary word-break-break-word dark:prose-invert"
|
||||
data-testid={
|
||||
"chat-message-" + chat.sender_name + "-" + chatMessage
|
||||
}
|
||||
>
|
||||
{chatMessage}
|
||||
</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 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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -1,8 +1,12 @@
|
|||
import { useState } from "react";
|
||||
import IconComponent from "../../../../../components/genericIconComponent";
|
||||
import { ForwardedIconComponent } from "../../../../../components/genericIconComponent";
|
||||
import { BACKEND_URL, BASE_URL_API } from "../../../../../constants/constants";
|
||||
import useFlowsManagerStore from "../../../../../stores/flowsManagerStore";
|
||||
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"]);
|
||||
|
||||
|
|
@ -10,10 +14,8 @@ export default function FileCard({
|
|||
fileName,
|
||||
content,
|
||||
fileType,
|
||||
}: fileCardPropsType): JSX.Element {
|
||||
const handleDownload = (): void => {
|
||||
//TODO: update download function
|
||||
};
|
||||
showFile = true,
|
||||
}: fileCardPropsType): JSX.Element | undefined {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
||||
function handleMouseEnter(): void {
|
||||
|
|
@ -23,64 +25,57 @@ export default function FileCard({
|
|||
setIsHovered(false);
|
||||
}
|
||||
|
||||
if (imgTypes.has(fileType)) {
|
||||
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)) {
|
||||
return (
|
||||
<div
|
||||
className="inline-block w-full rounded-lg transition-all"
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
style={{ display: "inline-block" }}
|
||||
>
|
||||
<div className="relative w-[50%] rounded-lg border border-border">
|
||||
<img
|
||||
src={imgSrc}
|
||||
alt="generated image"
|
||||
className="m-0 h-auto w-auto rounded-lg border border-border p-0 transition-all"
|
||||
/>
|
||||
<DownloadButton
|
||||
isHovered={isHovered}
|
||||
handleDownload={() => handleDownload({ fileName, content })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative h-1/4 w-1/4"
|
||||
className={fileWrapperClasses}
|
||||
onClick={() => handleDownload({ fileName, content })}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<img
|
||||
src={`${BACKEND_URL.slice(
|
||||
0,
|
||||
BACKEND_URL.length - 1,
|
||||
)}${BASE_URL_API}files/images/${content}`}
|
||||
alt="generated image"
|
||||
className="h-full w-full rounded-lg"
|
||||
/>
|
||||
{isHovered && (
|
||||
<div className={`file-card-modal-image-div `}>
|
||||
<button
|
||||
className="file-card-modal-image-button "
|
||||
onClick={handleDownload}
|
||||
>
|
||||
<IconComponent
|
||||
name="DownloadCloud"
|
||||
className="h-5 w-5 text-current hover:scale-110"
|
||||
/>
|
||||
</button>
|
||||
<div 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(fileName, 20)}</span>
|
||||
<span>File</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<DownloadButton
|
||||
isHovered={isHovered}
|
||||
handleDownload={() => handleDownload({ fileName, content })}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button onClick={handleDownload} className="file-card-modal-button">
|
||||
<div className="file-card-modal-div">
|
||||
{" "}
|
||||
{imgTypes.has(fileType) ? (
|
||||
<img
|
||||
src={`${BACKEND_URL.slice(
|
||||
0,
|
||||
BACKEND_URL.length - 1,
|
||||
)}${BASE_URL_API}files/images/${content}`}
|
||||
alt=""
|
||||
className="h-8 w-8"
|
||||
/>
|
||||
) : (
|
||||
<IconComponent name="File" className="h-8 w-8" />
|
||||
)}
|
||||
<div className="file-card-modal-footer">
|
||||
{" "}
|
||||
<div className="file-card-modal-name">{fileName}</div>
|
||||
<div className="file-card-modal-type">{fileType}</div>
|
||||
</div>
|
||||
<IconComponent
|
||||
name="DownloadCloud"
|
||||
className="ml-auto h-6 w-6 text-current"
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" : ""
|
||||
}`;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
import { useState } from "react";
|
||||
import IconComponent from "../../../../../components/genericIconComponent";
|
||||
import LoadingComponent from "../../../../../components/loadingComponent";
|
||||
import IconComponent, {
|
||||
ForwardedIconComponent,
|
||||
} from "../../../../../components/genericIconComponent";
|
||||
import { Skeleton } from "../../../../../components/ui/skeleton";
|
||||
import formatFileName from "./utils/format-file-name";
|
||||
|
||||
export default function FilePreview({
|
||||
error,
|
||||
|
|
@ -13,35 +16,92 @@ export default function FilePreview({
|
|||
error: boolean;
|
||||
onDelete: () => void;
|
||||
}) {
|
||||
const isImage = file.type.toLowerCase().includes("image");
|
||||
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="relative inline-block w-56">
|
||||
{loading && <LoadingComponent remSize={5} />}
|
||||
{error && <div>Error...</div>}
|
||||
<div
|
||||
className={`relative overflow-hidden rounded-md bg-background p-4 transition duration-300 ${
|
||||
isHovered ? "shadow-md" : ""
|
||||
}`}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
<img
|
||||
src={URL.createObjectURL(file)}
|
||||
alt="file"
|
||||
className="block h-auto w-full"
|
||||
/>
|
||||
{isHovered && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black bg-opacity-30">
|
||||
<div
|
||||
className="cursor-pointer rounded-full bg-white bg-opacity-80 p-2"
|
||||
onClick={onDelete}
|
||||
<div className="relative inline-block">
|
||||
{loading ? (
|
||||
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"
|
||||
>
|
||||
<IconComponent name="trash" className="stroke-red-500" />
|
||||
<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
|
||||
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>
|
||||
)}
|
||||
</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" : ""
|
||||
}`}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
>
|
||||
{isImage ? (
|
||||
<img
|
||||
src={URL.createObjectURL(file)}
|
||||
alt="file"
|
||||
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 && (
|
||||
<div
|
||||
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}
|
||||
>
|
||||
<IconComponent name="X" className="stroke-slate-950 stroke-2" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -11,9 +11,10 @@ import useFlowStore from "../../../../stores/flowStore";
|
|||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||
import { VertexBuildTypeAPI, sendAllProps } from "../../../../types/api";
|
||||
import { ChatMessageType, ChatOutputType } from "../../../../types/chat";
|
||||
import { chatViewProps } from "../../../../types/components";
|
||||
import { FilePreviewType, chatViewProps } from "../../../../types/components";
|
||||
import { classNames } from "../../../../utils/utils";
|
||||
import ChatInput from "./chatInput";
|
||||
import useDragAndDrop from "./chatInput/hooks/use-drag-and-drop";
|
||||
import ChatMessage from "./chatMessage";
|
||||
|
||||
export default function ChatView({
|
||||
|
|
@ -61,13 +62,12 @@ export default function ChatView({
|
|||
const chatMessages: ChatMessageType[] = chatOutputResponses
|
||||
.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp))
|
||||
//
|
||||
.filter(
|
||||
(output) => output.data.messages && output.data.messages.length > 0,
|
||||
)
|
||||
.filter((output) => output.data.message)
|
||||
.map((output, index) => {
|
||||
try {
|
||||
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;
|
||||
return {
|
||||
isSend: !is_ai,
|
||||
|
|
@ -131,7 +131,7 @@ export default function ChatView({
|
|||
function updateChat(
|
||||
chat: ChatMessageType,
|
||||
message: string,
|
||||
stream_url?: string,
|
||||
stream_url?: string
|
||||
) {
|
||||
// if (message === "") return;
|
||||
chat.message = message;
|
||||
|
|
@ -154,9 +154,23 @@ export default function ChatView({
|
|||
// return newChatHistory;
|
||||
// });
|
||||
}
|
||||
const [files, setFiles] = useState<FilePreviewType[]>([]);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
|
||||
const { dragOver, dragEnter, dragLeave, onDrop } = useDragAndDrop(
|
||||
setIsDragging,
|
||||
setFiles,
|
||||
currentFlowId
|
||||
);
|
||||
|
||||
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-position">
|
||||
<Button
|
||||
|
|
@ -258,6 +272,9 @@ export default function ChatView({
|
|||
setChatValue(value);
|
||||
}}
|
||||
inputRef={ref}
|
||||
files={files}
|
||||
setFiles={setFiles}
|
||||
isDragging={isDragging}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import { Button } from "../../components/ui/button";
|
|||
import { modalHeaderType } from "../../types/components";
|
||||
import { cn } from "../../utils/utils";
|
||||
import { switchCaseModalSize } from "./helpers/switch-case-size";
|
||||
import * as Form from "@radix-ui/react-form";
|
||||
|
||||
type ContentProps = { children: ReactNode };
|
||||
type HeaderProps = { children: ReactNode; description: string };
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const useColumnDefs = (
|
|||
myData: any,
|
||||
handleOnNewValue: (newValue: any, name: string) => void,
|
||||
changeAdvanced: (n: string) => void,
|
||||
open: boolean,
|
||||
open: boolean
|
||||
) => {
|
||||
const columnDefs: ColDef[] = useMemo(
|
||||
() => [
|
||||
|
|
@ -81,7 +81,7 @@ const useColumnDefs = (
|
|||
cellClass: "no-border",
|
||||
},
|
||||
],
|
||||
[open, myData],
|
||||
[open, myData]
|
||||
);
|
||||
|
||||
return columnDefs;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ const EditNodeModal = forwardRef(
|
|||
setOpen: (open: boolean) => void;
|
||||
data: NodeDataType;
|
||||
},
|
||||
ref,
|
||||
ref
|
||||
) => {
|
||||
const myData = useRef(data);
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ const EditNodeModal = forwardRef(
|
|||
data,
|
||||
handleOnNewValue,
|
||||
changeAdvanced,
|
||||
open,
|
||||
open
|
||||
);
|
||||
|
||||
const [gridApi, setGridApi] = useState<GridApi | null>(null);
|
||||
|
|
@ -107,7 +107,7 @@ const EditNodeModal = forwardRef(
|
|||
<BaseModal.Footer submit={{ label: "Save Changes" }} />
|
||||
</BaseModal>
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default EditNodeModal;
|
||||
|
|
|
|||
|
|
@ -40,22 +40,22 @@ export default function ComponentsComponent({
|
|||
const allFlows = useFlowsManagerStore((state) => state.allFlows);
|
||||
|
||||
const flowsFromFolder = useFolderStore(
|
||||
(state) => state.selectedFolder?.flows,
|
||||
(state) => state.selectedFolder?.flows
|
||||
);
|
||||
|
||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const [openDelete, setOpenDelete] = useState(false);
|
||||
const searchFlowsComponents = useFlowsManagerStore(
|
||||
(state) => state.searchFlowsComponents,
|
||||
(state) => state.searchFlowsComponents
|
||||
);
|
||||
|
||||
const setSelectedFlowsComponentsCards = useFlowsManagerStore(
|
||||
(state) => state.setSelectedFlowsComponentsCards,
|
||||
(state) => state.setSelectedFlowsComponentsCards
|
||||
);
|
||||
|
||||
const selectedFlowsComponentsCards = useFlowsManagerStore(
|
||||
(state) => state.selectedFlowsComponentsCards,
|
||||
(state) => state.selectedFlowsComponentsCards
|
||||
);
|
||||
|
||||
const [handleFileDrop] = useFileDrop(uploadFlow, type)!;
|
||||
|
|
@ -109,7 +109,7 @@ export default function ComponentsComponent({
|
|||
const { handleSelectAll } = useSelectAll(
|
||||
flowsFromFolder,
|
||||
getValues,
|
||||
setValue,
|
||||
setValue
|
||||
);
|
||||
|
||||
const { handleDuplicate } = useDuplicateFlows(
|
||||
|
|
@ -124,7 +124,7 @@ export default function ComponentsComponent({
|
|||
setSuccessData,
|
||||
setSelectedFlowsComponentsCards,
|
||||
handleSelectAll,
|
||||
cardTypes,
|
||||
cardTypes
|
||||
);
|
||||
|
||||
const version = useDarkStore((state) => state.version);
|
||||
|
|
@ -138,7 +138,7 @@ export default function ComponentsComponent({
|
|||
setSuccessData,
|
||||
setSelectedFlowsComponentsCards,
|
||||
handleSelectAll,
|
||||
cardTypes,
|
||||
cardTypes
|
||||
);
|
||||
|
||||
const { handleSelectOptionsChange } = useSelectOptionsChange(
|
||||
|
|
@ -146,7 +146,7 @@ export default function ComponentsComponent({
|
|||
setErrorData,
|
||||
setOpenDelete,
|
||||
handleDuplicate,
|
||||
handleExport,
|
||||
handleExport
|
||||
);
|
||||
|
||||
const { handleDeleteMultiple } = useDeleteMultipleFlows(
|
||||
|
|
@ -158,21 +158,21 @@ export default function ComponentsComponent({
|
|||
myCollectionId,
|
||||
getFolderById,
|
||||
setSuccessData,
|
||||
setErrorData,
|
||||
setErrorData
|
||||
);
|
||||
|
||||
useSelectedFlows(entireFormValues, setSelectedFlowsComponentsCards);
|
||||
|
||||
const descriptionModal = useDescriptionModal(
|
||||
selectedFlowsComponentsCards,
|
||||
type,
|
||||
type
|
||||
);
|
||||
|
||||
const getTotalRowsCount = () => {
|
||||
if (type === "all") return allFlows?.length;
|
||||
|
||||
return allFlows?.filter(
|
||||
(f) => (f.is_component ?? false) === (type === "component"),
|
||||
(f) => (f.is_component ?? false) === (type === "component")
|
||||
)?.length;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import { useState } from "react";
|
||||
import IconComponent from "../../../../components/genericIconComponent";
|
||||
import IconComponent, {
|
||||
ForwardedIconComponent,
|
||||
} from "../../../../components/genericIconComponent";
|
||||
import ShadTooltip from "../../../../components/shadTooltipComponent";
|
||||
import { Button } from "../../../../components/ui/button";
|
||||
import { Checkbox } from "../../../../components/ui/checkbox";
|
||||
import { cn } from "../../../../utils/utils";
|
||||
import { Button } from "../../../../components/ui/button";
|
||||
|
||||
type HeaderComponentProps = {
|
||||
handleSelectAll: (select) => void;
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export default function ApiKeysPage() {
|
|||
userData,
|
||||
setLoadingKeys,
|
||||
keysList,
|
||||
setUserId,
|
||||
setUserId
|
||||
);
|
||||
|
||||
function resetFilter() {
|
||||
|
|
@ -36,7 +36,7 @@ export default function ApiKeysPage() {
|
|||
selectedRows,
|
||||
resetFilter,
|
||||
setSuccessData,
|
||||
setErrorData,
|
||||
setErrorData
|
||||
);
|
||||
|
||||
const columnDefs = getColumnDefs();
|
||||
|
|
|
|||
|
|
@ -16,18 +16,14 @@ import {
|
|||
import { BuildStatus } from "../constants/enums";
|
||||
import { getFlowPool } from "../controllers/API";
|
||||
import { VertexBuildTypeAPI } from "../types/api";
|
||||
import { ChatInputType, ChatOutputType } from "../types/chat";
|
||||
import {
|
||||
NodeDataType,
|
||||
NodeType,
|
||||
sourceHandleType,
|
||||
targetHandleType,
|
||||
} from "../types/flow";
|
||||
import {
|
||||
ChatOutputType,
|
||||
FlowStoreType,
|
||||
VertexLayerElementType,
|
||||
chatInputType,
|
||||
} from "../types/zustand/flow";
|
||||
import { FlowStoreType, VertexLayerElementType } from "../types/zustand/flow";
|
||||
import { buildVertices } from "../utils/buildUtils";
|
||||
import {
|
||||
checkChatInput,
|
||||
|
|
@ -77,8 +73,8 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
},
|
||||
updateFlowPool: (
|
||||
nodeId: string,
|
||||
data: VertexBuildTypeAPI | ChatOutputType | chatInputType,
|
||||
buildId?: string,
|
||||
data: VertexBuildTypeAPI | ChatOutputType | ChatInputType,
|
||||
buildId?: string
|
||||
) => {
|
||||
let newFlowPool = cloneDeep({ ...get().flowPool });
|
||||
if (!newFlowPool[nodeId]) {
|
||||
|
|
@ -94,9 +90,9 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
}
|
||||
//update data results
|
||||
else {
|
||||
newFlowPool[nodeId][index].data.messages[0] = data as
|
||||
newFlowPool[nodeId][index].data.message = data as
|
||||
| ChatOutputType
|
||||
| chatInputType;
|
||||
| ChatInputType;
|
||||
}
|
||||
}
|
||||
get().setFlowPool(newFlowPool);
|
||||
|
|
@ -171,7 +167,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
flowsManager.autoSaveCurrentFlow(
|
||||
newChange,
|
||||
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(
|
||||
get().nodes,
|
||||
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 node;
|
||||
}),
|
||||
})
|
||||
);
|
||||
},
|
||||
getNode: (id: string) => {
|
||||
|
|
@ -216,8 +212,8 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
get().nodes.filter((node) =>
|
||||
typeof nodeId === "string"
|
||||
? node.id !== nodeId
|
||||
: !nodeId.includes(node.id),
|
||||
),
|
||||
: !nodeId.includes(node.id)
|
||||
)
|
||||
);
|
||||
},
|
||||
deleteEdge: (edgeId) => {
|
||||
|
|
@ -225,8 +221,8 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
get().edges.filter((edge) =>
|
||||
typeof edgeId === "string"
|
||||
? edge.id !== edgeId
|
||||
: !edgeId.includes(edge.id),
|
||||
),
|
||||
: !edgeId.includes(edge.id)
|
||||
)
|
||||
);
|
||||
},
|
||||
paste: (selection, position) => {
|
||||
|
|
@ -292,7 +288,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
let source = idsMap[edge.source];
|
||||
let target = idsMap[edge.target];
|
||||
const sourceHandleObject: sourceHandleType = scapeJSONParse(
|
||||
edge.sourceHandle!,
|
||||
edge.sourceHandle!
|
||||
);
|
||||
let sourceHandle = scapedJSONStringfy({
|
||||
...sourceHandleObject,
|
||||
|
|
@ -302,7 +298,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
|
||||
edge.data.sourceHandle = sourceHandleObject;
|
||||
const targetHandleObject: targetHandleType = scapeJSONParse(
|
||||
edge.targetHandle!,
|
||||
edge.targetHandle!
|
||||
);
|
||||
let targetHandle = scapedJSONStringfy({
|
||||
...targetHandleObject,
|
||||
|
|
@ -323,7 +319,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
className: "stroke-gray-900 ",
|
||||
selected: false,
|
||||
},
|
||||
newEdges.map((edge) => ({ ...edge, selected: false })),
|
||||
newEdges.map((edge) => ({ ...edge, selected: false }))
|
||||
);
|
||||
});
|
||||
get().setEdges(newEdges);
|
||||
|
|
@ -342,10 +338,10 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
});
|
||||
|
||||
const newNodes = get().nodes.filter(
|
||||
(node) => !nodesIdsSelected.includes(node.id),
|
||||
(node) => !nodesIdsSelected.includes(node.id)
|
||||
);
|
||||
const newEdges = get().edges.filter(
|
||||
(edge) => !edgesIdsSelected.includes(edge.id),
|
||||
(edge) => !edgesIdsSelected.includes(edge.id)
|
||||
);
|
||||
|
||||
set({ nodes: newNodes, edges: newEdges });
|
||||
|
|
@ -403,7 +399,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
style: { stroke: "#555" },
|
||||
className: "stroke-foreground stroke-connection",
|
||||
},
|
||||
oldEdges,
|
||||
oldEdges
|
||||
);
|
||||
|
||||
return newEdges;
|
||||
|
|
@ -413,7 +409,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
.autoSaveCurrentFlow(
|
||||
get().nodes,
|
||||
newEdges,
|
||||
get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 },
|
||||
get().reactFlowInstance?.getViewport() ?? { x: 0, y: 0, zoom: 1 }
|
||||
);
|
||||
},
|
||||
unselectAll: () => {
|
||||
|
|
@ -448,7 +444,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
function validateSubgraph(nodes: string[]) {
|
||||
const errorsObjs = validateNodes(
|
||||
get().nodes.filter((node) => nodes.includes(node.id)),
|
||||
get().edges,
|
||||
get().edges
|
||||
);
|
||||
|
||||
const errors = errorsObjs.map((obj) => obj.errors).flat();
|
||||
|
|
@ -467,13 +463,13 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
function handleBuildUpdate(
|
||||
vertexBuildData: VertexBuildTypeAPI,
|
||||
status: BuildStatus,
|
||||
runId: string,
|
||||
runId: string
|
||||
) {
|
||||
if (vertexBuildData && vertexBuildData.inactivated_vertices) {
|
||||
get().removeFromVerticesBuild(vertexBuildData.inactivated_vertices);
|
||||
get().updateBuildStatus(
|
||||
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
|
||||
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(
|
||||
(vertex) =>
|
||||
!vertexBuildData.inactivated_vertices?.includes(vertex.id),
|
||||
(vertex) => !vertexBuildData.inactivated_vertices?.includes(vertex)
|
||||
);
|
||||
const nextVertices: VertexLayerElementType[] = zip(
|
||||
next_vertices_ids,
|
||||
top_level_vertices,
|
||||
top_level_vertices
|
||||
).map(([id, reference]) => ({ id: id!, reference }));
|
||||
|
||||
const newLayers = [
|
||||
|
|
@ -518,8 +513,8 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
}
|
||||
|
||||
get().addDataToFlowPool(
|
||||
{ ...vertexBuildData, buildId: runId },
|
||||
vertexBuildData.id,
|
||||
{ ...vertexBuildData, run_id: runId },
|
||||
vertexBuildData.id
|
||||
);
|
||||
|
||||
useFlowStore.getState().updateBuildStatus([vertexBuildData.id], status);
|
||||
|
|
@ -528,7 +523,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
const newFlowBuildStatus = { ...get().flowBuildStatus };
|
||||
// filter out the vertices that are not status
|
||||
const verticesToUpdate = verticesIds?.filter(
|
||||
(id) => newFlowBuildStatus[id]?.status !== BuildStatus.BUILT,
|
||||
(id) => newFlowBuildStatus[id]?.status !== BuildStatus.BUILT
|
||||
);
|
||||
|
||||
if (verticesToUpdate) {
|
||||
|
|
@ -598,7 +593,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
verticesLayers: VertexLayerElementType[][];
|
||||
runId: string;
|
||||
verticesToRun: string[];
|
||||
} | null,
|
||||
} | null
|
||||
) => {
|
||||
set({ verticesBuild: vertices });
|
||||
},
|
||||
|
|
@ -623,7 +618,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
// that are going to be built
|
||||
verticesIds: get().verticesBuild!.verticesIds.filter(
|
||||
// keep the vertices that are not in the list of vertices to remove
|
||||
(vertex) => !vertices.includes(vertex),
|
||||
(vertex) => !vertices.includes(vertex)
|
||||
),
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Edge, Node, Viewport } from "reactflow";
|
||||
import { ChatOutputType, chatInputType } from "../chat";
|
||||
import { ChatInputType, ChatOutputType } from "../chat";
|
||||
import { FlowType } from "../flow";
|
||||
//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 };
|
||||
|
|
@ -154,15 +154,15 @@ export type VertexBuildTypeAPI = {
|
|||
data: VertexDataTypeAPI;
|
||||
timestamp: string;
|
||||
params: any;
|
||||
messages: ChatOutputType[] | chatInputType[];
|
||||
messages: ChatOutputType[] | ChatInputType[];
|
||||
};
|
||||
|
||||
// data is the object received by the API
|
||||
// it has results, artifacts, timedelta, duration
|
||||
export type VertexDataTypeAPI = {
|
||||
results: { [key: string]: string };
|
||||
logs: any[];
|
||||
messages: ChatOutputType[] | chatInputType[];
|
||||
logs: { message: any; type: string }[];
|
||||
message: ChatOutputType | ChatInputType;
|
||||
inactive?: boolean;
|
||||
timedelta?: number;
|
||||
duration?: string;
|
||||
|
|
|
|||
|
|
@ -22,15 +22,19 @@ export type ChatOutputType = {
|
|||
files?: Array<{ path: string; type: string; name: string }>;
|
||||
};
|
||||
|
||||
export type chatInputType = {
|
||||
result: string;
|
||||
export type ChatInputType = {
|
||||
message: string;
|
||||
sender: string;
|
||||
sender_name: string;
|
||||
stream_url?: string;
|
||||
files?: Array<{ path: string; type: string; name: string }>;
|
||||
};
|
||||
|
||||
export type FlowPoolObjectType = {
|
||||
timestamp: string;
|
||||
valid: boolean;
|
||||
// list of chat outputs or list of chat inputs
|
||||
messages: Array<ChatOutputType | chatInputType> | [];
|
||||
data: { artifacts?: any; results: any | ChatOutputType | chatInputType };
|
||||
messages: Array<ChatOutputType | ChatInputType> | [];
|
||||
data: { artifacts: any; results: any | ChatOutputType | ChatInputType };
|
||||
id: string;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -487,7 +487,12 @@ export type headerFlowsType = {
|
|||
style?: FlowStyleType;
|
||||
};
|
||||
|
||||
export type chatInputType = {
|
||||
export type ChatInputType = {
|
||||
isDragging: boolean;
|
||||
files: FilePreviewType[];
|
||||
setFiles: (
|
||||
files: FilePreviewType[] | ((prev: FilePreviewType[]) => FilePreviewType[])
|
||||
) => void;
|
||||
chatValue: string;
|
||||
inputRef: {
|
||||
current: any;
|
||||
|
|
@ -526,6 +531,7 @@ export type fileCardPropsType = {
|
|||
fileName: string;
|
||||
content: string;
|
||||
fileType: string;
|
||||
showFile?: boolean;
|
||||
};
|
||||
|
||||
export type nodeToolbarPropsType = {
|
||||
|
|
|
|||
|
|
@ -9,28 +9,16 @@ import {
|
|||
} from "reactflow";
|
||||
import { BuildStatus } from "../../../constants/enums";
|
||||
import { VertexBuildTypeAPI } from "../../api";
|
||||
import { ChatInputType, ChatOutputType } from "../../chat";
|
||||
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 = {
|
||||
timestamp: string;
|
||||
valid: boolean;
|
||||
messages: Array<ChatOutputType | chatInputType> | [];
|
||||
messages: Array<ChatOutputType | ChatInputType> | [];
|
||||
data: {
|
||||
artifacts: any | ChatOutputType | chatInputType;
|
||||
results: any | ChatOutputType | chatInputType;
|
||||
artifacts: any | ChatOutputType | ChatInputType;
|
||||
results: any | ChatOutputType | ChatInputType;
|
||||
};
|
||||
duration?: string;
|
||||
progress?: number;
|
||||
|
|
@ -45,8 +33,8 @@ export type FlowPoolObjectTypeNew = {
|
|||
timestamp: string;
|
||||
valid: boolean;
|
||||
data: {
|
||||
logs?: any | ChatOutputType | chatInputType;
|
||||
results: any | ChatOutputType | chatInputType;
|
||||
logs?: any | ChatOutputType | ChatInputType;
|
||||
results: any | ChatOutputType | ChatInputType;
|
||||
};
|
||||
duration?: string;
|
||||
progress?: number;
|
||||
|
|
@ -98,7 +86,7 @@ export type FlowStoreType = {
|
|||
state:
|
||||
| FlowState
|
||||
| undefined
|
||||
| ((oldState: FlowState | undefined) => FlowState),
|
||||
| ((oldState: FlowState | undefined) => FlowState)
|
||||
) => void;
|
||||
nodes: Node[];
|
||||
edges: Edge[];
|
||||
|
|
@ -106,11 +94,11 @@ export type FlowStoreType = {
|
|||
onEdgesChange: OnEdgesChange;
|
||||
setNodes: (
|
||||
update: Node[] | ((oldState: Node[]) => Node[]),
|
||||
skipSave?: boolean,
|
||||
skipSave?: boolean
|
||||
) => void;
|
||||
setEdges: (
|
||||
update: Edge[] | ((oldState: Edge[]) => Edge[]),
|
||||
skipSave?: boolean,
|
||||
skipSave?: boolean
|
||||
) => void;
|
||||
setNode: (id: string, update: Node | ((oldState: Node) => Node)) => void;
|
||||
getNode: (id: string) => Node | undefined;
|
||||
|
|
@ -118,12 +106,12 @@ export type FlowStoreType = {
|
|||
deleteEdge: (edgeId: string | Array<string>) => void;
|
||||
paste: (
|
||||
selection: { nodes: any; edges: any },
|
||||
position: { x: number; y: number; paneX?: number; paneY?: number },
|
||||
position: { x: number; y: number; paneX?: number; paneY?: number }
|
||||
) => void;
|
||||
lastCopiedSelection: { nodes: any; edges: any } | null;
|
||||
setLastCopiedSelection: (
|
||||
newSelection: { nodes: any; edges: any } | null,
|
||||
isCrop?: boolean,
|
||||
isCrop?: boolean
|
||||
) => void;
|
||||
cleanFlow: () => void;
|
||||
setFilterEdge: (newState) => void;
|
||||
|
|
@ -150,7 +138,7 @@ export type FlowStoreType = {
|
|||
verticesLayers: VertexLayerElementType[][];
|
||||
runId: string;
|
||||
verticesToRun: string[];
|
||||
} | null,
|
||||
} | null
|
||||
) => void;
|
||||
addToVerticesBuild: (vertices: string[]) => void;
|
||||
removeFromVerticesBuild: (vertices: string[]) => void;
|
||||
|
|
@ -167,8 +155,8 @@ export type FlowStoreType = {
|
|||
};
|
||||
updateFlowPool: (
|
||||
nodeId: string,
|
||||
data: VertexBuildTypeAPI | ChatOutputType | chatInputType,
|
||||
buildId?: string,
|
||||
data: VertexBuildTypeAPI | ChatOutputType | ChatInputType,
|
||||
buildId?: string
|
||||
) => void;
|
||||
getNodePosition: (nodeId: string) => { x: number; y: number };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export type chatInputType = {
|
||||
export type ChatInputType = {
|
||||
result: string;
|
||||
};
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ export type FlowPoolObjectType = {
|
|||
timestamp: string;
|
||||
valid: boolean;
|
||||
params: any;
|
||||
data: { artifacts: any; results: any | ChatOutputType | chatInputType };
|
||||
data: { artifacts: any; results: any | ChatOutputType | ChatInputType };
|
||||
id: string;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ import {
|
|||
Package2,
|
||||
Palette,
|
||||
Paperclip,
|
||||
PaperclipIcon,
|
||||
Pencil,
|
||||
PencilLine,
|
||||
Pin,
|
||||
|
|
@ -533,6 +534,7 @@ export const nodeIconsLucide: iconsType = {
|
|||
FolderPlusIcon,
|
||||
FolderIcon,
|
||||
Discord: FaDiscord,
|
||||
PaperclipIcon,
|
||||
RotateCcw,
|
||||
Settings,
|
||||
Streamlit,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue