Merge remote-tracking branch 'origin/two_edges' into new_Group

This commit is contained in:
anovazzi1 2024-06-07 15:18:57 -03:00
commit 8742c87bfe
291 changed files with 9361 additions and 6719 deletions

View file

@ -121,7 +121,7 @@ def run(
),
):
"""
Run the Langflow.
Run Langflow.
"""
configure(log_level=log_level, log_file=log_file)

View file

@ -9,7 +9,7 @@ from loguru import logger
from sqlmodel import Session, col, select
from langflow.api.utils import remove_api_keys, validate_is_component
from langflow.api.v1.schemas import FlowListCreate, FlowListIds, FlowListRead
from langflow.api.v1.schemas import FlowListCreate, FlowListRead
from langflow.initial_setup.setup import STARTER_FOLDER_NAME
from langflow.services.auth.utils import get_current_active_user
from langflow.services.database.models.flow import Flow, FlowCreate, FlowRead, FlowUpdate
@ -258,9 +258,9 @@ async def download_file(
return FlowListRead(flows=flows)
@router.post("/multiple_delete/")
@router.delete("/")
async def delete_multiple_flows(
flow_ids: FlowListIds, user: User = Depends(get_current_active_user), db: Session = Depends(get_session)
flow_ids: List[UUID], user: User = Depends(get_current_active_user), db: Session = Depends(get_session)
):
"""
Delete multiple flows by their IDs.
@ -274,9 +274,7 @@ async def delete_multiple_flows(
"""
try:
deleted_flows = db.exec(
select(Flow).where(col(Flow.id).in_(flow_ids.flow_ids)).where(Flow.user_id == user.id)
).all()
deleted_flows = db.exec(select(Flow).where(col(Flow.id).in_(flow_ids)).where(Flow.user_id == user.id)).all()
for flow in deleted_flows:
db.delete(flow)
db.commit()

View file

@ -1,9 +1,9 @@
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from langflow.services.deps import get_monitor_service
from langflow.services.monitor.schema import (
MessageModelRequest,
MessageModelResponse,
TransactionModelResponse,
VertexBuildMapModel,
@ -66,6 +66,44 @@ async def get_messages(
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/messages", status_code=204)
async def delete_messages(
message_ids: List[int],
monitor_service: MonitorService = Depends(get_monitor_service),
):
try:
monitor_service.delete_messages(message_ids=message_ids)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/messages/{message_id}", response_model=MessageModelResponse)
async def update_message(
message_id: str,
message: MessageModelRequest,
monitor_service: MonitorService = Depends(get_monitor_service),
):
try:
message_dict = message.model_dump(exclude_none=True)
message_dict.pop("index", None)
monitor_service.update_message(message_id=message_id, **message_dict)
return MessageModelResponse(index=message_id, **message_dict)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/messages/session/{session_id}", status_code=204)
async def delete_messages_session(
session_id: str,
monitor_service: MonitorService = Depends(get_monitor_service),
):
try:
monitor_service.delete_messages_session(session_id=session_id)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/transactions", response_model=List[TransactionModelResponse])
async def get_transactions(
source: Optional[str] = Query(None),

View file

@ -15,10 +15,25 @@ import shlex
from collections import OrderedDict, namedtuple
from http.cookies import SimpleCookie
from uncurl.api import parser # type: ignore
parser.add_argument("-x", "--proxy", default={})
parser.add_argument("-U", "--proxy-user", default="")
ParsedArgs = namedtuple(
"ParsedContext",
[
"command",
"url",
"data",
"data_binary",
"method",
"headers",
"compressed",
"insecure",
"user",
"include",
"silent",
"proxy",
"proxy_user",
"cookies",
],
)
ParsedContext = namedtuple("ParsedContext", ["method", "url", "data", "headers", "cookies", "verify", "auth", "proxy"])
@ -27,24 +42,89 @@ def normalize_newlines(multiline_text):
return multiline_text.replace(" \\\n", " ")
def parse_curl_command(curl_command):
tokens = shlex.split(normalize_newlines(curl_command))
tokens = [token for token in tokens if token and token != " "]
if "curl" not in tokens[0]:
raise ValueError("Invalid curl command")
args_template = {
"command": None,
"url": None,
"data": None,
"data_binary": None,
"method": "get",
"headers": [],
"compressed": False,
"insecure": False,
"user": (),
"include": False,
"silent": False,
"proxy": None,
"proxy_user": None,
"cookies": {},
}
args = args_template.copy()
i = 0
while i < len(tokens):
token = tokens[i]
if token == "-X":
i += 1
args["method"] = tokens[i].lower()
elif token in ("-d", "--data"):
i += 1
args["data"] = tokens[i]
args["method"] = "post"
elif token in ("-b", "--data-binary", "--data-raw"):
i += 1
args["data_binary"] = tokens[i]
args["method"] = "post"
elif token in ("-H", "--header"):
i += 1
args["headers"].append(tokens[i])
elif token == "--compressed":
args["compressed"] = True
elif token in ("-k", "--insecure"):
args["insecure"] = True
elif token in ("-u", "--user"):
i += 1
args["user"] = tuple(tokens[i].split(":"))
elif token in ("-I", "--include"):
args["include"] = True
elif token in ("-s", "--silent"):
args["silent"] = True
elif token in ("-x", "--proxy"):
i += 1
args["proxy"] = tokens[i]
elif token in ("-U", "--proxy-user"):
i += 1
args["proxy_user"] = tokens[i]
elif not token.startswith("-"):
if args["command"] is None:
args["command"] = token
else:
args["url"] = token
i += 1
return ParsedArgs(**args)
def parse_context(curl_command):
method = "get"
tokens = shlex.split(normalize_newlines(curl_command))
tokens = [token for token in tokens if token and token != " "]
parsed_args = parser.parse_args(tokens)
parsed_args: ParsedArgs = parse_curl_command(curl_command)
post_data = parsed_args.data or parsed_args.data_binary
if post_data:
method = "post"
if parsed_args.X:
method = parsed_args.X.lower()
if parsed_args.method:
method = parsed_args.method.lower()
cookie_dict = OrderedDict()
quoted_headers = OrderedDict()
for curl_header in parsed_args.header:
for curl_header in parsed_args.headers:
if curl_header.startswith(":"):
occurrence = [m.start() for m in re.finditer(":", curl_header)]
header_key, header_value = curl_header[: occurrence[1]], curl_header[occurrence[1] + 1 :]

View file

@ -92,7 +92,7 @@ def read_text_file(file_path: str) -> str:
with open(file_path, "rb") as f:
raw_data = f.read()
result = chardet.detect(raw_data)
encoding = result['encoding']
encoding = result["encoding"]
with open(file_path, "r", encoding=encoding) as f:
return f.read()

View file

@ -0,0 +1,25 @@
import re
from langflow.custom import Component
from langflow.field_typing import Input, Output, Text
class CodeBlockExtractor(Component):
display_name = "Code Block Extractor"
description = "Extracts code block from text."
inputs = [Input(name="text", field_type=Text, description="Text to extract code blocks from.")]
outputs = [Output(name="code_block", display_name="Code Block", method="get_code_block")]
def get_code_block(self) -> Text:
text = self.text.strip()
# Extract code block
# It may start with ``` or ```language
# It may end with ```
pattern = r"^```(?:\w+)?\s*\n(.*?)(?=^```)```"
match = re.search(pattern, text, re.MULTILINE)
code_block = ""
if match:
code_block = match.group(1)
return code_block

View file

@ -1,27 +0,0 @@
from .AstraDBSearch import AstraDBSearchComponent
from .ChromaSearch import ChromaSearchComponent
from .FAISSSearch import FAISSSearchComponent
from .MongoDBAtlasVectorSearch import MongoDBAtlasSearchComponent
from .PineconeSearch import PineconeSearchComponent
from .QdrantSearch import QdrantSearchComponent
from .RedisSearch import RedisSearchComponent
from .SupabaseVectorStoreSearch import SupabaseSearchComponent
from .VectaraSearch import VectaraSearchComponent
from .WeaviateSearch import WeaviateSearchVectorStore
from .pgvectorSearch import PGVectorSearchComponent
from .Couchbase import CouchbaseSearchComponent # type: ignore
__all__ = [
"AstraDBSearchComponent",
"ChromaSearchComponent",
"CouchbaseSearchComponent",
"FAISSSearchComponent",
"MongoDBAtlasSearchComponent",
"PineconeSearchComponent",
"QdrantSearchComponent",
"RedisSearchComponent",
"SupabaseSearchComponent",
"VectaraSearchComponent",
"WeaviateSearchVectorStore",
"PGVectorSearchComponent",
]

View file

@ -1,28 +0,0 @@
from .AstraDB import AstraDBVectorStoreComponent
from .Chroma import ChromaComponent
from .FAISS import FAISSComponent
from .MongoDBAtlasVector import MongoDBAtlasComponent
from .Pinecone import PineconeComponent
from .Qdrant import QdrantComponent
from .Redis import RedisComponent
from .SupabaseVectorStore import SupabaseComponent
from .Vectara import VectaraComponent
from .Weaviate import WeaviateVectorStoreComponent
from .pgvector import PGVectorComponent
from .Couchbase import CouchbaseComponent
__all__ = [
"AstraDBVectorStoreComponent",
"ChromaComponent",
"CouchbaseComponent",
"FAISSComponent",
"MongoDBAtlasComponent",
"PineconeComponent",
"QdrantComponent",
"RedisComponent",
"SupabaseComponent",
"VectaraComponent",
"WeaviateVectorStoreComponent",
"base",
"PGVectorComponent",
]

View file

@ -297,7 +297,7 @@ class CodeParser:
bases = self.execute_and_inspect_classes(self.code)
except Exception as e:
# If the code cannot be executed, return an empty list
logger.exception(e)
logger.debug(e)
bases = []
raise e
return bases

View file

@ -71,13 +71,13 @@ class Component(CustomComponent):
for output in self.outputs:
# Build the output if it's connected to some other vertex
# or if it's not connected to any vertex
if not vertex.outgoing_edges or output.display_name in vertex.edges_source_names:
if not vertex.outgoing_edges or output.name in vertex.edges_source_names:
method: Callable | Awaitable = getattr(self, output.method)
result = method()
# If the method is asynchronous, we need to await it
if inspect.iscoroutinefunction(method):
result = await result
_results[output.display_name] = result
_results[output.name] = result
self._results = _results
return _results

View file

@ -79,7 +79,8 @@ class DirectoryReader:
component_tuple = (*build_component(component), component)
components.append(component_tuple)
except Exception as e:
logger.error(f"Error while loading component { component['name']}: {e}")
logger.debug(f"Error while loading component { component['name']}")
logger.debug(e)
continue
items.append({"name": menu["name"], "path": menu["path"], "components": components})
filtered = [menu for menu in items if menu["components"]]
@ -265,8 +266,7 @@ class DirectoryReader:
if validation_result:
try:
output_types = self.get_output_types_from_code(result_content)
except Exception as exc:
logger.exception(f"Error while getting output types from code: {str(exc)}")
except Exception:
output_types = [component_name_camelcase]
else:
output_types = [component_name_camelcase]

View file

@ -29,18 +29,26 @@ from .constants import (
from .range_spec import RangeSpec
def _import_template_field():
def _import_input_class():
from langflow.template.field.base import Input
return Input
def _import_output_class():
from langflow.template.field.base import Output
return Output
def __getattr__(name: str) -> Any:
# This is to avoid circular imports
if name == "Input":
return _import_template_field()
return _import_input_class()
elif name == "RangeSpec":
return RangeSpec
elif name == "Output":
return _import_output_class()
# The other names should work as if they were imported from constants
# Import the constants module langflow.field_typing.constants
from . import constants

View file

@ -6,7 +6,7 @@ from langchain.memory.chat_memory import BaseChatMemory
from langchain_core.document_loaders import BaseLoader
from langchain_core.documents import Document
from langchain_core.embeddings import Embeddings
from langchain_core.language_models import BaseLLM, BaseLanguageModel
from langchain_core.language_models import BaseLanguageModel, BaseLLM
from langchain_core.memory import BaseMemory
from langchain_core.output_parsers import BaseOutputParser
from langchain_core.prompts import BasePromptTemplate, ChatPromptTemplate, PromptTemplate

View file

@ -156,7 +156,7 @@ class InterfaceVertex(ComponentVertex):
if isinstance(message, str):
message = unescape_string(message)
stream_url = None
text_output = self.results["Message"]
text_output = self.results["message"]
if isinstance(text_output, (AIMessage, AIMessageChunk)):
artifacts = ChatOutputResponse.from_message(
text_output,
@ -173,8 +173,8 @@ class InterfaceVertex(ComponentVertex):
elif isinstance(message, (AsyncIterator, Iterator)):
stream_url = self.build_stream_url()
message = ""
self.results["Message"] = message
self.results["Record"].message = message
self.results["message"] = message
self.results["record"].message = message
self._built_object = self.results
elif not isinstance(text_output, str):
message = str(text_output)

View file

@ -90,7 +90,9 @@ async def run_flow(
fallback_to_env_vars = get_settings_service().settings.fallback_to_env_var
return await graph.arun(inputs_list, inputs_components=inputs_components, types=types, fallback_to_env_vars=fallback_to_env_vars)
return await graph.arun(
inputs_list, inputs_components=inputs_components, types=types, fallback_to_env_vars=fallback_to_env_vars
)
def generate_function_for_flow(
@ -249,7 +251,7 @@ def get_flow_by_id_or_endpoint_name(
flow = db.get(Flow, flow_id)
except ValueError:
endpoint_name = flow_id_or_name
stmt = select(Flow).where(Flow.name == endpoint_name)
stmt = select(Flow).where(Flow.endpoint_name == endpoint_name)
if user_id:
stmt = stmt.where(Flow.user_id == user_id)
flow = db.exec(stmt).first()

View file

@ -599,6 +599,7 @@
"Text"
],
"selected": "Text",
"display_name": "Message",
"name": "message",
"method": "text_response"
},
@ -607,6 +608,7 @@
"Record"
],
"selected": "Record",
"display_name": "Record",
"name": "record",
"method": "record_response"
}
@ -775,6 +777,7 @@
"Text"
],
"selected": "Text",
"display_name": "Message",
"name": "message",
"method": "text_response"
},
@ -783,6 +786,7 @@
"Record"
],
"selected": "Record",
"display_name": "Record",
"name": "record",
"method": "record_response"
}

View file

@ -459,6 +459,7 @@
"Text"
],
"selected": "Text",
"display_name": "Message",
"name": "message",
"method": "text_response"
},
@ -467,6 +468,7 @@
"Record"
],
"selected": "Record",
"display_name": "Record",
"name": "record",
"method": "record_response"
}

View file

@ -431,6 +431,7 @@
"Text"
],
"selected": "Text",
"display_name": "Message",
"name": "message",
"method": "text_response"
},
@ -439,6 +440,7 @@
"Record"
],
"selected": "Record",
"display_name": "Record",
"name": "record",
"method": "record_response"
}
@ -631,6 +633,7 @@
"Text"
],
"selected": "Text",
"display_name": "Message",
"name": "message",
"method": "text_response"
},
@ -639,6 +642,7 @@
"Record"
],
"selected": "Record",
"display_name": "Record",
"name": "record",
"method": "record_response"
}

View file

@ -156,6 +156,7 @@
"Text"
],
"selected": "Text",
"display_name": "Message",
"name": "message",
"method": "text_response"
},
@ -164,6 +165,7 @@
"Record"
],
"selected": "Record",
"display_name": "Record",
"name": "record",
"method": "record_response"
}
@ -356,6 +358,7 @@
"Text"
],
"selected": "Text",
"display_name": "Message",
"name": "message",
"method": "text_response"
},
@ -364,6 +367,7 @@
"Record"
],
"selected": "Record",
"display_name": "Record",
"name": "record",
"method": "record_response"
}

View file

@ -437,6 +437,7 @@
"Text"
],
"selected": "Text",
"display_name": "Message",
"name": "message",
"method": "text_response"
},
@ -445,6 +446,7 @@
"Record"
],
"selected": "Record",
"display_name": "Record",
"name": "record",
"method": "record_response"
}
@ -634,6 +636,7 @@
"Text"
],
"selected": "Text",
"display_name": "Message",
"name": "message",
"method": "text_response"
},
@ -642,6 +645,7 @@
"Record"
],
"selected": "Record",
"display_name": "Record",
"name": "record",
"method": "record_response"
}
@ -755,6 +759,7 @@
"Text"
],
"selected": "Text",
"display_name": "Text",
"name": "text",
"method": "text_response"
}

View file

@ -154,6 +154,7 @@
"Text"
],
"selected": "Text",
"display_name": "Message",
"name": "message",
"method": "text_response"
},
@ -162,6 +163,7 @@
"Record"
],
"selected": "Record",
"display_name": "Record",
"name": "record",
"method": "record_response"
}
@ -1454,6 +1456,7 @@
"Text"
],
"selected": "Text",
"display_name": "Message",
"name": "message",
"method": "text_response"
},
@ -1462,6 +1465,7 @@
"Record"
],
"selected": "Record",
"display_name": "Record",
"name": "record",
"method": "record_response"
}

View file

@ -1,3 +1,4 @@
from .load import load_flow_from_json, run_flow_from_json # noqa: F401
from .load import load_flow_from_json, run_flow_from_json
from .utils import upload_file, get_flow
__all__ = ["load_flow_from_json", "run_flow_from_json"]
__all__ = ["load_flow_from_json", "run_flow_from_json", "upload_file", "get_flow"]

View file

@ -0,0 +1,89 @@
import httpx
from langflow.services.database.models.flow.model import FlowBase
def upload(file_path, host, flow_id):
"""
Upload a file to Langflow and return the file path.
Args:
file_path (str): The path to the file to be uploaded.
host (str): The host URL of Langflow.
flow_id (UUID): The ID of the flow to which the file belongs.
Returns:
dict: A dictionary containing the file path.
Raises:
Exception: If an error occurs during the upload process.
"""
try:
url = f"{host}/api/v1/upload/{flow_id}"
response = httpx.post(url, files={"file": open(file_path, "rb")})
if response.status_code == 200:
return response.json()
else:
raise Exception(f"Error uploading file: {response.status_code}")
except Exception as e:
raise Exception(f"Error uploading file: {e}")
def upload_file(file_path, host, flow_id, components, tweaks={}):
"""
Upload a file to Langflow and return the file path.
Args:
file_path (str): The path to the file to be uploaded.
host (str): The host URL of Langflow.
port (int): The port number of Langflow.
flow_id (UUID): The ID of the flow to which the file belongs.
components (str): List of component IDs or names that need the file.
tweaks (dict): A dictionary of tweaks to be applied to the file.
Returns:
dict: A dictionary containing the file path and any tweaks that were applied.
Raises:
Exception: If an error occurs during the upload process.
"""
try:
response = upload(file_path, host, flow_id)
if response["file_path"]:
for component in components:
if isinstance(component, str):
tweaks[component] = {"file_path": response["file_path"]}
else:
raise ValueError(f"Component ID or name must be a string. Got {type(component)}")
return tweaks
else:
raise ValueError("Error uploading file")
except Exception as e:
raise ValueError(f"Error uploading file: {e}")
def get_flow(url: str, flow_id: str):
"""Get the details of a flow from Langflow.
Args:
url (str): The host URL of Langflow.
port (int): The port number of Langflow.
flow_id (UUID): The ID of the flow to retrieve.
Returns:
dict: A dictionary containing the details of the flow.
Raises:
Exception: If an error occurs during the retrieval process.
"""
try:
flow_url = f"{url}/api/v1/flows/{flow_id}"
response = httpx.get(flow_url)
if response.status_code == 200:
json_response = response.json()
flow = FlowBase(**json_response).model_dump()
return flow
else:
raise Exception(f"Error retrieving flow: {response.status_code}")
except Exception as e:
raise Exception(f"Error retrieving flow: {e}")

View file

@ -59,7 +59,7 @@ async def run_graph_internal(
outputs or [],
stream=stream,
session_id=session_id_str or "",
fallback_to_env_vars=fallback_to_env_vars
fallback_to_env_vars=fallback_to_env_vars,
)
if session_id_str and session_service:
await session_service.update_session(session_id_str, (graph, artifacts))

View file

@ -55,6 +55,7 @@ class ApiKeyRead(ApiKeyBase):
id: UUID
api_key: str = Field(schema_extra={"validate_default": True})
user_id: UUID = Field()
created_at: datetime = Field()
@field_validator("api_key")
@classmethod

View file

@ -29,7 +29,6 @@ class FlowBase(SQLModel):
is_component: Optional[bool] = Field(default=False, nullable=True)
updated_at: Optional[datetime] = Field(default_factory=lambda: datetime.now(timezone.utc), nullable=True)
webhook: Optional[bool] = Field(default=False, nullable=True, description="Can be used on the webhook endpoint")
folder_id: Optional[UUID] = Field(default=None, nullable=True)
endpoint_name: Optional[str] = Field(default=None, nullable=True, index=True)
@field_validator("endpoint_name")

View file

@ -122,6 +122,13 @@ class MessageModelResponse(MessageModel):
return v
class MessageModelRequest(MessageModel):
message: str = Field(default="")
sender: str = Field(default="")
sender_name: str = Field(default="")
session_id: str = Field(default="")
class VertexBuildModel(BaseModel):
index: Optional[int] = Field(default=None, alias="index", exclude=True)
id: Optional[str] = Field(default=None, alias="id")

View file

@ -32,6 +32,10 @@ class MonitorService(Service):
except Exception as e:
logger.exception(f"Error initializing monitor service: {e}")
def exec_query(self, query: str):
with duckdb.connect(str(self.db_path)) as conn:
return conn.execute(query).df()
def to_df(self, table_name):
return self.load_table_as_dataframe(table_name)
@ -69,7 +73,7 @@ class MonitorService(Service):
valid: Optional[bool] = None,
order_by: Optional[str] = "timestamp",
):
query = "SELECT index,flow_id, valid, params, data, artifacts, timestamp FROM vertex_builds"
query = "SELECT id, index,flow_id, valid, params, data, artifacts, timestamp FROM vertex_builds"
conditions = []
if flow_id:
conditions.append(f"flow_id = '{flow_id}'")
@ -98,11 +102,22 @@ class MonitorService(Service):
with duckdb.connect(str(self.db_path)) as conn:
conn.execute(query)
def delete_messages(self, session_id: str):
def delete_messages_session(self, session_id: str):
query = f"DELETE FROM messages WHERE session_id = '{session_id}'"
with duckdb.connect(str(self.db_path)) as conn:
conn.execute(query)
return self.exec_query(query)
def delete_messages(self, message_ids: list[int]):
query = f"DELETE FROM messages WHERE index IN ({','.join(map(str, message_ids))})"
return self.exec_query(query)
def update_message(self, message_id: int, **kwargs):
query = (
f"""UPDATE messages SET {', '.join(f"{k} = '{v}'" for k, v in kwargs.items())} WHERE index = {message_id}"""
)
return self.exec_query(query)
def add_message(self, message: MessageModel):
self.add_row("messages", message)

View file

@ -78,7 +78,6 @@ class Settings(BaseSettings):
langchain_cache: str = "InMemoryCache"
load_flows_path: Optional[str] = None
# Redis
redis_host: str = "localhost"
redis_port: int = 6379

View file

@ -84,7 +84,7 @@ class Input(BaseModel):
def serialize_model(self, handler):
result = handler(self)
# If the field is str, we add the Text input type
if self.field_type in [str, Text]:
if self.field_type in ["str", "Text"]:
if "input_types" not in result:
result["input_types"] = ["Text"]
if self.field_type == Text:
@ -110,7 +110,10 @@ class Input(BaseModel):
# this should be done for all types
# How to check if v is a type?
if isinstance(v, (type, GenericAlias)):
return str(v)
if isinstance(v, type):
v = v.__name__
else:
v = str(v)
elif not isinstance(v, str):
raise ValueError(f"type must be a string or a type, not {type(v)}")
return v
@ -144,19 +147,19 @@ class Input(BaseModel):
class Output(BaseModel):
types: Optional[list[str]] = Field(default=[], serialization_alias="types")
types: Optional[list[str]] = Field(default=[])
"""List of output types for the field."""
selected: Optional[str] = Field(default=None, serialization_alias="selected")
selected: Optional[str] = Field(default=None)
"""The selected output type for the field."""
name: str = Field(default=None)
"""The name of the field."""
display_name: Optional[str] = Field(default=None)
"""The display name of the field."""
name: str = Field(default=None, serialization_alias="name")
"""The name of the field."""
method: Optional[str] = Field(default=None, serialization_alias="method")
method: Optional[str] = Field(default=None)
"""The method to use for the output."""
def to_dict(self):

View file

@ -1159,13 +1159,13 @@ files = [
[[package]]
name = "langchain"
version = "0.2.2"
version = "0.2.3"
description = "Building applications with LLMs through composability"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain-0.2.2-py3-none-any.whl", hash = "sha256:58ca0c47bcdd156da66f50a0a4fcedc49bf6950827f4a6b06c8c4842d55805f3"},
{file = "langchain-0.2.2.tar.gz", hash = "sha256:9d61e50e9cdc2bea659bc5e6c03650ba048fda63a307490ae368e539f61a0d3a"},
{file = "langchain-0.2.3-py3-none-any.whl", hash = "sha256:5dc33cd9c8008693d328b7cb698df69073acecc89ad9c2a95f243b3314f8d834"},
{file = "langchain-0.2.3.tar.gz", hash = "sha256:81962cc72cce6515f7bd71e01542727870789bf8b666c6913d85559080c1a201"},
]
[package.dependencies]
@ -1181,29 +1181,15 @@ requests = ">=2,<3"
SQLAlchemy = ">=1.4,<3"
tenacity = ">=8.1.0,<9.0.0"
[package.extras]
azure = ["azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-textanalytics (>=5.3.0,<6.0.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-core (>=1.26.4,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "azure-search-documents (==11.4.0b8)", "openai (<2)"]
clarifai = ["clarifai (>=9.1.0)"]
cli = ["typer (>=0.9.0,<0.10.0)"]
cohere = ["cohere (>=4,<6)"]
docarray = ["docarray[hnswlib] (>=0.32.0,<0.33.0)"]
embeddings = ["sentence-transformers (>=2,<3)"]
extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cohere (>=4,<6)", "couchbase (>=4.1.9,<5.0.0)", "dashvector (>=1.0.1,<2.0.0)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "langchain-openai (>=0.1,<0.2)", "lxml (>=4.9.3,<6.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "rdflib (==7.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"]
javascript = ["esprima (>=4.0.1,<5.0.0)"]
llms = ["clarifai (>=9.1.0)", "cohere (>=4,<6)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (<2)", "openlm (>=0.0.5,<0.0.6)", "torch (>=1,<3)", "transformers (>=4,<5)"]
openai = ["openai (<2)", "tiktoken (>=0.7,<1.0)"]
qdrant = ["qdrant-client (>=1.3.1,<2.0.0)"]
text-helpers = ["chardet (>=5.1.0,<6.0.0)"]
[[package]]
name = "langchain-community"
version = "0.2.2"
version = "0.2.4"
description = "Community contributed LangChain integrations."
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain_community-0.2.2-py3-none-any.whl", hash = "sha256:470ee16e05f1acacb91a656b6d3c2cbf6fb6a8dcb00a13901cd1353cd29c2bb3"},
{file = "langchain_community-0.2.2.tar.gz", hash = "sha256:fb09faf4640726a929932056dc55ff120e490aaf2e424fae8ddbb15605195447"},
{file = "langchain_community-0.2.4-py3-none-any.whl", hash = "sha256:8582e9800f4837660dc297cccd2ee1ddc1d8c440d0fe8b64edb07620f0373b0e"},
{file = "langchain_community-0.2.4.tar.gz", hash = "sha256:2bb6a1a36b8500a564d25d76469c02457b1a7c3afea6d4a609a47c06b993e3e4"},
]
[package.dependencies]
@ -1218,19 +1204,15 @@ requests = ">=2,<3"
SQLAlchemy = ">=1.4,<3"
tenacity = ">=8.1.0,<9.0.0"
[package.extras]
cli = ["typer (>=0.9.0,<0.10.0)"]
extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "azure-ai-documentintelligence (>=1.0.0b1,<2.0.0)", "azure-identity (>=1.15.0,<2.0.0)", "azure-search-documents (==11.4.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.6,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "cloudpathlib (>=0.18,<0.19)", "cloudpickle (>=2.0.0)", "cohere (>=4,<5)", "databricks-vectorsearch (>=0.21,<0.22)", "datasets (>=2.15.0,<3.0.0)", "dgml-utils (>=0.3.0,<0.4.0)", "elasticsearch (>=8.12.0,<9.0.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "fireworks-ai (>=0.9.0,<0.10.0)", "friendli-client (>=1.2.4,<2.0.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "google-cloud-documentai (>=2.20.1,<3.0.0)", "gql (>=3.4.1,<4.0.0)", "gradientai (>=1.4.0,<2.0.0)", "hdbcli (>=2.19.21,<3.0.0)", "hologres-vector (>=0.0.6,<0.0.7)", "html2text (>=2020.1.16,<2021.0.0)", "httpx (>=0.24.1,<0.25.0)", "httpx-sse (>=0.4.0,<0.5.0)", "javelin-sdk (>=0.1.8,<0.2.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "jsonschema (>1)", "lxml (>=4.9.3,<6.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "msal (>=1.25.0,<2.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "nvidia-riva-client (>=2.14.0,<3.0.0)", "oci (>=2.119.1,<3.0.0)", "openai (<2)", "openapi-pydantic (>=0.3.2,<0.4.0)", "oracle-ads (>=2.9.1,<3.0.0)", "oracledb (>=2.2.0,<3.0.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "praw (>=7.7.1,<8.0.0)", "premai (>=0.3.25,<0.4.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pyjwt (>=2.8.0,<3.0.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "rdflib (==7.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "simsimd (>=4.3.1,<5.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "tidb-vector (>=0.0.3,<1.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "tree-sitter (>=0.20.2,<0.21.0)", "tree-sitter-languages (>=1.8.0,<2.0.0)", "upstash-redis (>=0.15.0,<0.16.0)", "vdms (>=0.0.20,<0.0.21)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"]
[[package]]
name = "langchain-core"
version = "0.2.4"
version = "0.2.5"
description = "Building applications with LLMs through composability"
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langchain_core-0.2.4-py3-none-any.whl", hash = "sha256:5212f7ec78a525e88a178ed3aefe2fd7134b03fb92573dfbab9914f1d92d6ec5"},
{file = "langchain_core-0.2.4.tar.gz", hash = "sha256:82bdcc546eb0341cefcf1f4ecb3e49836fff003903afddda2d1312bb8491ef81"},
{file = "langchain_core-0.2.5-py3-none-any.whl", hash = "sha256:abe5138f22acff23a079ec538be5268bbf97cf023d51987a0dd474d2a16cae3e"},
{file = "langchain_core-0.2.5.tar.gz", hash = "sha256:4a5c2f56b22396a63ef4790043660e393adbfa6832b978f023ca996a04b8e752"},
]
[package.dependencies]
@ -1241,9 +1223,6 @@ pydantic = ">=1,<3"
PyYAML = ">=5.3"
tenacity = ">=8.1.0,<9.0.0"
[package.extras]
extended-testing = ["jinja2 (>=3,<4)"]
[[package]]
name = "langchain-experimental"
version = "0.0.60"
@ -1296,13 +1275,13 @@ types-requests = ">=2.31.0.2,<3.0.0.0"
[[package]]
name = "langsmith"
version = "0.1.71"
version = "0.1.75"
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
{file = "langsmith-0.1.71-py3-none-any.whl", hash = "sha256:a9979de2780442eb24eced31314e49f5ece6f807a0d70740b2c6c39217226794"},
{file = "langsmith-0.1.71.tar.gz", hash = "sha256:bdb1037a08acf7c19b3969c085df09c1eecb65baca8400b3b76ae871e2c8a97e"},
{file = "langsmith-0.1.75-py3-none-any.whl", hash = "sha256:d08b08dd6b3fa4da170377f95123d77122ef4c52999d10fff4ae08ff70d07aed"},
{file = "langsmith-0.1.75.tar.gz", hash = "sha256:61274e144ea94c297dd78ce03e6dfae18459fe9bd8ab5094d61a0c4816561279"},
]
[package.dependencies]
@ -1600,13 +1579,13 @@ files = [
[[package]]
name = "marshmallow"
version = "3.21.2"
version = "3.21.3"
description = "A lightweight library for converting complex datatypes to and from native Python datatypes."
optional = false
python-versions = ">=3.8"
files = [
{file = "marshmallow-3.21.2-py3-none-any.whl", hash = "sha256:70b54a6282f4704d12c0a41599682c5c5450e843b9ec406308653b47c59648a1"},
{file = "marshmallow-3.21.2.tar.gz", hash = "sha256:82408deadd8b33d56338d2182d455db632c6313aa2af61916672146bb32edc56"},
{file = "marshmallow-3.21.3-py3-none-any.whl", hash = "sha256:86ce7fb914aa865001a4b2092c4c2872d13bc347f3d42673272cabfdbad386f1"},
{file = "marshmallow-3.21.3.tar.gz", hash = "sha256:4f57c5e050a54d66361e826f94fba213eb10b67b2fdb02c3e0343ce207ba1662"},
]
[package.dependencies]
@ -2214,13 +2193,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
[[package]]
name = "pydantic-settings"
version = "2.3.0"
version = "2.3.1"
description = "Settings management using Pydantic"
optional = false
python-versions = ">=3.8"
files = [
{file = "pydantic_settings-2.3.0-py3-none-any.whl", hash = "sha256:26eeed27370a9c5e3f64e4a7d6602573cbedf05ed940f1d5b11c3f178427af7a"},
{file = "pydantic_settings-2.3.0.tar.gz", hash = "sha256:78db28855a71503cfe47f39500a1dece523c640afd5280edb5c5c9c9cfa534c9"},
{file = "pydantic_settings-2.3.1-py3-none-any.whl", hash = "sha256:acb2c213140dfff9669f4fe9f8180d43914f51626db28ab2db7308a576cce51a"},
{file = "pydantic_settings-2.3.1.tar.gz", hash = "sha256:e34bbd649803a6bb3e2f0f58fb0edff1f0c7f556849fda106cc21bcce12c30ab"},
]
[package.dependencies]

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "langflow-base"
version = "0.0.56"
version = "0.0.60"
description = "A Python package with a built-in web application"
authors = ["Langflow <contact@langflow.org>"]
maintainers = [

View file

@ -26,6 +26,7 @@
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toggle": "^1.0.3",
"@radix-ui/react-tooltip": "^1.0.6",
"@tabler/icons-react": "^2.32.0",
"@tailwindcss/forms": "^0.5.6",
@ -42,6 +43,7 @@
"cmdk": "^1.0.0",
"dompurify": "^3.0.5",
"dotenv": "^16.4.5",
"emoji-regex": "^10.3.0",
"esbuild": "^0.17.19",
"file-saver": "^2.0.5",
"framer-motion": "^11.0.6",
@ -50,6 +52,7 @@
"million": "^3.0.6",
"moment": "^2.29.4",
"openseadragon": "^4.1.1",
"p-debounce": "^4.0.0",
"playwright": "^1.42.0",
"react": "^18.2.21",
"react-ace": "^10.1.0",
@ -2761,6 +2764,31 @@
}
}
},
"node_modules/@radix-ui/react-toggle": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.0.3.tgz",
"integrity": "sha512-Pkqg3+Bc98ftZGsl60CLANXQBBQ4W3mTFS9EJvNxKMZ7magklKV69/id1mlAlOFDDfHvlCms0fx8fA4CMKDJHg==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.1",
"@radix-ui/react-primitive": "1.0.3",
"@radix-ui/react-use-controllable-state": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-tooltip": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.0.7.tgz",
@ -5965,9 +5993,9 @@
"integrity": "sha512-C6q/xcUJf/2yODRxAVCfIk4j3y3LMsD0ehiE2RQNV2cxc8XU62gR6vvYh3+etSUzlgTfil+qDHI1vubpdf0TOA=="
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
"version": "10.3.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz",
"integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw=="
},
"node_modules/end-of-stream": {
"version": "1.4.4",
@ -10017,6 +10045,15 @@
"node": ">=8"
}
},
"node_modules/p-debounce": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/p-debounce/-/p-debounce-4.0.0.tgz",
"integrity": "sha512-4Ispi9I9qYGO4lueiLDhe4q4iK5ERK8reLsuzH6BPaXn53EGaua8H66PXIFGrW897hwjXp+pVLrm/DLxN0RF0A==",
"license": "MIT",
"engines": {
"node": ">=12"
}
},
"node_modules/p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
@ -12172,6 +12209,16 @@
"node": ">=8"
}
},
"node_modules/string-width-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/string-width/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",

View file

@ -21,6 +21,7 @@
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toggle": "^1.0.3",
"@radix-ui/react-tooltip": "^1.0.6",
"@tabler/icons-react": "^2.32.0",
"@tailwindcss/forms": "^0.5.6",
@ -37,6 +38,7 @@
"cmdk": "^1.0.0",
"dompurify": "^3.0.5",
"dotenv": "^16.4.5",
"emoji-regex": "^10.3.0",
"esbuild": "^0.17.19",
"file-saver": "^2.0.5",
"framer-motion": "^11.0.6",
@ -45,6 +47,7 @@
"million": "^3.0.6",
"moment": "^2.29.4",
"openseadragon": "^4.1.1",
"p-debounce": "^4.0.0",
"playwright": "^1.42.0",
"react": "^18.2.21",
"react-ace": "^10.1.0",

View file

@ -15,7 +15,7 @@ dotenv.config({ path: path.resolve(__dirname, "../../.env") });
export default defineConfig({
testDir: "./tests",
/* Run tests in files in parallel */
fullyParallel: true,
fullyParallel: false,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
@ -52,18 +52,18 @@ export default defineConfig({
},
},
{
name: "firefox",
use: {
...devices["Desktop Firefox"],
launchOptions: {
firefoxUserPrefs: {
"dom.events.asyncClipboard.readText": true,
"dom.events.testing.asyncClipboard": true,
},
},
},
},
// {
// name: "firefox",
// use: {
// ...devices["Desktop Firefox"],
// launchOptions: {
// firefoxUserPrefs: {
// "dom.events.asyncClipboard.readText": true,
// "dom.events.testing.asyncClipboard": true,
// },
// },
// },
// },
],
webServer: [
{

View file

@ -164,3 +164,13 @@ body {
.ag-body-vertical-scroll-viewport::-webkit-scrollbar-thumb:hover {
background-color: #bbb;
}
/* This CSS is to not apply the border for the column having 'no-border' class */
.no-border.ag-cell:focus {
border: none !important;
outline: none;
}
.no-border.ag-cell {
border: none !important;
outline: none;
}

View file

@ -1,4 +1,3 @@
import axios from "axios";
import { useContext, useEffect, useState } from "react";
import { ErrorBoundary } from "react-error-boundary";
import { useNavigate } from "react-router-dom";
@ -222,12 +221,19 @@ export default function App() {
id={alert.id}
removeAlert={removeAlert}
/>
) : alert.type === "notice" ? (
<NoticeAlert
key={alert.id}
title={alert.title}
link={alert.link}
id={alert.id}
removeAlert={removeAlert}
/>
) : (
alert.type === "notice" && (
<NoticeAlert
alert.type === "success" && (
<SuccessAlert
key={alert.id}
title={alert.title}
link={alert.link}
id={alert.id}
removeAlert={removeAlert}
/>
@ -236,20 +242,6 @@ export default function App() {
</div>
))}
</div>
<div className="z-40 flex flex-col-reverse">
{tempNotificationList.map((alert) => (
<div key={alert.id}>
{alert.type === "success" && (
<SuccessAlert
key={alert.id}
title={alert.title}
id={alert.id}
removeAlert={removeAlert}
/>
)}
</div>
))}
</div>
</div>
</div>
);

View file

@ -386,7 +386,7 @@ export default function ParameterComponent({
});
}}
name={name}
data={data}
data={data.node?.template[name]}
/>
</div>
{data.node?.template[name]?.refresh_button && (
@ -442,8 +442,8 @@ export default function ParameterComponent({
data.node?.template[name]?.real_time_refresh)
}
>
<div className="mt-2 flex w-full items-center">
<div className="w-5/6 flex-grow">
<div className="mt-2 flex w-full items-center gap-2">
<div className="flex-1">
<Dropdown
disabled={disabled}
isLoading={isLoading}
@ -461,7 +461,6 @@ export default function ParameterComponent({
name={name}
data={data}
button_text={data.node?.template[name]?.refresh_button_text}
className="extra-side-bar-buttons ml-2 mt-1"
handleUpdateValues={handleRefreshButtonPress}
id={"refresh-button-" + name}
/>

View file

@ -1,3 +1,4 @@
import emojiRegex from "emoji-regex";
import { cloneDeep } from "lodash";
import { useCallback, useEffect, useMemo, useState } from "react";
import { NodeToolbar, useUpdateNodeInternals } from "reactflow";
@ -55,14 +56,14 @@ export default function GenericNode({
const [nodeName, setNodeName] = useState(data.node!.display_name);
const [inputDescription, setInputDescription] = useState(false);
const [nodeDescription, setNodeDescription] = useState(
data.node?.description!
data.node?.description!,
);
const [isOutdated, setIsOutdated] = useState(false);
const buildStatus = useFlowStore(
(state) => state.flowBuildStatus[data.id]?.status
(state) => state.flowBuildStatus[data.id]?.status,
);
const lastRunTime = useFlowStore(
(state) => state.flowBuildStatus[data.id]?.timestamp
(state) => state.flowBuildStatus[data.id]?.timestamp,
);
const [validationStatus, setValidationStatus] =
useState<validationStatusType | null>(null);
@ -77,10 +78,10 @@ export default function GenericNode({
// first check if data.type in NATIVE_CATEGORIES
// if not return
if (!data.node?.template?.code?.value) return;
const thisNodeTemplate = templates[data.type].template;
const thisNodeTemplate = templates[data.type]?.template;
// if the template does not have a code key
// return
if (!thisNodeTemplate.code) return;
if (!thisNodeTemplate?.code) return;
const currentCode = thisNodeTemplate.code?.value;
const thisNodesCode = data.node!.template?.code?.value;
const componentsToIgnore = ["Custom Component"];
@ -115,7 +116,7 @@ export default function GenericNode({
updateNodeInternals(data.id);
},
[data.id, data.node, setNode, setIsOutdated]
[data.id, data.node, setNode, setIsOutdated],
);
if (!data.node!.template) {
@ -220,8 +221,7 @@ export default function GenericNode({
const nameEditable = true;
const emojiRegex = /\p{Emoji}/u;
const isEmoji = emojiRegex.test(data?.node?.icon!);
const isEmoji = emojiRegex().test(data?.node?.icon!);
const iconNodeRender = useCallback(() => {
const iconElement = data?.node?.icon;
@ -255,8 +255,17 @@ export default function GenericNode({
const isDark = useDarkStore((state) => state.dark);
const renderIconStatus = (
buildStatus: BuildStatus | undefined,
validationStatus: validationStatusType | null
validationStatus: validationStatusType | null,
) => {
const conditionSuccess = validationStatus && validationStatus.valid;
const conditionInactive =
validationStatus &&
!validationStatus.valid &&
buildStatus === BuildStatus.INACTIVE;
const conditionError =
buildStatus === BuildStatus.ERROR ||
(validationStatus && !validationStatus.valid);
if (buildStatus === BuildStatus.BUILDING) {
return <Loading className="text-medium-indigo" />;
} else {
@ -264,31 +273,30 @@ export default function GenericNode({
<>
<IconComponent
name="Play"
className="absolute ml-0.5 h-5 fill-current stroke-2 text-medium-indigo opacity-0 transition-all group-hover:opacity-100"
className={cn(
!conditionSuccess && !conditionInactive && !conditionError
? "opacity-100"
: "opacity-0",
"absolute ml-0.5 h-5 fill-current stroke-2 text-muted-foreground transition-all group-hover:text-medium-indigo group-hover/node:opacity-100",
)}
/>
{validationStatus && validationStatus.valid ? (
{conditionSuccess ? (
<Checkmark
className="absolute ml-0.5 h-5 stroke-2 text-status-green opacity-100 transition-all group-hover:opacity-0"
className="absolute ml-0.5 h-5 stroke-2 text-status-green opacity-100 transition-all group-hover/node:opacity-0"
isVisible={true}
/>
) : validationStatus &&
!validationStatus.valid &&
buildStatus === BuildStatus.INACTIVE ? (
) : conditionInactive ? (
<IconComponent
name="Play"
className="absolute ml-0.5 h-5 fill-current stroke-2 text-status-green opacity-30 transition-all group-hover:opacity-0"
className="absolute ml-0.5 h-5 fill-current stroke-2 text-status-gray opacity-30 transition-all group-hover/node:opacity-0"
/>
) : buildStatus === BuildStatus.ERROR ||
(validationStatus && !validationStatus.valid) ? (
) : conditionError ? (
<Xmark
isVisible={true}
className="absolute ml-0.5 h-5 fill-current stroke-2 text-status-red opacity-100 transition-all group-hover:opacity-0"
className="absolute ml-0.5 h-5 fill-current stroke-2 text-status-red opacity-100 transition-all group-hover/node:opacity-0"
/>
) : (
<IconComponent
name="Play"
className="absolute ml-0.5 h-5 fill-current stroke-2 text-muted-foreground opacity-100 transition-all group-hover:opacity-0"
/>
<></>
)}
</>
);
@ -296,7 +304,7 @@ export default function GenericNode({
};
const getSpecificClassFromBuildStatus = (
buildStatus: BuildStatus | undefined,
validationStatus: validationStatusType | null
validationStatus: validationStatusType | null,
) => {
let isInvalid = validationStatus && !validationStatus.valid;
@ -320,11 +328,11 @@ export default function GenericNode({
selected: boolean,
showNode: boolean,
buildStatus: BuildStatus | undefined,
validationStatus: validationStatusType | null
validationStatus: validationStatusType | null,
) => {
const specificClassFromBuildStatus = getSpecificClassFromBuildStatus(
buildStatus,
validationStatus
validationStatus,
);
const baseBorderClass = getBaseBorderClass(selected);
@ -332,8 +340,8 @@ export default function GenericNode({
const names = classNames(
baseBorderClass,
nodeSizeClass,
"generic-node-div",
specificClassFromBuildStatus
"generic-node-div group/node",
specificClassFromBuildStatus,
);
return names;
};
@ -393,7 +401,7 @@ export default function GenericNode({
selected,
showNode,
buildStatus,
validationStatus
validationStatus,
)}
>
{data.node?.beta && showNode && (
@ -416,6 +424,7 @@ export default function GenericNode({
"generic-node-title-arrangement rounded-full" +
(!showNode && " justify-center ")
}
data-testid="generic-node-title-arrangement"
>
{iconNodeRender()}
{showNode && (
@ -452,7 +461,7 @@ export default function GenericNode({
<div className="group flex items-start gap-1.5">
<ShadTooltip content={data.node?.display_name}>
<div
onDoubleClick={(event) => {
onClick={(event) => {
if (nameEditable) {
setInputName(true);
}
@ -466,21 +475,6 @@ export default function GenericNode({
{data.node?.display_name}
</div>
</ShadTooltip>
{nameEditable && (
<div
onClick={(event) => {
setInputName(true);
takeSnapshot();
event.stopPropagation();
event.preventDefault();
}}
>
<IconComponent
name="PencilLine"
className="hidden h-3 w-3 text-status-blue group-hover:block"
/>
</div>
)}
</div>
)}
</div>
@ -538,7 +532,7 @@ export default function GenericNode({
}
title={getFieldTitle(
data.node?.template!,
templateField
templateField,
)}
info={data.node?.template[templateField].info}
name={templateField}
@ -566,7 +560,7 @@ export default function GenericNode({
proxy={data.node?.template[templateField].proxy}
showNode={showNode}
/>
)
),
)}
{/* <ParameterComponent
index={0}
@ -720,14 +714,14 @@ export default function GenericNode({
) : (
<div
className={cn(
"generic-node-desc-text truncate-multiline word-break-break-word",
"generic-node-desc-text cursor-text truncate-multiline word-break-break-word",
(data.node?.description === "" ||
!data.node?.description) &&
nameEditable
? "font-light italic"
: ""
: "",
)}
onDoubleClick={(e) => {
onClick={(e) => {
setInputDescription(true);
takeSnapshot();
}}
@ -787,13 +781,13 @@ export default function GenericNode({
}
title={getFieldTitle(
data.node?.template!,
templateField
templateField,
)}
info={data.node?.template[templateField].info}
name={templateField}
tooltipTitle={
data.node?.template[templateField].input_types?.join(
"\n"
"\n",
) ?? data.node?.template[templateField].type
}
required={data.node!.template[templateField].required}
@ -820,7 +814,7 @@ export default function GenericNode({
<div
className={classNames(
Object.keys(data.node!.template).length < 1 ? "hidden" : "",
"flex-max-width justify-center"
"flex-max-width justify-center",
)}
>
{" "}

View file

@ -39,7 +39,7 @@ const useFetchDataOnMount = (
setErrorData({
title: "Error while updating the Component",
list: [responseError.response.data.detail ?? "Unknown error"],
list: [responseError?.response?.data?.detail ?? "Unknown error"],
});
}
setIsLoading(false);

View file

@ -43,7 +43,9 @@ const useHandleOnNewValue = (
let responseError = error as ResponseErrorTypeAPI;
setErrorData({
title: "Error while updating the Component",
list: [responseError.response.data.detail.error ?? "Unknown error"],
list: [
responseError?.response?.data?.detail.error ?? "Unknown error",
],
});
}
setIsLoading(false);

View file

@ -26,7 +26,7 @@ const useHandleRefreshButtonPress = (setIsLoading, setNode) => {
setErrorData({
title: "Error while updating the Component",
list: [responseError.response.data.detail ?? "Unknown error"],
list: [responseError?.response?.data?.detail ?? "Unknown error"],
});
}
setIsLoading(false);

View file

@ -2,7 +2,7 @@ import { APITemplateType } from "../../types/api";
export default function getFieldTitle(
template: APITemplateType,
templateField: string,
templateField: string
): string {
return template[templateField].display_name
? template[templateField].display_name!

View file

@ -36,7 +36,7 @@ export default function AlertDropdown({
}}
>
<PopoverTrigger>{children}</PopoverTrigger>
<PopoverContent className="nocopy nopan nodelete nodrag noundo flex h-[500px] w-[500px] flex-col">
<PopoverContent className="nocopy nowheel nopan nodelete nodrag noundo flex h-[500px] w-[500px] flex-col">
<div className="text-md flex flex-row justify-between pl-3 font-medium text-foreground">
Notifications
<div className="flex gap-3 pr-3 ">

View file

@ -40,7 +40,7 @@ export default function ErrorAlert({
removeAlert(id);
}, 500);
}}
className="error-build-message nocopy nopan nodelete nodrag noundo"
className="error-build-message nocopy nowheel nopan nodelete nodrag noundo"
>
<div className="flex">
<div className="flex-shrink-0">
@ -51,13 +51,15 @@ export default function ErrorAlert({
/>
</div>
<div className="ml-3">
<h3 className="error-build-foreground">{title}</h3>
<h3 className="error-build-foreground line-clamp-2">{title}</h3>
{list?.length !== 0 &&
list?.some((item) => item !== null && item !== undefined) ? (
<div className="error-build-message-div">
<ul className="error-build-message-list">
{list.map((item, index) => (
<li key={index}>{item}</li>
<li key={index} className="line-clamp-5">
{item}
</li>
))}
</ul>
</div>

View file

@ -36,7 +36,7 @@ export default function NoticeAlert({
setShow(false);
removeAlert(id);
}}
className="nocopy nopan nodelete nodrag noundo mt-6 w-96 rounded-md bg-info-background p-4 shadow-xl"
className="nocopy nowheel nopan nodelete nodrag noundo mt-6 w-96 rounded-md bg-info-background p-4 shadow-xl"
>
<div className="flex">
<div className="flex-shrink-0">
@ -47,7 +47,7 @@ export default function NoticeAlert({
/>
</div>
<div className="ml-3 flex-1 md:flex md:justify-between">
<p className="text-sm text-info-foreground word-break-break-word">
<p className="line-clamp-2 text-sm text-info-foreground word-break-break-word">
{title}
</p>
<p className="mt-3 text-sm md:ml-6 md:mt-0">

View file

@ -34,7 +34,7 @@ export default function SuccessAlert({
setShow(false);
removeAlert(id);
}}
className="success-alert nocopy nopan nodelete nodrag noundo"
className="success-alert nocopy nowheel nopan nodelete nodrag noundo"
>
<div className="flex">
<div className="flex-shrink-0">
@ -45,7 +45,7 @@ export default function SuccessAlert({
/>
</div>
<div className="ml-3">
<p className="success-alert-message">{title}</p>
<p className="success-alert-message line-clamp-2">{title}</p>
</div>
</div>
</div>

View file

@ -31,14 +31,14 @@ export default function ImageViewer({ image }) {
const fullPageButton = document.getElementById("full-page-button");
zoomInButton!.addEventListener("click", () =>
viewer.viewport.zoomBy(1.2),
viewer.viewport.zoomBy(1.2)
);
zoomOutButton!.addEventListener("click", () =>
viewer.viewport.zoomBy(0.8),
viewer.viewport.zoomBy(0.8)
);
homeButton!.addEventListener("click", () => viewer.viewport.goHome());
fullPageButton!.addEventListener("click", () =>
viewer.setFullScreen(true),
viewer.setFullScreen(true)
);
// Optionally, you can set additional viewer options here
@ -47,16 +47,16 @@ export default function ImageViewer({ image }) {
return () => {
viewer.destroy();
zoomInButton!.removeEventListener("click", () =>
viewer.viewport.zoomBy(1.2),
viewer.viewport.zoomBy(1.2)
);
zoomOutButton!.removeEventListener("click", () =>
viewer.viewport.zoomBy(0.8),
viewer.viewport.zoomBy(0.8)
);
homeButton!.removeEventListener("click", () =>
viewer.viewport.goHome(),
viewer.viewport.goHome()
);
fullPageButton!.removeEventListener("click", () =>
viewer.setFullScreen(true),
viewer.setFullScreen(true)
);
};
}

View file

@ -6,16 +6,18 @@ import {
AccordionTrigger,
} from "../../components/ui/accordion";
import { AccordionComponentType } from "../../types/components";
import { cn } from "../../utils/utils";
export default function AccordionComponent({
trigger,
children,
disabled,
open = [],
keyValue,
sideBar,
}: AccordionComponentType): JSX.Element {
const [value, setValue] = useState(
open.length === 0 ? "" : getOpenAccordion(),
open.length === 0 ? "" : getOpenAccordion()
);
function getOpenAccordion(): string {
@ -29,7 +31,9 @@ export default function AccordionComponent({
}
function handleClick(): void {
value === "" ? setValue(keyValue!) : setValue("");
if (!disabled) {
value === "" ? setValue(keyValue!) : setValue("");
}
}
return (
@ -38,16 +42,18 @@ export default function AccordionComponent({
type="single"
className="w-full"
value={value}
onValueChange={setValue}
onValueChange={!disabled ? setValue : () => {}}
>
<AccordionItem value={keyValue!} className="border-b">
<AccordionTrigger
onClick={() => {
handleClick();
}}
className={
sideBar ? "w-full bg-muted px-[0.75rem] py-[0.5rem]" : "ml-3"
}
disabled={disabled}
className={cn(
sideBar ? "w-full bg-muted px-[0.75rem] py-[0.5rem]" : "ml-3",
disabled ? "cursor-not-allowed" : "cursor-pointer"
)}
>
{trigger}
</AccordionTrigger>

View file

@ -7,7 +7,6 @@ import { useTypesStore } from "../../stores/typesStore";
import { ResponseErrorDetailAPI } from "../../types/api";
import ForwardedIconComponent from "../genericIconComponent";
import InputComponent from "../inputComponent";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
import { Label } from "../ui/label";
import { Textarea } from "../ui/textarea";
@ -24,19 +23,19 @@ export default function AddNewVariableButton({ children }): JSX.Element {
const setErrorData = useAlertStore((state) => state.setErrorData);
const componentFields = useTypesStore((state) => state.ComponentFields);
const unavaliableFields = new Set(
Object.keys(useGlobalVariablesStore((state) => state.unavaliableFields)),
Object.keys(useGlobalVariablesStore((state) => state.unavaliableFields))
);
const availableFields = () => {
const fields = Array.from(componentFields).filter(
(field) => !unavaliableFields.has(field),
(field) => !unavaliableFields.has(field)
);
return sortByName(fields);
};
const addGlobalVariable = useGlobalVariablesStore(
(state) => state.addGlobalVariable,
(state) => state.addGlobalVariable
);
function handleSaveVariable() {
@ -65,12 +64,17 @@ export default function AddNewVariableButton({ children }): JSX.Element {
let responseError = error as ResponseErrorDetailAPI;
setErrorData({
title: "Error creating variable",
list: [responseError.response.data.detail ?? "Unknown error"],
list: [responseError?.response?.data?.detail ?? "Unknown error"],
});
});
}
return (
<BaseModal open={open} setOpen={setOpen} size="x-small">
<BaseModal
open={open}
setOpen={setOpen}
size="x-small"
onSubmit={handleSaveVariable}
>
<BaseModal.Header
description={
"This variable will be encrypted and will be available for you to use in any of your projects."
@ -137,9 +141,9 @@ export default function AddNewVariableButton({ children }): JSX.Element {
></InputComponent>
</div>
</BaseModal.Content>
<BaseModal.Footer>
<Button onClick={handleSaveVariable}>Save Variable</Button>
</BaseModal.Footer>
<BaseModal.Footer
submit={{ label: "Save Variable", dataTestId: "save-variable-btn" }}
/>
</BaseModal>
);
}

View file

@ -27,8 +27,8 @@ import {
import { Checkbox } from "../ui/checkbox";
import { FormControl, FormField } from "../ui/form";
import Loading from "../ui/loading";
import { convertTestName } from "./utils/convert-test-name";
import DragCardComponent from "./components/dragCardComponent";
import { convertTestName } from "./utils/convert-test-name";
export default function CollectionCardComponent({
data,
@ -60,11 +60,11 @@ export default function CollectionCardComponent({
const [loading, setLoading] = useState(false);
const [loadingLike, setLoadingLike] = useState(false);
const [liked_by_user, setLiked_by_user] = useState(
data?.liked_by_user ?? false,
data?.liked_by_user ?? false
);
const [likes_count, setLikes_count] = useState(data?.liked_by_count ?? 0);
const [downloads_count, setDownloads_count] = useState(
data?.downloads_count ?? 0,
data?.downloads_count ?? 0
);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
@ -75,12 +75,12 @@ export default function CollectionCardComponent({
const [openPlayground, setOpenPlayground] = useState(false);
const [openDelete, setOpenDelete] = useState(false);
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId,
(state) => state.setCurrentFlowId
);
const [loadingPlayground, setLoadingPlayground] = useState(false);
const selectedFlowsComponentsCards = useFlowsManagerStore(
(state) => state.selectedFlowsComponentsCards,
(state) => state.selectedFlowsComponentsCards
);
const name = data.is_component ? "Component" : "Flow";
@ -220,7 +220,7 @@ export default function CollectionCardComponent({
"group relative flex min-h-[11rem] flex-col justify-between overflow-hidden transition-all hover:bg-muted/50 hover:shadow-md hover:dark:bg-[#ffffff10]",
disabled ? "pointer-events-none opacity-50" : "",
onClick ? "cursor-pointer" : "",
isSelectedCard ? "border border-selected" : "",
isSelectedCard ? "border border-selected" : ""
)}
onClick={onClick}
>
@ -233,7 +233,7 @@ export default function CollectionCardComponent({
"visible flex-shrink-0",
data.is_component
? "mx-0.5 h-6 w-6 text-component-icon"
: "h-7 w-7 flex-shrink-0 text-flow-icon",
: "h-7 w-7 flex-shrink-0 text-flow-icon"
)}
name={data.is_component ? "ToyBrick" : "Group"}
/>
@ -428,7 +428,7 @@ export default function CollectionCardComponent({
name="Trash2"
className={cn(
"h-5 w-5",
!authorized ? " text-ring" : "",
!authorized ? " text-ring" : ""
)}
/>
</Button>
@ -463,7 +463,7 @@ export default function CollectionCardComponent({
liked_by_user
? "fill-destructive stroke-destructive"
: "",
!authorized ? " text-ring" : "",
!authorized ? " text-ring" : ""
)}
/>
</Button>
@ -501,7 +501,7 @@ export default function CollectionCardComponent({
}
className={cn(
loading ? "h-5 w-5 animate-spin" : "h-5 w-5",
!authorized ? " text-ring" : "",
!authorized ? " text-ring" : ""
)}
/>
</Button>

View file

@ -65,7 +65,7 @@ export default function CardsWrapComponent({
"h-full w-full",
isDragging
? "mb-36 flex flex-col items-center justify-center gap-4 text-2xl font-light"
: "",
: ""
)}
>
{isDragging ? (

View file

@ -50,7 +50,7 @@ export default function FlowToolbar(): JSX.Element {
"relative inline-flex h-full w-full items-center justify-center gap-[4px] bg-muted px-5 py-3 text-sm font-semibold text-foreground transition-all duration-150 ease-in-out hover:bg-background hover:bg-hover ",
!hasApiKey || !validApiKey || !hasStore
? " button-disable text-muted-foreground "
: "",
: ""
)}
>
<ForwardedIconComponent
@ -59,14 +59,14 @@ export default function FlowToolbar(): JSX.Element {
"-m-0.5 -ml-1 h-6 w-6",
!hasApiKey || !validApiKey || !hasStore
? "extra-side-bar-save-disable"
: "",
: ""
)}
/>
Share
</button>
</ShareModal>
),
[hasApiKey, validApiKey, currentFlow, hasStore],
[hasApiKey, validApiKey, currentFlow, hasStore]
);
return (
@ -118,7 +118,7 @@ export default function FlowToolbar(): JSX.Element {
<ApiModal flow={currentFlow}>
<div
className={classNames(
"relative inline-flex w-full items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-foreground transition-all duration-150 ease-in-out hover:bg-hover",
"relative inline-flex w-full items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-foreground transition-all duration-150 ease-in-out hover:bg-hover"
)}
>
<ForwardedIconComponent

View file

@ -236,7 +236,7 @@ export default function CodeTabsComponent({
<div className="api-modal-according-display">
<div
className={classNames(
"h-[70vh] w-full overflow-y-auto overflow-x-hidden rounded-lg bg-muted custom-scroll",
"h-[70vh] w-full overflow-y-auto overflow-x-hidden rounded-lg bg-muted custom-scroll"
)}
>
{data?.map((node: any, i) => (
@ -275,8 +275,8 @@ export default function CodeTabsComponent({
.show &&
LANGFLOW_SUPPORTED_TYPES.has(
node.data.node.template[templateField]
.type,
),
.type
)
)
.map((templateField, indx) => {
return (
@ -334,7 +334,7 @@ export default function CodeTabsComponent({
target,
node.data.node.template[
templateField
],
]
);
}}
/>
@ -380,7 +380,7 @@ export default function CodeTabsComponent({
target,
node.data.node.template[
templateField
],
]
);
}}
/>
@ -433,7 +433,7 @@ export default function CodeTabsComponent({
target,
node.data.node.template[
templateField
],
]
);
}}
/>
@ -470,7 +470,7 @@ export default function CodeTabsComponent({
e,
node.data.node.template[
templateField
],
]
);
}}
size="small"
@ -501,7 +501,7 @@ export default function CodeTabsComponent({
].fileTypes
}
onFileChange={(
value: any,
value: any
) => {
node.data.node.template[
templateField
@ -554,7 +554,7 @@ export default function CodeTabsComponent({
target,
node.data.node.template[
templateField
],
]
);
}}
/>
@ -594,7 +594,7 @@ export default function CodeTabsComponent({
target,
node.data.node.template[
templateField
],
]
);
}}
value={
@ -656,7 +656,7 @@ export default function CodeTabsComponent({
target,
node.data.node.template[
templateField
],
]
);
}}
/>
@ -702,7 +702,7 @@ export default function CodeTabsComponent({
target,
node.data.node.template[
templateField
],
]
);
}}
/>
@ -748,7 +748,7 @@ export default function CodeTabsComponent({
target,
node.data.node.template[
templateField
],
]
);
}}
/>
@ -780,8 +780,8 @@ export default function CodeTabsComponent({
].value,
type(
node,
templateField,
),
templateField
)
)
}
duplicateKey={
@ -790,15 +790,15 @@ export default function CodeTabsComponent({
onChange={(target) => {
const valueToNumbers =
convertValuesToNumbers(
target,
target
);
node.data.node!.template[
templateField
].value = valueToNumbers;
setErrorDuplicateKey(
hasDuplicateKeys(
valueToNumbers,
),
valueToNumbers
)
);
setData((old) => {
let newInputList =
@ -815,7 +815,7 @@ export default function CodeTabsComponent({
target,
node.data.node.template[
templateField
],
]
);
}}
isList={
@ -863,7 +863,7 @@ export default function CodeTabsComponent({
target,
node.data.node.template[
templateField
],
]
);
}}
/>

View file

@ -67,7 +67,7 @@ function CsvOutputComponent({
if (file) {
const { rowData: data, colDefs: columns } = convertCSVToData(
file,
separator,
separator
);
setRowData(data);
setColDefs(columns);

View file

@ -33,9 +33,8 @@ export default function Dropdown({
const refButton = useRef<HTMLButtonElement>(null);
const PopoverContentDropdown = children
? PopoverContent
: PopoverContentWithoutPortal;
const PopoverContentDropdown =
children || editNode ? PopoverContent : PopoverContentWithoutPortal;
return (
<>

View file

@ -109,7 +109,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
{setEndpointName && (
<Label>
<div className="edit-flow-arrangement mt-3">
<span className="font-medium">Endpoint name:</span>
<span className="font-medium">Endpoint Name</span>
{!isEndpointNameValid && (
<span className="edit-flow-span">
Invalid endpoint name. Use only letters, numbers, hyphens, and
@ -123,7 +123,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
type="text"
name="endpoint_name"
value={endpointName ?? ""}
placeholder="An alternative name for the run endpoint"
placeholder="An alternative name to run the endpoint"
maxLength={maxLength}
id="endpoint_name"
onDoubleClickCapture={(event) => {

View file

@ -1,7 +1,6 @@
import BaseModal from "../../modals/baseModal";
import { fetchErrorComponentType } from "../../types/components";
import IconComponent from "../genericIconComponent";
import { Button } from "../ui/button";
export default function FetchErrorComponent({
message,
@ -12,7 +11,14 @@ export default function FetchErrorComponent({
}: fetchErrorComponentType) {
return (
<>
<BaseModal size="small-h-full" open={openModal} type="modal">
<BaseModal
size="small-h-full"
open={openModal}
type="modal"
onSubmit={() => {
setRetry();
}}
>
<BaseModal.Content>
<div role="status" className="m-auto flex flex-col items-center">
<IconComponent
@ -27,24 +33,9 @@ export default function FetchErrorComponent({
</div>
</BaseModal.Content>
<BaseModal.Footer>
<div className="m-auto">
<Button
disabled={isLoadingHealth}
onClick={() => {
setRetry();
}}
>
{isLoadingHealth ? (
<div>
<IconComponent name={"Loader2"} className={"animate-spin"} />
</div>
) : (
"Retry"
)}
</Button>
</div>
</BaseModal.Footer>
<BaseModal.Footer
submit={{ label: "Retry", loading: isLoadingHealth }}
/>
</BaseModal>
</>
);

View file

@ -37,21 +37,11 @@ export const MenuBar = ({}: {}): JSX.Element => {
const isBuilding = useFlowStore((state) => state.isBuilding);
const getTypes = useTypesStore((state) => state.getTypes);
function handleAddFlow(duplicate?: boolean) {
function handleAddFlow() {
try {
if (duplicate) {
if (!currentFlow) {
throw new Error("No flow to duplicate");
}
addFlow(true, currentFlow).then((id) => {
setSuccessData({ title: "Flow duplicated successfully" });
navigate("/flow/" + id);
});
} else {
addFlow(true).then((id) => {
navigate("/flow/" + id);
});
}
addFlow(true).then((id) => {
navigate("/flow/" + id);
});
} catch (err) {
setErrorData(err as { title: string; list?: Array<string> });
}
@ -97,15 +87,6 @@ export const MenuBar = ({}: {}): JSX.Element => {
<IconComponent name="Plus" className="header-menu-options" />
New
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
handleAddFlow(true);
}}
className="cursor-pointer"
>
<IconComponent name="Copy" className="header-menu-options" />
Duplicate
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {

View file

@ -56,7 +56,7 @@ export default function Header(): JSX.Element {
const lastFlowVisitedIndex = routeHistory
.reverse()
.findIndex(
(path) => path.includes("/flow/") && path !== location.pathname
(path) => path.includes("/flow/") && path !== location.pathname,
);
const lastFlowVisited = routeHistory[lastFlowVisitedIndex];
@ -81,14 +81,16 @@ export default function Header(): JSX.Element {
<span className="ml-4 text-2xl">⛓️</span>
</Link>
{showArrowReturnIcon && (
<button
<Button
variant="none"
size="none"
onClick={() => {
checkForChanges();
redirectToLastLocation();
}}
>
<IconComponent name="ChevronLeft" className="w-4" />
</button>
</Button>
)}
<MenuBar />
@ -181,24 +183,14 @@ export default function Header(): JSX.Element {
/>
</div>
</AlertDropdown>
{autoLogin && (
<button
onClick={() => {
navigate("/account/api-keys");
}}
>
<IconComponent
name="Key"
className="side-bar-button-size text-muted-foreground hover:text-accent-foreground"
/>
</button>
)}
<>
<Separator orientation="vertical" />
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
<Button
variant="none"
size="none"
data-testid="user-profile-settings"
className={
"h-7 w-7 rounded-full focus-visible:outline-0 " +
@ -212,6 +204,28 @@ export default function Header(): JSX.Element {
/>
</DropdownMenuTrigger>
<DropdownMenuContent>
{!autoLogin && (
<>
<DropdownMenuLabel>
<div className="flex items-center gap-3">
<div
className={
"h-5 w-5 rounded-full focus-visible:outline-0 " +
(userData?.profile_image ??
(userData?.id
? gradients[
parseInt(userData?.id ?? "", 30) %
gradients.length
]
: "bg-gray-500"))
}
/>
{userData?.username ?? "User"}
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
</>
)}
<DropdownMenuLabel>General</DropdownMenuLabel>
<DropdownMenuItem
className="cursor-pointer"

View file

@ -10,7 +10,11 @@ import {
CommandList,
} from "../../../ui/command";
import { Input } from "../../../ui/input";
import { Popover, PopoverContentWithoutPortal } from "../../../ui/popover";
import {
Popover,
PopoverContent,
PopoverContentWithoutPortal,
} from "../../../ui/popover";
const CustomInputPopover = ({
id,
refInput,
@ -39,6 +43,9 @@ const CustomInputPopover = ({
showOptions,
}) => {
const setErrorData = useAlertStore.getState().setErrorData;
const PopoverContentInput = editNode
? PopoverContent
: PopoverContentWithoutPortal;
const handleInputChange = (e) => {
if (password) {
@ -68,9 +75,9 @@ const CustomInputPopover = ({
(selectedOption !== "" || !onChange) && setSelectedOption
? selectedOption
: (selectedOptions?.length !== 0 || !onChange) &&
setSelectedOptions
? selectedOptions?.join(", ")
: value
setSelectedOptions
? selectedOptions?.join(", ")
: value
}
autoFocus={autoFocus}
disabled={disabled}
@ -96,7 +103,7 @@ const CustomInputPopover = ({
(password && !(setSelectedOption || setSelectedOptions))
? "pr-8"
: "",
className!,
className!
)}
placeholder={password && editNode ? "Key" : placeholder}
onChange={handleInputChange}
@ -107,8 +114,8 @@ const CustomInputPopover = ({
data-testid={editNode ? id + "-edit" : id}
/>
</PopoverAnchor>
<PopoverContentWithoutPortal
className="nocopy nopan nodelete nodrag noundo p-0"
<PopoverContentInput
className="nocopy nowheel nopan nodelete nodrag noundo p-0"
style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }}
side="bottom"
align="center"
@ -134,15 +141,15 @@ const CustomInputPopover = ({
onSelect={(currentValue) => {
setSelectedOption &&
setSelectedOption(
currentValue === selectedOption ? "" : currentValue,
currentValue === selectedOption ? "" : currentValue
);
setSelectedOptions &&
setSelectedOptions(
selectedOptions?.includes(currentValue)
? selectedOptions.filter(
(item) => item !== currentValue,
(item) => item !== currentValue
)
: [...selectedOptions, currentValue],
: [...selectedOptions, currentValue]
);
!setSelectedOptions && setShowOptions(false);
}}
@ -155,7 +162,7 @@ const CustomInputPopover = ({
selectedOption === option ||
selectedOptions?.includes(option)
? "opacity-100"
: "opacity-0",
: "opacity-0"
)}
>
<div className="absolute opacity-100 transition-all group-hover:opacity-0">
@ -184,7 +191,7 @@ const CustomInputPopover = ({
</CommandGroup>
</CommandList>
</Command>
</PopoverContentWithoutPortal>
</PopoverContentInput>
</Popover>
);
};

View file

@ -9,7 +9,11 @@ import {
CommandList,
} from "../../../ui/command";
import { Input } from "../../../ui/input";
import { Popover, PopoverContentWithoutPortal } from "../../../ui/popover";
import {
Popover,
PopoverContent,
PopoverContentWithoutPortal,
} from "../../../ui/popover";
const CustomInputPopoverObject = ({
id,
refInput,
@ -23,6 +27,7 @@ const CustomInputPopoverObject = ({
disabled,
setShowOptions,
required,
editNode,
className,
placeholder,
onChange,
@ -34,6 +39,10 @@ const CustomInputPopoverObject = ({
handleKeyDown,
showOptions,
}) => {
const PopoverContentInput = editNode
? PopoverContent
: PopoverContentWithoutPortal;
const handleInputChange = (e) => {
onChange && onChange(e.target.value);
};
@ -51,14 +60,14 @@ const CustomInputPopoverObject = ({
? options.find((option) => option.id === selectedOption)?.name ||
""
: (selectedOptions?.length !== 0 || !onChange) &&
setSelectedOptions
? selectedOptions
.map(
(optionId) =>
options.find((option) => option.id === optionId)?.name,
)
.join(", ")
: value
setSelectedOptions
? selectedOptions
.map(
(optionId) =>
options.find((option) => option.id === optionId)?.name
)
.join(", ")
: value
}
autoFocus={autoFocus}
disabled={disabled}
@ -79,8 +88,8 @@ const CustomInputPopoverObject = ({
data-testid={id}
/>
</PopoverAnchor>
<PopoverContentWithoutPortal
className="nocopy nopan nodelete nodrag noundo p-0"
<PopoverContentInput
className="nocopy nowheel nopan nodelete nodrag noundo p-0"
style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }}
side="bottom"
align="center"
@ -106,15 +115,15 @@ const CustomInputPopoverObject = ({
onSelect={(currentValue) => {
setSelectedOption &&
setSelectedOption(
currentValue === selectedOption ? "" : currentValue,
currentValue === selectedOption ? "" : currentValue
);
setSelectedOptions &&
setSelectedOptions(
selectedOptions?.includes(currentValue)
? selectedOptions.filter(
(item) => item !== currentValue,
(item) => item !== currentValue
)
: [...selectedOptions, currentValue],
: [...selectedOptions, currentValue]
);
!setSelectedOptions && setShowOptions(false);
}}
@ -127,7 +136,7 @@ const CustomInputPopoverObject = ({
selectedOption === option.id ||
selectedOptions?.includes(option.id)
? "opacity-100"
: "opacity-0",
: "opacity-0"
)}
>
<div className="absolute opacity-100 transition-all group-hover:opacity-0">
@ -159,7 +168,7 @@ const CustomInputPopoverObject = ({
</CommandGroup>
</CommandList>
</Command>
</PopoverContentWithoutPortal>
</PopoverContentInput>
</Popover>
);
};

View file

@ -71,7 +71,7 @@ export default function InputComponent({
editNode ? " input-edit-node " : "",
password && editNode ? "pr-8" : "",
password && !editNode ? "pr-10" : "",
className!,
className!
)}
placeholder={password && editNode ? "Key" : placeholder}
onChange={(e) => {
@ -108,6 +108,7 @@ export default function InputComponent({
setSelectedOptions={setSelectedOptions}
options={objectOptions}
value={value}
editNode={editNode}
autoFocus={autoFocus}
disabled={disabled}
setShowOptions={setShowOptions}
@ -153,7 +154,7 @@ export default function InputComponent({
<span
className={cn(
password && selectedOption === "" ? "right-8" : "right-0",
"absolute inset-y-0 flex items-center pr-2.5",
"absolute inset-y-0 flex items-center pr-2.5"
)}
>
<button
@ -166,7 +167,7 @@ export default function InputComponent({
selectedOption !== ""
? "text-medium-indigo"
: "text-muted-foreground",
"hover:text-accent-foreground",
"hover:text-accent-foreground"
)}
>
<ForwardedIconComponent
@ -186,7 +187,7 @@ export default function InputComponent({
"mb-px",
editNode
? "input-component-true-button"
: "input-component-false-button",
: "input-component-false-button"
)}
onClick={(event) => {
event.preventDefault();
@ -203,7 +204,7 @@ export default function InputComponent({
className={classNames(
editNode
? "input-component-true-svg"
: "input-component-false-svg",
: "input-component-false-svg"
)}
>
<path
@ -222,7 +223,7 @@ export default function InputComponent({
className={classNames(
editNode
? "input-component-true-svg"
: "input-component-false-svg",
: "input-component-false-svg"
)}
>
<path

View file

@ -1,7 +1,6 @@
import { useEffect, useState } from "react";
import {
CONSOLE_ERROR_MSG,
CONSOLE_SUCCESS_MSG,
INVALID_FILE_ALERT,
} from "../../constants/alerts_constants";
import { uploadFile } from "../../controllers/API";

View file

@ -32,11 +32,11 @@ export default function InputGlobalComponent({
const setErrorData = useAlertStore((state) => state.setErrorData);
useEffect(() => {
if (data.node?.template[name])
if (data)
if (
globalVariablesEntries &&
!globalVariablesEntries.includes(data.node?.template[name].value) &&
data.node?.template[name].load_from_db
!globalVariablesEntries.includes(data.value) &&
data.load_from_db
) {
setTimeout(() => {
onChange("", true);
@ -46,17 +46,11 @@ export default function InputGlobalComponent({
}, [globalVariablesEntries]);
useEffect(() => {
if (
!data.node?.template[name].value &&
data.node?.template[name].display_name
) {
if (
unavaliableFields[data.node?.template[name].display_name!] &&
!disabled
) {
if (!data.value && data.display_name) {
if (unavaliableFields[data.display_name!] && !disabled) {
setTimeout(() => {
setDb(true);
onChange(unavaliableFields[data.node?.template[name].display_name!]);
onChange(unavaliableFields[data.display_name!]);
}, 100);
}
}
@ -68,10 +62,7 @@ export default function InputGlobalComponent({
await deleteGlobalVariable(id)
.then(() => {
removeGlobalVariable(key);
if (
data?.node?.template[name].value === key &&
data?.node?.template[name].load_from_db
) {
if (data?.value === key && data?.load_from_db) {
onChange("");
setDb(false);
}
@ -94,8 +85,8 @@ export default function InputGlobalComponent({
id={"input-" + name}
editNode={editNode}
disabled={disabled}
password={data.node?.template[name].password ?? false}
value={data.node?.template[name].value ?? ""}
password={data.password ?? false}
value={data.value ?? ""}
options={globalVariablesEntries}
optionsPlaceholder={"Global Variables"}
optionsIcon="Globe"
@ -138,10 +129,10 @@ export default function InputGlobalComponent({
</DeleteConfirmationModal>
)}
selectedOption={
data?.node?.template[name].load_from_db &&
data?.load_from_db &&
globalVariablesEntries &&
globalVariablesEntries.includes(data?.node?.template[name].value ?? "")
? data?.node?.template[name].value
globalVariablesEntries.includes(data?.value ?? "")
? data?.value
: ""
}
setSelectedOption={(value) => {

View file

@ -55,10 +55,11 @@ export default function InputListComponent({
/>
{idx === value.length - 1 ? (
<button
onClick={() => {
onClick={(e) => {
let newInputList = _.cloneDeep(value);
newInputList.push("");
onChange(newInputList);
e.preventDefault();
}}
data-testid={
`input-list-plus-btn${
@ -79,10 +80,11 @@ export default function InputListComponent({
editNode ? "-edit" : ""
}_${componentName}-` + idx
}
onClick={() => {
onClick={(e) => {
let newInputList = _.cloneDeep(value);
newInputList.splice(idx, 1);
onChange(newInputList);
e.preventDefault();
}}
disabled={disabled || playgroundDisabled}
>

View file

@ -11,9 +11,8 @@ export default function ShadTooltip({
delayDuration = 500,
}: ShadToolTipType): JSX.Element {
return (
<Tooltip delayDuration={delayDuration}>
<Tooltip defaultOpen={!children} delayDuration={delayDuration}>
<TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>
<TooltipContent
className={cn(styleClasses, "max-w-96")}
side={side}

View file

@ -11,9 +11,12 @@ type SideBarButtonsComponentProps = {
pathname: string;
handleOpenNewFolderModal?: () => void;
};
const SideBarButtonsComponent = ({ items }: SideBarButtonsComponentProps) => {
const SideBarButtonsComponent = ({
items,
pathname,
}: SideBarButtonsComponentProps) => {
return (
<>
<div className="flex gap-2 overflow-auto lg:h-[70vh] lg:flex-col">
{items.map((item) => (
<Link to={item.href!}>
<div
@ -21,14 +24,20 @@ const SideBarButtonsComponent = ({ items }: SideBarButtonsComponentProps) => {
data-testid={`sidebar-nav-${item.title}`}
className={cn(
buttonVariants({ variant: "ghost" }),
"!w-[200px] cursor-pointer justify-start gap-2 border border-transparent hover:border-border hover:bg-transparent"
pathname === item.href
? "border border-border bg-muted hover:bg-muted"
: "border border-transparent hover:border-border hover:bg-transparent",
"flex w-full shrink-0 justify-start gap-4"
)}
>
{item.title}
{item.icon}
<span className="block max-w-full truncate opacity-100">
{item.title}
</span>
</div>
</Link>
))}
</>
</div>
);
};
export default SideBarButtonsComponent;

View file

@ -13,6 +13,7 @@ import IconComponent, {
import { Button, buttonVariants } from "../../../ui/button";
import { Input } from "../../../ui/input";
import useFileDrop from "../../hooks/use-on-file-drop";
import useAlertStore from "../../../../stores/alertStore";
type SideBarFoldersButtonsComponentProps = {
folders: FolderType[];
@ -33,7 +34,7 @@ const SideBarFoldersButtonsComponent = ({
const [foldersNames, setFoldersNames] = useState({});
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
const [editFolders, setEditFolderName] = useState(
folders.map((obj) => ({ name: obj.name, edit: false }))
folders.map((obj) => ({ name: obj.name, edit: false })),
);
const uploadFolder = useFolderStore((state) => state.uploadFolder);
const currentFolder = pathname.split("/");
@ -51,6 +52,7 @@ const SideBarFoldersButtonsComponent = ({
const location = useLocation();
const folderId = location?.state?.folderId ?? myCollectionId;
const getFolderById = useFolderStore((state) => state.getFolderById);
const setErrorData = useAlertStore((state) => state.setErrorData);
const handleFolderChange = (folderId: string) => {
getFolderById(folderId);
@ -58,11 +60,21 @@ const SideBarFoldersButtonsComponent = ({
const { dragOver, dragEnter, dragLeave, onDrop } = useFileDrop(
folderId,
handleFolderChange
handleFolderChange,
);
const handleUploadFlowsToFolder = () => {
uploadFolder(folderId);
uploadFolder(folderId)
.then(() => {
getFolderById(folderId);
})
.catch((err) => {
console.log(err);
setErrorData({
title: `Error on upload`,
list: [err["response"]["data"]],
});
});
};
const handleDownloadFolder = (id: string) => {
@ -73,7 +85,7 @@ const SideBarFoldersButtonsComponent = ({
addFolder({ name: "New Folder", parent_id: null, description: "" }).then(
(res) => {
getFoldersApi(true);
}
},
);
}
@ -93,24 +105,25 @@ const SideBarFoldersButtonsComponent = ({
return (
<>
<div className="flex shrink-0 items-center justify-between">
<Button variant="primary" onClick={addNewFolder}>
<ForwardedIconComponent
name="Plus"
className="main-page-nav-button"
/>
New Folder
<div className="flex shrink-0 items-center justify-between gap-2">
<div className="flex-1 self-start text-lg font-semibold">Folders</div>
<Button
variant="primary"
size="icon"
className="px-2"
onClick={addNewFolder}
data-testid="add-folder-button"
>
<ForwardedIconComponent name="FolderPlus" className="w-4" />
</Button>
<Button
variant="primary"
className="px-7"
size="icon"
className="px-2"
onClick={handleUploadFlowsToFolder}
data-testid="upload-folder-button"
>
<ForwardedIconComponent
name="Upload"
className="main-page-nav-button"
/>
Upload
<ForwardedIconComponent name="Upload" className="w-4" />
</Button>
</div>
@ -118,7 +131,7 @@ const SideBarFoldersButtonsComponent = ({
<>
{folders.map((item, index) => {
const editFolderName = editFolders?.filter(
(folder) => folder.name === item.name
(folder) => folder.name === item.name,
)[0];
return (
<div
@ -134,7 +147,7 @@ const SideBarFoldersButtonsComponent = ({
? "border border-border bg-muted hover:bg-muted"
: "border hover:bg-transparent lg:border-transparent lg:hover:border-border",
"group flex w-full shrink-0 cursor-pointer gap-2 opacity-100 lg:min-w-full",
folderIdDragging === item.id! ? "bg-border" : ""
folderIdDragging === item.id! ? "bg-border" : "",
)}
onClick={() => handleChangeFolder!(item.id!)}
>
@ -176,11 +189,11 @@ const SideBarFoldersButtonsComponent = ({
event.stopPropagation();
event.preventDefault();
}}
className="flex w-full items-center gap-2"
className="flex w-full items-center gap-4"
>
<IconComponent
name={"folder"}
className="mr-2 w-4 flex-shrink-0 justify-start stroke-[1.5] opacity-100"
className="w-4 flex-shrink-0 justify-start stroke-[1.5] opacity-100"
/>
{editFolderName?.edit ? (
<div>
@ -204,7 +217,7 @@ const SideBarFoldersButtonsComponent = ({
folders.map((obj) => ({
name: obj.name,
edit: false,
}))
})),
);
}
if (e.key === "Enter") {
@ -237,10 +250,10 @@ const SideBarFoldersButtonsComponent = ({
};
const updatedFolder = await updateFolder(
body,
item.id!
item.id!,
);
const updateFolders = folders.filter(
(f) => f.name !== item.name
(f) => f.name !== item.name,
);
setFolders([...updateFolders, updatedFolder]);
setFoldersNames({});
@ -248,7 +261,7 @@ const SideBarFoldersButtonsComponent = ({
folders.map((obj) => ({
name: obj.name,
edit: false,
}))
})),
);
} else {
setFoldersNames((old) => ({
@ -259,14 +272,14 @@ const SideBarFoldersButtonsComponent = ({
}}
value={foldersNames[item.name]}
id={`input-folder-${item.name}`}
data-testid={`input-folder`}
/>
</div>
) : (
<span className="block max-w-full truncate opacity-100">
<span className="block w-full truncate opacity-100">
{item.name}
</span>
)}
<div className="flex-1" />
{index > 0 && (
<Button
className="hidden p-0 hover:bg-white group-hover:block hover:dark:bg-[#0c101a00]"
@ -283,21 +296,6 @@ const SideBarFoldersButtonsComponent = ({
/>
</Button>
)}
{/* {index > 0 && (
<Button
className="hidden p-0 hover:bg-white group-hover:block hover:dark:bg-[#0c101a00]"
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
}}
variant={"ghost"}
>
<IconComponent
name={"pencil"}
className=" w-4 stroke-[1.5] text-white "
/>
</Button>
)} */}
<Button
className="hidden p-0 hover:bg-white group-hover:block hover:dark:bg-[#0c101a00]"
onClick={(e) => {
@ -305,7 +303,8 @@ const SideBarFoldersButtonsComponent = ({
e.stopPropagation();
e.preventDefault();
}}
variant={"ghost"}
size="none"
variant="none"
>
<IconComponent
name={"Download"}

View file

@ -12,7 +12,7 @@ import { addVersionToDuplicates } from "../../../utils/reactflowUtils";
const useFileDrop = (folderId, folderChangeCallback) => {
const setFolderDragging = useFolderStore((state) => state.setFolderDragging);
const setFolderIdDragging = useFolderStore(
(state) => state.setFolderIdDragging,
(state) => state.setFolderIdDragging
);
const setErrorData = useAlertStore((state) => state.setErrorData);
@ -45,7 +45,7 @@ const useFileDrop = (folderId, folderChangeCallback) => {
| React.DragEvent<HTMLDivElement>
| React.DragEvent<HTMLButtonElement>
| React.DragEvent<HTMLAnchorElement>,
folderId: string,
folderId: string
) => {
e.preventDefault();
@ -60,7 +60,7 @@ const useFileDrop = (folderId, folderChangeCallback) => {
| React.DragEvent<HTMLDivElement>
| React.DragEvent<HTMLButtonElement>
| React.DragEvent<HTMLAnchorElement>,
folderId: string,
folderId: string
) => {
if (e.dataTransfer.types.some((types) => types === "Files")) {
setFolderDragging(true);
@ -73,7 +73,7 @@ const useFileDrop = (folderId, folderChangeCallback) => {
e:
| React.DragEvent<HTMLDivElement>
| React.DragEvent<HTMLButtonElement>
| React.DragEvent<HTMLAnchorElement>,
| React.DragEvent<HTMLAnchorElement>
) => {
e.preventDefault();
if (e.target === e.currentTarget) {
@ -87,7 +87,7 @@ const useFileDrop = (folderId, folderChangeCallback) => {
| React.DragEvent<HTMLDivElement>
| React.DragEvent<HTMLButtonElement>
| React.DragEvent<HTMLAnchorElement>,
folderId: string,
folderId: string
) => {
if (e?.dataTransfer?.getData("flow")) {
const data = JSON.parse(e?.dataTransfer?.getData("flow"));

View file

@ -5,9 +5,6 @@ import { cn } from "../../utils/utils";
import HorizontalScrollFadeComponent from "../horizontalScrollFadeComponent";
import SideBarButtonsComponent from "./components/sideBarButtons";
import SideBarFoldersButtonsComponent from "./components/sideBarFolderButtons";
import { addFolder } from "../../pages/MainPage/services";
import { useNavigate } from "react-router-dom";
import useFlowStore from "../../stores/flowStore";
type SidebarNavProps = {
items: {
@ -15,7 +12,6 @@ type SidebarNavProps = {
title: string;
icon: React.ReactNode;
}[];
handleOpenNewFolderModal?: () => void;
handleChangeFolder?: (id: string) => void;
handleEditFolder?: (item: FolderType) => void;
handleDeleteFolder?: (item: FolderType) => void;
@ -41,16 +37,20 @@ export default function SidebarNav({
return (
<nav className={cn(className)} {...props}>
<HorizontalScrollFadeComponent>
<SideBarButtonsComponent items={items} pathname={pathname} />
{!loadingFolders && folders?.length > 0 && isFolderPath && (
<SideBarFoldersButtonsComponent
folders={folders}
pathname={pathname}
handleChangeFolder={handleChangeFolder}
handleEditFolder={handleEditFolder}
handleDeleteFolder={handleDeleteFolder}
/>
{items.length > 0 ? (
<SideBarButtonsComponent items={items} pathname={pathname} />
) : (
!loadingFolders &&
folders?.length > 0 &&
isFolderPath && (
<SideBarFoldersButtonsComponent
folders={folders}
pathname={pathname}
handleChangeFolder={handleChangeFolder}
handleEditFolder={handleEditFolder}
handleDeleteFolder={handleDeleteFolder}
/>
)
)}
</HorizontalScrollFadeComponent>
</nav>

View file

@ -0,0 +1,29 @@
import { cn } from "../../../../utils/utils";
export default function ResetColumns({
resetGrid,
}: {
resetGrid: () => void;
}): JSX.Element {
return (
/*<div className="absolute left-2 bottom-1 cursor-pointer">
<div
className="flex h-10 items-center justify-center px-2 pl-3 rounded-md border border-ring/60 text-sm text-[#bccadc] ring-offset-background placeholder:text-muted-foreground hover:bg-muted focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
onClick={() => setShow(!show)}
>
<ForwardedIconComponent name="Settings"></ForwardedIconComponent>
<ForwardedIconComponent name={show ? "ChevronLeft" : "ChevronRight"} className="transition-all"></ForwardedIconComponent>
</div>
</div>*/
<div className={cn("absolute bottom-4 left-6")}>
<span
className="cursor-pointer underline"
onClick={() => {
resetGrid();
}}
>
Reset Columns
</span>
</div>
);
}

View file

@ -1,11 +1,11 @@
import { CustomCellRendererProps } from "ag-grid-react";
import { cn, isTimeStampString } from "../../utils/utils";
import ArrayReader from "../arrayReaderComponent";
import DateReader from "../dateReaderComponent";
import NumberReader from "../numberReader";
import ObjectRender from "../objectRender";
import StringReader from "../stringReaderComponent";
import { Badge } from "../ui/badge";
import { cn, isTimeStampString } from "../../../../utils/utils";
import ArrayReader from "../../../arrayReaderComponent";
import DateReader from "../../../dateReaderComponent";
import NumberReader from "../../../numberReader";
import ObjectRender from "../../../objectRender";
import StringReader from "../../../stringReaderComponent";
import { Badge } from "../../../ui/badge";
export default function TableAutoCellRender({
value,
@ -43,7 +43,6 @@ export default function TableAutoCellRender({
} else {
return <StringReader string={value} />;
}
break;
case "number":
return <NumberReader number={value} />;
default:

View file

@ -0,0 +1,266 @@
import { CustomCellRendererProps } from "ag-grid-react";
import { cloneDeep } from "lodash";
import { useState } from "react";
import CodeAreaComponent from "../../../codeAreaComponent";
import DictComponent from "../../../dictComponent";
import Dropdown from "../../../dropdownComponent";
import FloatComponent from "../../../floatComponent";
import InputFileComponent from "../../../inputFileComponent";
import InputGlobalComponent from "../../../inputGlobalComponent";
import InputListComponent from "../../../inputListComponent";
import IntComponent from "../../../intComponent";
import KeypairListComponent from "../../../keypairListComponent";
import PromptAreaComponent from "../../../promptComponent";
import TextAreaComponent from "../../../textAreaComponent";
import ToggleShadComponent from "../../../toggleShadComponent";
import useFlowStore from "../../../../stores/flowStore";
import {
convertObjToArray,
convertValuesToNumbers,
hasDuplicateKeys,
scapedJSONStringfy,
} from "../../../../utils/reactflowUtils";
import { classNames } from "../../../../utils/utils";
export default function TableNodeCellRender({
node: { data },
value: {
value,
nodeClass,
handleOnNewValue: handleOnNewValueNode,
handleOnChangeDb,
},
}: CustomCellRendererProps) {
const handleOnNewValue = (newValue: any, name: string) => {
handleOnNewValueNode(newValue, name);
setTemplateData((old) => {
let newData = cloneDeep(old);
newData.value = newValue;
return newData;
});
setTemplateValue(newValue);
};
const [templateValue, setTemplateValue] = useState(value);
const [templateData, setTemplateData] = useState(data);
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
const edges = useFlowStore((state) => state.edges);
const id = {
inputTypes: templateData.input_types,
type: templateData.type,
id: nodeClass.id,
fieldName: templateData.key,
};
const disabled =
edges.some(
(edge) =>
edge.targetHandle ===
scapedJSONStringfy(
templateData.proxy
? {
...id,
proxy: templateData.proxy,
}
: id,
),
) ?? false;
function getCellType() {
switch (templateData.type) {
case "str":
if (!templateData.options) {
return templateData?.list ? (
<InputListComponent
componentName={templateData.key ?? undefined}
editNode={true}
disabled={disabled}
value={
!templateValue || templateValue === "" ? [""] : templateValue
}
onChange={(value: string[]) => {
handleOnNewValue(value, templateData.key);
}}
/>
) : templateData.multiline ? (
<TextAreaComponent
id={"textarea-edit-" + templateData.name}
data-testid={"textarea-edit-" + templateData.name}
disabled={disabled}
editNode={true}
value={templateValue ?? ""}
onChange={(value: string | string[]) => {
handleOnNewValue(value, templateData.key);
}}
/>
) : (
<InputGlobalComponent
disabled={disabled}
editNode={true}
onChange={(value) => handleOnNewValue(value, templateData.key)}
setDb={(value) => {
handleOnChangeDb(value, templateData.key);
}}
name={templateData.key}
data={templateData}
/>
);
} else {
return (
<Dropdown
editNode={true}
options={templateData.options}
onSelect={(value) => handleOnNewValue(value, templateData.key)}
value={templateValue ?? "Choose an option"}
id={"dropdown-edit-" + templateData.name}
/>
);
}
case "NestedDict":
return (
<DictComponent
disabled={disabled}
editNode={true}
value={templateValue.toString() === "{}" ? {} : templateValue}
onChange={(newValue) => {
handleOnNewValue(newValue, templateData.key);
}}
id="editnode-div-dict-input"
/>
);
case "dict":
return (
<div
className={classNames(
"max-h-48 w-full overflow-auto custom-scroll",
templateValue?.length > 1 ? "my-3" : "",
)}
>
<KeypairListComponent
disabled={disabled}
editNode={true}
value={
templateValue?.length === 0 || !templateValue
? [{ "": "" }]
: convertObjToArray(templateValue, templateData.type)
}
duplicateKey={errorDuplicateKey}
onChange={(newValue) => {
const valueToNumbers = convertValuesToNumbers(newValue);
setErrorDuplicateKey(hasDuplicateKeys(valueToNumbers));
handleOnNewValue(valueToNumbers, templateData.key);
}}
isList={templateData.list ?? false}
/>
</div>
);
case "bool":
return (
<ToggleShadComponent
id={"toggle-edit-" + templateData.name}
disabled={disabled}
enabled={templateValue}
setEnabled={(isEnabled) => {
handleOnNewValue(isEnabled, templateData.key);
}}
size="small"
editNode={true}
/>
);
case "float":
return (
<FloatComponent
disabled={disabled}
editNode={true}
rangeSpec={templateData.rangeSpec}
value={templateValue ?? ""}
onChange={(value) => {
handleOnNewValue(value, templateData.key);
}}
/>
);
case "int":
return (
<IntComponent
rangeSpec={templateData.rangeSpec}
id={"edit-int-input-" + templateData.name}
disabled={disabled}
editNode={true}
value={templateValue ?? ""}
onChange={(value) => {
handleOnNewValue(value, templateData.key);
}}
/>
);
case "file":
return (
<InputFileComponent
editNode={true}
disabled={disabled}
value={templateValue ?? ""}
onChange={(value: string | string[]) => {
handleOnNewValue(value, templateData.key);
}}
fileTypes={templateData.fileTypes}
onFileChange={(filePath: string) => {
templateData.file_path = filePath;
}}
/>
);
case "prompt":
return (
<PromptAreaComponent
readonly={nodeClass.flow ? true : false}
field_name={templateData.key}
editNode={true}
disabled={disabled}
nodeClass={nodeClass}
setNodeClass={(value) => {
nodeClass = value;
}}
value={templateValue ?? ""}
onChange={(value: string | string[]) => {
handleOnNewValue(value, templateData.key);
}}
id={"prompt-area-edit-" + templateData.name}
data-testid={"modal-prompt-input-" + templateData.name}
/>
);
case "code":
return (
<CodeAreaComponent
readonly={nodeClass.flow && templateData.dynamic ? true : false}
dynamic={templateData.dynamic ?? false}
setNodeClass={(value) => {
nodeClass = value;
}}
nodeClass={nodeClass}
disabled={disabled}
editNode={true}
value={templateValue ?? ""}
onChange={(value: string | string[]) => {
handleOnNewValue(value, templateData.key);
}}
id={"code-area-edit" + templateData.name}
/>
);
case "Any":
return <>-</>;
default:
return String(templateValue);
}
}
return (
<div className="group flex h-full w-[300px] items-center justify-center py-2.5">
{getCellType()}
</div>
);
}

View file

@ -0,0 +1,24 @@
import { CustomCellRendererProps } from "ag-grid-react";
import { useState } from "react";
import ToggleShadComponent from "../../../toggleShadComponent";
export default function TableToggleCellRender({
value: { name, enabled, setEnabled },
}: CustomCellRendererProps) {
const [value, setValue] = useState(enabled);
return (
<div className="flex h-full items-center">
<ToggleShadComponent
id={"show" + name}
enabled={value}
setEnabled={(e) => {
setValue(e);
setEnabled(e);
}}
size="small"
editNode={true}
/>
</div>
);
}

View file

@ -0,0 +1,9 @@
import { CustomTooltipProps } from "ag-grid-react";
export default function TableTooltipRender({ value }: CustomTooltipProps) {
return (
<div className="z-45 overflow-y-auto rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1">
{value}
</div>
);
}

View file

@ -1,22 +1,26 @@
import "ag-grid-community/styles/ag-grid.css"; // Mandatory CSS required by the grid
import "ag-grid-community/styles/ag-theme-quartz.css"; // Optional Theme applied to the grid
import { AgGridReact, AgGridReactProps } from "ag-grid-react";
import { ElementRef, forwardRef, useCallback } from "react";
import { ElementRef, forwardRef, useRef } from "react";
import {
DEFAULT_TABLE_ALERT_MSG,
DEFAULT_TABLE_ALERT_TITLE,
} from "../../constants/constants";
import { useDarkStore } from "../../stores/darkStore";
import "../../style/ag-theme-shadcn.css"; // Custom CSS applied to the grid
import { cn } from "../../utils/utils";
import { cn, toTitleCase } from "../../utils/utils";
import ForwardedIconComponent from "../genericIconComponent";
import { Alert, AlertDescription, AlertTitle } from "../ui/alert";
import ResetColumns from "./components/ResetColumns";
import resetGrid from "./utils/reset-grid-columns";
import { useParams } from "react-router-dom";
interface TableComponentProps extends AgGridReactProps {
columnDefs: NonNullable<AgGridReactProps["columnDefs"]>;
rowData: NonNullable<AgGridReactProps["rowData"]>;
alertTitle?: string;
alertDescription?: string;
editable?: boolean | string[];
}
const TableComponent = forwardRef<
@ -31,7 +35,67 @@ const TableComponent = forwardRef<
},
ref,
) => {
let colDef = props.columnDefs.map((col, index) => {
let newCol = {
...col,
headerName: toTitleCase(col.headerName),
};
if (index === props.columnDefs.length - 1) {
newCol = {
...newCol,
resizable: false,
};
}
if (props.onSelectionChanged && index === 0) {
newCol = {
...newCol,
checkboxSelection: true,
headerCheckboxSelection: true,
headerCheckboxSelectionFilteredOnly: true,
};
}
if (
(typeof props.editable === "boolean" && props.editable) ||
(Array.isArray(props.editable) &&
props.editable.includes(newCol.headerName ?? ""))
) {
newCol = {
...newCol,
editable: true,
};
}
return newCol;
});
const gridRef = useRef(null);
// @ts-ignore
const realRef = ref?.current ? ref : gridRef;
const dark = useDarkStore((state) => state.dark);
const initialColumnDefs = useRef(colDef);
const makeLastColumnNonResizable = (columnDefs) => {
columnDefs.forEach((colDef, index) => {
colDef.resizable = index !== columnDefs.length - 1;
});
return columnDefs;
};
const onGridReady = (params) => {
// @ts-ignore
realRef.current = params;
const updatedColumnDefs = makeLastColumnNonResizable([...colDef]);
params.api.setGridOption("columnDefs", updatedColumnDefs);
initialColumnDefs.current = params.api.getColumnDefs();
if (props.onGridReady) props.onGridReady(params);
};
const onColumnMoved = (params) => {
const updatedColumnDefs = makeLastColumnNonResizable(
params.columnApi.getAllGridColumns().map((col) => col.getColDef()),
);
params.api.setGridOption("columnDefs", updatedColumnDefs);
if (props.onColumnMoved) props.onColumnMoved(params);
};
if (props.rowData.length === 0) {
return (
<div className="flex h-full w-full items-center justify-center rounded-md border">
@ -46,12 +110,12 @@ const TableComponent = forwardRef<
</div>
);
}
return (
<div
className={cn(
dark ? "ag-theme-quartz-dark" : "ag-theme-quartz",
"ag-theme-shadcn flex h-full flex-col",
"relative",
)} // applying the grid theme
>
<AgGridReact
@ -60,8 +124,13 @@ const TableComponent = forwardRef<
defaultColDef={{
minWidth: 100,
}}
ref={ref}
columnDefs={colDef}
ref={realRef}
pagination={true}
onGridReady={onGridReady}
onColumnMoved={onColumnMoved}
/>
<ResetColumns resetGrid={() => resetGrid(realRef, initialColumnDefs)} />
</div>
);
},

View file

@ -0,0 +1,12 @@
export default function resetGrid(ref, initialColumnDefs) {
if (ref?.current && ref?.current.api) {
ref.current.api.resetColumnState();
if (initialColumnDefs.current) {
const resetColumns = ref.current.api.applyColumnState({
state: initialColumnDefs.current,
applyOrder: true,
});
return resetColumns;
}
}
}

View file

@ -29,20 +29,18 @@ export default function ToggleShadComponent({
}
return (
<div className={disabled ? "pointer-events-none cursor-not-allowed " : ""}>
<Switch
id={id}
data-testid={id}
style={{
transform: `scaleX(${scaleX}) scaleY(${scaleY})`,
}}
disabled={disabled}
className=""
checked={enabled}
onCheckedChange={(isEnabled: boolean) => {
setEnabled(isEnabled);
}}
></Switch>
</div>
<Switch
id={id}
data-testid={id}
style={{
transform: `scaleX(${scaleX}) scaleY(${scaleY})`,
}}
disabled={disabled}
className=""
checked={enabled}
onCheckedChange={(isEnabled: boolean) => {
setEnabled(isEnabled);
}}
></Switch>
);
}

View file

@ -4,6 +4,7 @@ import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDownIcon } from "@radix-ui/react-icons";
import * as React from "react";
import { cn } from "../../utils/utils";
import ShadTooltip from "../shadTooltipComponent";
const Accordion = AccordionPrimitive.Root;
@ -22,9 +23,14 @@ AccordionItem.displayName = "AccordionItem";
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
>(({ className, children, disabled, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger asChild ref={ref} {...props}>
<AccordionPrimitive.Trigger
disabled={disabled}
asChild
ref={ref}
{...props}
>
<div
className={cn(
"flex flex-1 cursor-pointer items-center justify-between py-4 text-sm font-medium transition-all [&[data-state=open]>svg]:rotate-180",
@ -32,7 +38,18 @@ const AccordionTrigger = React.forwardRef<
)}
>
{children}
<ChevronDownIcon className="h-4 w-4 font-bold text-primary transition-transform duration-200" />
<ShadTooltip
styleClasses="z-50"
content={disabled ? "Empty" : ""}
side="top"
>
<ChevronDownIcon
className={cn(
"h-4 w-4 font-bold transition-transform duration-200",
disabled ? "text-muted-foreground" : "text-primary"
)}
/>
</ShadTooltip>
</div>
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>

View file

@ -2,9 +2,10 @@ import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
import { cn } from "../../utils/utils";
import ForwardedIconComponent from "../genericIconComponent";
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
{
variants: {
variant: {
@ -19,6 +20,7 @@ const buttonVariants = cva(
"border border-muted bg-muted text-secondary-foreground hover:bg-secondary-foreground/5",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "underline-offset-4 hover:underline text-primary",
none: "",
},
size: {
default: "h-10 py-2 px-4",
@ -26,6 +28,7 @@ const buttonVariants = cva(
xs: "py-0.5 px-3 rounded-md",
lg: "h-11 px-8 rounded-md",
icon: "py-1 px-1 rounded-md",
none: "",
},
},
defaultVariants: {
@ -39,6 +42,7 @@ export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
loading?: boolean;
}
function toTitleCase(text: string) {
@ -49,19 +53,49 @@ function toTitleCase(text: string) {
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, children, ...props }, ref) => {
(
{
className,
variant,
size,
loading,
type,
disabled,
asChild = false,
children,
...props
},
ref
) => {
const Comp = asChild ? Slot : "button";
let newChildren = children;
if (typeof children === "string") {
newChildren = toTitleCase(children);
}
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
children={newChildren}
{...props}
/>
<>
<Comp
className={cn(buttonVariants({ variant, size, className }))}
disabled={loading || disabled}
{...(asChild ? {} : { type: type || "button" })}
ref={ref}
{...props}
>
{loading ? (
<span className="relative">
<span className="invisible">{newChildren}</span>
<span className="absolute inset-0 flex items-center justify-center">
<ForwardedIconComponent
name={"Loader2"}
className={"animate-spin"}
/>
</span>
</span>
) : (
newChildren
)}
</Comp>
</>
);
}
);

View file

@ -8,7 +8,7 @@ const Card = React.forwardRef<
<div
ref={ref}
className={cn(
"flex flex-col justify-between rounded-lg border bg-muted text-card-foreground shadow-sm transition-all hover:shadow-lg",
"flex flex-col justify-between rounded-lg border bg-muted text-card-foreground shadow-sm transition-all",
className
)}
{...props}

View file

@ -31,11 +31,7 @@ function RefreshButton({
// icon class name should take into account the disabled state and the loading state
const disabledIconTextClass = disabled ? "text-muted-foreground" : "";
const iconClassName = cn(
"h-4 w-4",
isLoading ? "animate-spin" : "animate-wiggle",
disabledIconTextClass
);
const iconClassName = cn("h-4 w-4 animate-wiggle", disabledIconTextClass);
return (
<Button
@ -44,10 +40,11 @@ function RefreshButton({
className={classNames}
onClick={handleClick}
id={id}
loading={isLoading}
>
{button_text && <span className="mr-1">{button_text}</span>}
<IconComponent
name={isLoading ? "Loader2" : "RefreshCcw"}
name={"RefreshCcw"}
className={iconClassName}
id={id + "-icon"}
/>

View file

@ -0,0 +1,45 @@
"use client";
import * as TogglePrimitive from "@radix-ui/react-toggle";
import { cva, type VariantProps } from "class-variance-authority";
import * as React from "react";
import { cn } from "../../utils/utils";
const toggleVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-transparent",
outline:
"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
},
size: {
default: "h-10 px-3",
sm: "h-9 px-2.5",
lg: "h-11 px-5",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
const Toggle = React.forwardRef<
React.ElementRef<typeof TogglePrimitive.Root>,
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
VariantProps<typeof toggleVariants>
>(({ className, variant, size, ...props }, ref) => (
<TogglePrimitive.Root
ref={ref}
className={cn(toggleVariants({ variant, size, className }))}
{...props}
/>
));
Toggle.displayName = TogglePrimitive.Root.displayName;
export { Toggle, toggleVariants };

View file

@ -28,4 +28,26 @@ const TooltipContent = React.forwardRef<
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
const TooltipContentWithoutPortal = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-45 overflow-y-auto rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1",
className
)}
{...props}
/>
));
TooltipContentWithoutPortal.displayName = TooltipPrimitive.Content.displayName;
export {
Tooltip,
TooltipContent,
TooltipContentWithoutPortal,
TooltipProvider,
TooltipTrigger,
};

View file

@ -23,6 +23,7 @@ export const USER_EDIT_ERROR_ALERT = "Error on edit user";
export const USER_ADD_ERROR_ALERT = "Error when adding new user";
export const SIGNIN_ERROR_ALERT = "Error signing in";
export const DEL_KEY_ERROR_ALERT = "Error on delete key";
export const DEL_KEY_ERROR_ALERT_PLURAL = "Error on delete keys";
export const UPLOAD_ERROR_ALERT = "Error uploading file";
export const WRONG_FILE_ERROR_ALERT = "Invalid file type";
export const UPLOAD_ALERT_LIST = "Please upload a JSON file";
@ -54,6 +55,7 @@ export const USER_DEL_SUCCESS_ALERT = "Success! User deleted!";
export const USER_EDIT_SUCCESS_ALERT = "Success! User edited!";
export const USER_ADD_SUCCESS_ALERT = "Success! New user added!";
export const DEL_KEY_SUCCESS_ALERT = "Success! Key deleted!";
export const DEL_KEY_SUCCESS_ALERT_PLURAL = "Success! Keys deleted!";
export const FLOW_BUILD_SUCCESS_ALERT = `Flow built successfully`;
export const SAVE_SUCCESS_ALERT = "Changes saved successfully!";

View file

@ -590,6 +590,7 @@ export const CONTROL_PATCH_USER_STATE = {
password: "",
cnfPassword: "",
gradient: "",
apikey: "",
};
export const CONTROL_LOGIN_STATE = {
@ -612,11 +613,8 @@ export const FETCH_ERROR_DESCRIPION =
export const SIGN_UP_SUCCESS = "Account created! Await admin activation. ";
export const API_PAGE_PARAGRAPH_1 =
"Your secret API keys are listed below. Please note that we do not display your secret API keys again after you generate them.";
export const API_PAGE_PARAGRAPH_2 =
"Do not share your API key with others, or expose it in the browser or other client-side code.";
export const API_PAGE_PARAGRAPH =
"Your secret API keys are listed below. Do not share your API key with others, or expose it in the browser or other client-side code.";
export const API_PAGE_USER_KEYS =
"This user does not have any keys assigned at the moment.";
@ -670,7 +668,7 @@ export const ZERO_NOTIFICATIONS = "No new notifications";
export const SUCCESS_BUILD = "Built sucessfully ✨";
export const ALERT_SAVE_WITH_API =
"Caution: Uncheck this box only removes API keys from fields specifically designated for API keys.";
"Caution: Unchecking this box only removes API keys from fields specifically designated for API keys.";
export const SAVE_WITH_API_CHECKBOX = "Save with my API keys";
export const EDIT_TEXT_MODAL_TITLE = "Edit Text";
@ -738,3 +736,5 @@ export const DEFAULT_TABLE_ALERT_MSG = `Oops! It seems there's no data to displa
export const DEFAULT_TABLE_ALERT_TITLE = "No Data Available";
export const LOCATIONS_TO_RETURN = ["/flow/", "/settings/"];
export const MAX_BATCH_SIZE = 50;

View file

@ -2,11 +2,11 @@ import axios, { AxiosError, AxiosInstance } from "axios";
import { useContext, useEffect } from "react";
import { Cookies } from "react-cookie";
import { renewAccessToken } from ".";
import { AUTHORIZED_DUPLICATE_REQUESTS } from "../../constants/constants";
import { BuildStatus } from "../../constants/enums";
import { AuthContext } from "../../contexts/authContext";
import useAlertStore from "../../stores/alertStore";
import useFlowStore from "../../stores/flowStore";
import { checkDuplicateRequestAndStoreRequest } from "./helpers/check-duplicate-requests";
// Create a new Axios instance
const api: AxiosInstance = axios.create({
@ -81,28 +81,12 @@ function ApiInterceptor() {
// Request interceptor to add access token to every request
const requestInterceptor = api.interceptors.request.use(
(config) => {
const lastUrl = localStorage.getItem("lastUrlCalled");
const lastMethodCalled = localStorage.getItem("lastMethodCalled");
const checkRequest = checkDuplicateRequestAndStoreRequest(config);
const isContained = AUTHORIZED_DUPLICATE_REQUESTS.some((request) =>
config?.url!.includes(request),
);
if (
config?.url === lastUrl &&
!isContained &&
lastMethodCalled === config.method
) {
return Promise.reject("Duplicate request");
if (!checkRequest) {
return Promise.reject("Duplicate request.");
}
localStorage.setItem("lastUrlCalled", config.url ?? "");
localStorage.setItem("lastMethodCalled", config.method ?? "");
localStorage.setItem(
"lastRequestData",
JSON.stringify(config.data) ?? "",
);
const accessToken = cookies.get("access_token_lf");
if (accessToken && !isAuthorizedURL(config?.url)) {
config.headers["Authorization"] = `Bearer ${accessToken}`;

View file

@ -0,0 +1,30 @@
import { AUTHORIZED_DUPLICATE_REQUESTS } from "../../../constants/constants";
export function checkDuplicateRequestAndStoreRequest(config) {
const lastUrl = localStorage.getItem("lastUrlCalled");
const lastMethodCalled = localStorage.getItem("lastMethodCalled");
const lastRequestTime = localStorage.getItem("lastRequestTime");
const currentTime = Date.now();
const isContained = AUTHORIZED_DUPLICATE_REQUESTS.some((request) =>
config?.url!.includes(request)
);
if (
config?.url === lastUrl &&
!isContained &&
lastMethodCalled === config.method &&
lastMethodCalled === "get" && // Assuming you want to check only for GET requests
lastRequestTime &&
currentTime - parseInt(lastRequestTime, 10) < 800
) {
return false;
}
localStorage.setItem("lastUrlCalled", config.url ?? "");
localStorage.setItem("lastMethodCalled", config.method ?? "");
localStorage.setItem("lastRequestTime", currentTime.toString());
return true;
}

View file

@ -1,7 +1,7 @@
import { ColDef, ColGroupDef } from "ag-grid-community";
import { AxiosRequestConfig, AxiosResponse } from "axios";
import { Edge, Node, ReactFlowJsonObject } from "reactflow";
import { BASE_URL_API } from "../../constants/constants";
import { BASE_URL_API, MAX_BATCH_SIZE } from "../../constants/constants";
import { api } from "../../controllers/API/api";
import {
APIObjectType,
@ -17,6 +17,7 @@ import {
} from "../../types/api/index";
import { UserInputType } from "../../types/components";
import { FlowStyleType, FlowType } from "../../types/flow";
import { Message } from "../../types/messages";
import { StoreComponentResponse } from "../../types/store";
import { FlowPoolType } from "../../types/zustand/flow";
import { extractColumnsFromRows } from "../../utils/utils";
@ -1002,12 +1003,41 @@ export async function deleteFlowPool(
return await api.delete(`${BASE_URL_API}monitor/builds`, config);
}
/**
* Deletes multiple flow components by their IDs.
* @param flowIds - An array of flow IDs to be deleted.
* @param token - The authorization token for the API request.
* @returns A promise that resolves to an array of AxiosResponse objects representing the delete responses.
*/
export async function multipleDeleteFlowsComponents(
flowIds: string[]
): Promise<AxiosResponse<any>> {
return await api.post(`${BASE_URL_API}flows/multiple_delete/`, {
flow_ids: flowIds,
});
): Promise<AxiosResponse<any>[]> {
const batches: string[][] = [];
// Split the flowIds into batches
for (let i = 0; i < flowIds.length; i += MAX_BATCH_SIZE) {
batches.push(flowIds.slice(i, i + MAX_BATCH_SIZE));
}
// Function to delete a batch of flow IDs
const deleteBatch = async (batch: string[]): Promise<AxiosResponse<any>> => {
try {
return await api.delete(`${BASE_URL_API}flows/`, {
data: batch,
});
} catch (error) {
console.error("Error deleting flows:", error);
throw error;
}
};
// Execute all delete requests
const responses: Promise<AxiosResponse<any>>[] = batches.map((batch) =>
deleteBatch(batch)
);
// Return the responses after all requests are completed
return Promise.all(responses);
}
export async function getTransactionTable(
@ -1026,16 +1056,47 @@ export async function getTransactionTable(
}
export async function getMessagesTable(
id: string,
mode: "intersection" | "union",
id?: string,
excludedFields?: string[],
params = {}
): Promise<{ rows: Array<object>; columns: Array<ColDef | ColGroupDef> }> {
): Promise<{ rows: Array<Message>; columns: Array<ColDef | ColGroupDef> }> {
const config = {};
config["params"] = { flow_id: id };
if (id) {
config["params"] = { flow_id: id };
}
if (params) {
config["params"] = { ...config["params"], ...params };
}
const rows = await api.get(`${BASE_URL_API}monitor/messages`, config);
const columns = extractColumnsFromRows(rows.data, mode);
const columns = extractColumnsFromRows(rows.data, mode, excludedFields);
return { rows: rows.data, columns };
}
export async function getSessions(id?: string): Promise<Array<string>> {
const config = {};
if (id) {
config["params"] = { flow_id: id };
}
const rows = await api.get(`${BASE_URL_API}monitor/messages`, config);
const sessions = new Set<string>();
rows.data.forEach((row) => {
sessions.add(row.session_id);
});
return Array.from(sessions);
}
export async function deleteMessagesFn(ids: number[]) {
try {
return await api.delete(`${BASE_URL_API}monitor/messages`, {
data: ids,
});
} catch (error) {
console.error("Error deleting flows:", error);
throw error;
}
}
export async function updateMessageApi(data: Message) {
return await api.post(`${BASE_URL_API}monitor/messages/${data.index}`, data);
}

View file

@ -6,9 +6,9 @@ const SvgBotMessageSquare = (props) => (
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="lucide lucide-bot-message-square"
{...props}
>

View file

@ -0,0 +1,25 @@
export default function SvgStreamlit(props) {
return (
<svg
width="301"
height="165"
viewBox="0 0 301 165"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M150.731 101.547L98.1387 73.7471L6.84674 25.4969C6.7634 25.4136 6.59674 25.4136 6.51341 25.4136C3.18007 23.8303 -0.236608 27.1636 1.0134 30.497L47.5302 149.139L47.5385 149.164C47.5885 149.281 47.6302 149.397 47.6802 149.514C49.5885 153.939 53.7552 156.672 58.2886 157.747C58.6719 157.831 58.9461 157.906 59.4064 157.998C59.8645 158.1 60.5052 158.239 61.0552 158.281C61.1469 158.289 61.2302 158.289 61.3219 158.297H61.3886C61.4552 158.306 61.5219 158.306 61.5886 158.314H61.6802C61.7386 158.322 61.8052 158.322 61.8636 158.322H61.9719C62.0386 158.331 62.1052 158.331 62.1719 158.331V158.331C121.084 164.754 180.519 164.754 239.431 158.331V158.331C240.139 158.331 240.831 158.297 241.497 158.231C241.714 158.206 241.922 158.181 242.131 158.156C242.156 158.147 242.189 158.147 242.214 158.139C242.356 158.122 242.497 158.097 242.639 158.072C242.847 158.047 243.056 158.006 243.264 157.964C243.681 157.872 243.87 157.806 244.436 157.611C245.001 157.417 245.94 157.077 246.527 156.794C247.115 156.511 247.522 156.239 248.014 155.931C248.622 155.547 249.201 155.155 249.788 154.715C250.041 154.521 250.214 154.397 250.397 154.222L250.297 154.164L150.731 101.547Z"
fill="#FF4B4B"
/>
<path
d="M294.766 25.4981H294.683L203.357 73.7483L254.124 149.357L300.524 30.4981V30.3315C301.691 26.8314 298.108 23.6648 294.766 25.4981"
fill="#7D353B"
/>
<path
d="M155.598 2.55572C153.264 -0.852624 148.181 -0.852624 145.931 2.55572L98.1389 73.7477L150.731 101.548L250.398 154.222C251.024 153.609 251.526 153.012 252.056 152.381C252.806 151.456 253.506 150.465 254.123 149.356L203.356 73.7477L155.598 2.55572Z"
fill="#BD4043"
/>
</svg>
);
}

View file

@ -0,0 +1,8 @@
import React, { forwardRef } from "react";
import SvgStreamlit from "./SvgStreamlit";
export const Streamlit = forwardRef<SVGSVGElement, React.PropsWithChildren<{}>>(
(props, ref) => {
return <SvgStreamlit className="icon" ref={ref} {...props} />;
}
);

View file

@ -27,7 +27,7 @@ const useFolderSubmit = (setOpen, folderToEdit) => {
getFoldersApi(true);
setOpen(false);
}
},
}
);
} else {
addFolder(data).then(
@ -42,7 +42,7 @@ const useFolderSubmit = (setOpen, folderToEdit) => {
setErrorData({
title: `Error creating folder.`,
});
},
}
);
}
};

Some files were not shown because too many files have changed in this diff Show more