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