Formatting

This commit is contained in:
Gabriel Luiz Freitas Almeida 2024-03-08 11:36:32 -03:00
commit 39434eadc4
58 changed files with 630 additions and 1027 deletions

View file

@ -20,9 +20,7 @@ API_WORDS = ["api", "key", "token"]
def has_api_terms(word: str):
return "api" in word and (
"key" in word or ("token" in word and "tokens" not in word)
)
return "api" in word and ("key" in word or ("token" in word and "tokens" not in word))
def remove_api_keys(flow: dict):
@ -32,11 +30,7 @@ def remove_api_keys(flow: dict):
node_data = node.get("data").get("node")
template = node_data.get("template")
for value in template.values():
if (
isinstance(value, dict)
and has_api_terms(value["name"])
and value.get("password")
):
if isinstance(value, dict) and has_api_terms(value["name"]) and value.get("password"):
value["value"] = None
return flow
@ -57,9 +51,7 @@ def build_input_keys_response(langchain_object, artifacts):
input_keys_response["input_keys"][key] = value
# If the object has memory, that memory will have a memory_variables attribute
# memory variables should be removed from the input keys
if hasattr(langchain_object, "memory") and hasattr(
langchain_object.memory, "memory_variables"
):
if hasattr(langchain_object, "memory") and hasattr(langchain_object.memory, "memory_variables"):
# Remove memory variables from input keys
input_keys_response["input_keys"] = {
key: value
@ -69,9 +61,7 @@ def build_input_keys_response(langchain_object, artifacts):
# Add memory variables to memory_keys
input_keys_response["memory_keys"] = langchain_object.memory.memory_variables
if hasattr(langchain_object, "prompt") and hasattr(
langchain_object.prompt, "template"
):
if hasattr(langchain_object, "prompt") and hasattr(langchain_object.prompt, "template"):
input_keys_response["template"] = langchain_object.prompt.template
return input_keys_response
@ -106,11 +96,7 @@ def raw_frontend_data_is_valid(raw_frontend_data):
def is_valid_data(frontend_node, raw_frontend_data):
"""Check if the data is valid for processing."""
return (
frontend_node
and "template" in frontend_node
and raw_frontend_data_is_valid(raw_frontend_data)
)
return frontend_node and "template" in frontend_node and raw_frontend_data_is_valid(raw_frontend_data)
def update_template_values(frontend_template, raw_template):
@ -150,9 +136,7 @@ def get_file_path_value(file_path):
# If the path is not in the cache dir, return empty string
# This is to prevent access to files outside the cache dir
# If the path is not a file, return empty string
if not path.exists() or not str(path).startswith(
user_cache_dir("langflow", "langflow")
):
if not path.exists() or not str(path).startswith(user_cache_dir("langflow", "langflow")):
return ""
return file_path
@ -183,9 +167,7 @@ async def check_langflow_version(component: StoreComponentCreate):
langflow_version = get_lf_version_from_pypi()
if langflow_version is None:
raise HTTPException(
status_code=500, detail="Unable to verify the latest version of Langflow"
)
raise HTTPException(status_code=500, detail="Unable to verify the latest version of Langflow")
elif langflow_version != component.last_tested_version:
warnings.warn(
f"Your version of Langflow ({component.last_tested_version}) is outdated. "

View file

@ -103,9 +103,7 @@ def fix_variable(var, invalid_chars, wrong_variables):
# Handle variables starting with a number
if var[0].isdigit():
invalid_chars.append(var[0])
new_var, invalid_chars, wrong_variables = fix_variable(
var[1:], invalid_chars, wrong_variables
)
new_var, invalid_chars, wrong_variables = fix_variable(var[1:], invalid_chars, wrong_variables)
# Temporarily replace {{ and }} to avoid treating them as invalid
new_var = new_var.replace("{{", "ᴛᴇᴍᴘᴏᴘᴇɴ").replace("}}", "ᴛᴇᴍᴘʟsᴇ")
@ -132,9 +130,7 @@ def check_variable(var, invalid_chars, wrong_variables, empty_variables):
return wrong_variables, empty_variables
def check_for_errors(
input_variables, fixed_variables, wrong_variables, empty_variables
):
def check_for_errors(input_variables, fixed_variables, wrong_variables, empty_variables):
if any(var for var in input_variables if var not in fixed_variables):
error_message = (
f"Error: Input variables contain invalid characters or formats. \n"
@ -159,17 +155,11 @@ def check_input_variables(input_variables):
if is_json_like(var):
continue
new_var, wrong_variables, empty_variables = fix_variable(
var, invalid_chars, wrong_variables
)
wrong_variables, empty_variables = check_variable(
var, INVALID_CHARACTERS, wrong_variables, empty_variables
)
new_var, wrong_variables, empty_variables = fix_variable(var, invalid_chars, wrong_variables)
wrong_variables, empty_variables = check_variable(var, INVALID_CHARACTERS, wrong_variables, empty_variables)
fixed_variables.append(new_var)
variables_to_check.append(var)
check_for_errors(
variables_to_check, fixed_variables, wrong_variables, empty_variables
)
check_for_errors(variables_to_check, fixed_variables, wrong_variables, empty_variables)
return fixed_variables

View file

@ -122,9 +122,7 @@ async def build_vertex(
else:
raise ValueError(f"No result found for vertex {vertex_id}")
next_vertices_ids = vertex.successors_ids
next_vertices_ids = [
v for v in next_vertices_ids if graph.should_run_vertex(v)
]
next_vertices_ids = [v for v in next_vertices_ids if graph.should_run_vertex(v)]
result_data_response = ResultDataResponse(**result_dict.model_dump())

View file

@ -52,9 +52,7 @@ def get_all(
raise HTTPException(status_code=500, detail=str(exc)) from exc
@router.post(
"/run/{flow_id}", response_model=RunResponse, response_model_exclude_none=True
)
@router.post("/run/{flow_id}", response_model=RunResponse, response_model_exclude_none=True)
async def run_flow_with_caching(
session: Annotated[Session, Depends(get_session)],
flow_id: str,
@ -105,9 +103,7 @@ async def run_flow_with_caching(
"""
try:
if inputs is not None:
input_values: list[dict[str, Union[str, list[str]]]] = [
_input.model_dump() for _input in inputs
]
input_values: list[dict[str, Union[str, list[str]]]] = [_input.model_dump() for _input in inputs]
else:
input_values = [{}]
@ -115,9 +111,7 @@ async def run_flow_with_caching(
outputs = []
if session_id:
session_data = await session_service.load_session(
session_id, flow_id=flow_id
)
session_data = await session_service.load_session(session_id, flow_id=flow_id)
graph, artifacts = session_data if session_data else (None, None)
task_result: Any = None
if not graph:
@ -136,11 +130,7 @@ async def run_flow_with_caching(
else:
# Get the flow that matches the flow_id and belongs to the user
# flow = session.query(Flow).filter(Flow.id == flow_id).filter(Flow.user_id == api_key_user.id).first()
flow = session.exec(
select(Flow)
.where(Flow.id == flow_id)
.where(Flow.user_id == api_key_user.id)
).first()
flow = session.exec(select(Flow).where(Flow.id == flow_id).where(Flow.user_id == api_key_user.id)).first()
if flow is None:
raise ValueError(f"Flow {flow_id} not found")
@ -164,18 +154,12 @@ async def run_flow_with_caching(
# StatementError('(builtins.ValueError) badly formed hexadecimal UUID string')
if "badly formed hexadecimal UUID string" in str(exc):
# This means the Flow ID is not a valid UUID which means it can't find the flow
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
) from exc
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
except ValueError as exc:
if f"Flow {flow_id} not found" in str(exc):
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
) from exc
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
else:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)
) from exc
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) from exc
@router.post(
@ -204,8 +188,7 @@ async def process(
"""
# Raise a depreciation warning
logger.warning(
"The /process endpoint is deprecated and will be removed in a future version. "
"Please use /run instead."
"The /process endpoint is deprecated and will be removed in a future version. " "Please use /run instead."
)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
@ -277,16 +260,12 @@ async def custom_component(
built_frontend_node = build_custom_component_template(component, user_id=user.id)
built_frontend_node = update_frontend_node_with_template_values(
built_frontend_node, raw_code.frontend_node
)
built_frontend_node = update_frontend_node_with_template_values(built_frontend_node, raw_code.frontend_node)
return built_frontend_node
@router.post("/custom_component/reload", status_code=HTTPStatus.OK)
async def reload_custom_component(
path: str, user: User = Depends(get_current_active_user)
):
async def reload_custom_component(path: str, user: User = Depends(get_current_active_user)):
from langflow.interface.custom.utils import build_custom_component_template
try:

View file

@ -46,9 +46,7 @@ def post_validate_prompt(prompt_request: ValidatePromptRequest):
)
if not prompt_request.custom_fields:
prompt_request.custom_fields = defaultdict(list)
old_custom_fields = get_old_custom_fields(
prompt_request.custom_fields, prompt_request.name
)
old_custom_fields = get_old_custom_fields(prompt_request.custom_fields, prompt_request.name)
add_new_variables_to_template(
input_variables,
@ -65,9 +63,7 @@ def post_validate_prompt(prompt_request: ValidatePromptRequest):
prompt_request.name,
)
update_input_variables_field(
input_variables, prompt_request.frontend_node.template
)
update_input_variables_field(input_variables, prompt_request.frontend_node.template)
return PromptValidationResponse(
input_variables=input_variables,

View file

@ -45,9 +45,7 @@ class ChatComponent(CustomComponent):
return []
if not session_id or not sender or not sender_name:
raise ValueError(
"All of session_id, sender, and sender_name must be provided."
)
raise ValueError("All of session_id, sender, and sender_name must be provided.")
if isinstance(message, Record):
record = message
record.data.update(

View file

@ -67,9 +67,7 @@ def validate_prompt(prompt_template: str, silent_errors: bool = False) -> list[s
# Check if there are invalid characters in the input_variables
input_variables = check_input_variables(input_variables)
if any(var in INVALID_NAMES for var in input_variables):
raise ValueError(
f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. "
)
raise ValueError(f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. ")
try:
PromptTemplate(template=prompt_template, input_variables=input_variables)
@ -118,9 +116,7 @@ def add_new_variables_to_template(input_variables, custom_fields, template, name
raise HTTPException(status_code=500, detail=str(exc)) from exc
def remove_old_variables_from_template(
old_custom_fields, input_variables, custom_fields, template, name
):
def remove_old_variables_from_template(old_custom_fields, input_variables, custom_fields, template, name):
for variable in old_custom_fields:
if variable not in input_variables:
try:

View file

@ -56,9 +56,7 @@ class APIRequest(CustomComponent):
data = body if body else None
payload = json.dumps(data)
try:
response = await client.request(
method, url, headers=headers, content=payload, timeout=timeout
)
response = await client.request(method, url, headers=headers, content=payload, timeout=timeout)
try:
result = response.json()
except Exception:
@ -108,9 +106,6 @@ class APIRequest(CustomComponent):
bodies = [body.data]
async with httpx.AsyncClient() as client:
results = await asyncio.gather(
*[
self.make_request(client, method, u, headers, rec, timeout)
for u, rec in zip(urls, bodies)
]
*[self.make_request(client, method, u, headers, rec, timeout) for u, rec in zip(urls, bodies)]
)
return results

View file

@ -5,7 +5,7 @@ from langflow.memory import delete_messages, get_messages
class ClearMessageHistoryComponent(CustomComponent):
display_name = "Clear Message History"
description = "A component to clear the message history."
icon="ClearMessageHistory"
icon = "ClearMessageHistory"
beta: bool = True
def build_config(self):

View file

@ -38,18 +38,12 @@ class RunFlowComponent(CustomComponent):
return []
records = []
for message in messages:
message_dict = (
message if isinstance(message, dict) else message.model_dump()
)
record = Record(
text=message_dict.get("text", ""), data={"result": result_data}
)
message_dict = message if isinstance(message, dict) else message.model_dump()
record = Record(text=message_dict.get("text", ""), data={"result": result_data})
records.append(record)
return records
async def build(
self, input_value: Text, flow_name: str, tweaks: NestedDict
) -> List[Record]:
async def build(self, input_value: Text, flow_name: str, tweaks: NestedDict) -> List[Record]:
results: List[Optional[ResultData]] = await self.run_flow(
input_value=input_value, flow_name=flow_name, tweaks=tweaks
)

View file

@ -93,11 +93,6 @@ class TextToRecordComponent(CustomComponent):
if mode == "Text":
data = kwargs
else:
data = {
k: v
for key, d in kwargs.items()
for k, v in d.items()
if key not in ["mode", "n_keys", "keys"]
}
data = {k: v for key, d in kwargs.items() for k, v in d.items() if key not in ["mode", "n_keys", "keys"]}
record = Record(data=data)
return record

View file

@ -9,6 +9,7 @@ class PythonFunctionComponent(CustomComponent):
display_name = "Python Function"
description = "Define a Python function."
icon = "Python"
def build_config(self):
return {
"function_code": {

View file

@ -23,9 +23,7 @@ class PromptComponent(CustomComponent):
) -> Text:
prompt_template = PromptTemplate.from_template(Text(template))
kwargs = dict_values_to_string(kwargs)
kwargs = {
k: "\n".join(v) if isinstance(v, list) else v for k, v in kwargs.items()
}
kwargs = {k: "\n".join(v) if isinstance(v, list) else v for k, v in kwargs.items()}
try:
formated_prompt = prompt_template.format(**kwargs)
except Exception as exc:

View file

@ -9,6 +9,7 @@ class ChatOutput(ChatComponent):
display_name = "Chat Output"
description = "Used to send a message to the chat."
icon = "ChatOutput"
def build(
self,
sender: Optional[str] = "Machine",

View file

@ -11,9 +11,7 @@ from langflow.utils.util import build_loader_repr_from_records, unescape_string
class RecursiveCharacterTextSplitterComponent(CustomComponent):
display_name: str = "Recursive Character Text Splitter"
description: str = "Split text into chunks of a specified length."
documentation: str = (
"https://docs.langflow.org/components/text-splitters#recursivecharactertextsplitter"
)
documentation: str = "https://docs.langflow.org/components/text-splitters#recursivecharactertextsplitter"
def build_config(self):
return {

View file

@ -84,8 +84,7 @@ class ChromaComponent(CustomComponent):
if chroma_server_host is not None:
chroma_settings = chromadb.config.Settings(
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins
or None,
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins or None,
chroma_server_host=chroma_server_host,
chroma_server_port=chroma_server_port or None,
chroma_server_grpc_port=chroma_server_grpc_port or None,
@ -106,9 +105,7 @@ class ChromaComponent(CustomComponent):
documents.append(_input)
if documents is not None and embedding is not None:
if len(documents) == 0:
raise ValueError(
"If documents are provided, there must be at least one document."
)
raise ValueError("If documents are provided, there must be at least one document.")
chroma = Chroma.from_documents(
documents=documents, # type: ignore
persist_directory=index_directory,

View file

@ -9,9 +9,7 @@ from langflow.schema.schema import Record
class MongoDBAtlasComponent(CustomComponent):
display_name = "MongoDB Atlas"
description = (
"Construct a `MongoDB Atlas Vector Search` vector store from raw documents."
)
description = "Construct a `MongoDB Atlas Vector Search` vector store from raw documents."
icon = "MongoDB"
def build_config(self):
@ -39,9 +37,7 @@ class MongoDBAtlasComponent(CustomComponent):
try:
from pymongo import MongoClient
except ImportError:
raise ImportError(
"Please install pymongo to use MongoDB Atlas Vector Store"
)
raise ImportError("Please install pymongo to use MongoDB Atlas Vector Store")
try:
mongo_client: MongoClient = MongoClient(mongodb_atlas_cluster_uri)
collection = mongo_client[db_name][collection_name]

View file

@ -66,9 +66,7 @@ class RedisComponent(CustomComponent):
documents.append(_input)
if not documents:
if schema is None:
raise ValueError(
"If no documents are provided, a schema must be provided."
)
raise ValueError("If no documents are provided, a schema must be provided.")
redis_vs = Redis.from_existing_index(
embedding=embedding,
index_name=redis_index_name,

View file

@ -35,9 +35,7 @@ class SupabaseComponent(CustomComponent):
supabase_url: str = "",
table_name: str = "",
) -> Union[VectorStore, SupabaseVectorStore, BaseRetriever]:
supabase: Client = create_client(
supabase_url, supabase_key=supabase_service_key
)
supabase: Client = create_client(supabase_url, supabase_key=supabase_service_key)
documents = []
for _input in inputs or []:
if isinstance(_input, Record):

View file

@ -15,9 +15,7 @@ from langflow.schema.schema import Record
class VectaraComponent(CustomComponent):
display_name: str = "Vectara"
description: str = "Implementation of Vector Store using Vectara"
documentation = (
"https://python.langchain.com/docs/integrations/vectorstores/vectara"
)
documentation = "https://python.langchain.com/docs/integrations/vectorstores/vectara"
icon = "Vectara"
field_config = {
"vectara_customer_id": {

View file

@ -12,9 +12,7 @@ from langflow.schema.schema import Record
class WeaviateVectorStoreComponent(CustomComponent):
display_name: str = "Weaviate"
description: str = "Implementation of Vector Store using Weaviate"
documentation = (
"https://python.langchain.com/docs/integrations/vectorstores/weaviate"
)
documentation = "https://python.langchain.com/docs/integrations/vectorstores/weaviate"
field_config = {
"url": {"display_name": "Weaviate URL", "value": "http://localhost:8080"},
"api_key": {

View file

@ -16,9 +16,7 @@ class PGVectorComponent(CustomComponent):
display_name: str = "PGVector"
description: str = "Implementation of Vector Store using PostgreSQL"
documentation = (
"https://python.langchain.com/docs/integrations/vectorstores/pgvector"
)
documentation = "https://python.langchain.com/docs/integrations/vectorstores/pgvector"
def build_config(self):
"""

View file

@ -13,9 +13,7 @@ if TYPE_CHECKING:
class SourceHandle(BaseModel):
baseClasses: List[str] = Field(
..., description="List of base classes for the source handle."
)
baseClasses: List[str] = Field(..., description="List of base classes for the source handle.")
dataType: str = Field(..., description="Data type for the source handle.")
id: str = Field(..., description="Unique identifier for the source handle.")
@ -23,9 +21,7 @@ class SourceHandle(BaseModel):
class TargetHandle(BaseModel):
fieldName: str = Field(..., description="Field name for the target handle.")
id: str = Field(..., description="Unique identifier for the target handle.")
inputTypes: Optional[List[str]] = Field(
None, description="List of input types for the target handle."
)
inputTypes: Optional[List[str]] = Field(None, description="List of input types for the target handle.")
type: str = Field(..., description="Type of the target handle.")
@ -54,24 +50,16 @@ class Edge:
def validate_handles(self, source, target) -> None:
if self.target_handle.inputTypes is None:
self.valid_handles = (
self.target_handle.type in self.source_handle.baseClasses
)
self.valid_handles = self.target_handle.type in self.source_handle.baseClasses
else:
self.valid_handles = (
any(
baseClass in self.target_handle.inputTypes
for baseClass in self.source_handle.baseClasses
)
any(baseClass in self.target_handle.inputTypes for baseClass in self.source_handle.baseClasses)
or self.target_handle.type in self.source_handle.baseClasses
)
if not self.valid_handles:
logger.debug(self.source_handle)
logger.debug(self.target_handle)
raise ValueError(
f"Edge between {source.vertex_type} and {target.vertex_type} "
f"has invalid handles"
)
raise ValueError(f"Edge between {source.vertex_type} and {target.vertex_type} " f"has invalid handles")
def __setstate__(self, state):
self.source_id = state["source_id"]
@ -88,11 +76,7 @@ class Edge:
# Both lists contain strings and sometimes a string contains the value we are
# looking for e.g. comgin_out=["Chain"] and target_reqs=["LLMChain"]
# so we need to check if any of the strings in source_types is in target_reqs
self.valid = any(
output in target_req
for output in self.source_types
for target_req in self.target_reqs
)
self.valid = any(output in target_req for output in self.source_types for target_req in self.target_reqs)
# Get what type of input the target node is expecting
self.matched_type = next(
@ -103,10 +87,7 @@ class Edge:
if no_matched_type:
logger.debug(self.source_types)
logger.debug(self.target_reqs)
raise ValueError(
f"Edge between {source.vertex_type} and {target.vertex_type} "
f"has no matched type"
)
raise ValueError(f"Edge between {source.vertex_type} and {target.vertex_type} " f"has no matched type")
def __repr__(self) -> str:
return (
@ -118,13 +99,9 @@ class Edge:
return hash(self.__repr__())
def __eq__(self, __o: object) -> bool:
if not isinstance(__o, Edge):
return False
return (
self._source_handle == __o._source_handle
and self._target_handle == __o._target_handle
)
return self._source_handle == __o._source_handle and self._target_handle == __o._target_handle
class ContractEdge(Edge):
@ -181,9 +158,7 @@ class ContractEdge(Edge):
return f"{self.source_id} -[{self.target_param}]-> {self.target_id}"
def log_transaction(
edge: ContractEdge, source: "Vertex", target: "Vertex", status, error=None
):
def log_transaction(edge: ContractEdge, source: "Vertex", target: "Vertex", status, error=None):
try:
monitor_service = get_monitor_service()
clean_params = build_clean_params(target)

View file

@ -74,9 +74,7 @@ class Graph:
"""Returns the state of the graph."""
return self.state_manager.get_state(name, run_id=self._run_id)
def update_state(
self, name: str, record: Union[str, Record], caller: Optional[str] = None
) -> None:
def update_state(self, name: str, record: Union[str, Record], caller: Optional[str] = None) -> None:
"""Updates the state of the graph."""
if caller:
# If there is a caller which is a vertex_id, I want to activate
@ -108,9 +106,7 @@ class Graph:
def reset_activated_vertices(self):
self.activated_vertices = []
def append_state(
self, name: str, record: Union[str, Record], caller: Optional[str] = None
) -> None:
def append_state(self, name: str, record: Union[str, Record], caller: Optional[str] = None) -> None:
"""Appends the state of the graph."""
if caller:
self.activate_state_vertices(name, caller)
@ -155,10 +151,7 @@ class Graph:
"""Runs the graph with the given inputs."""
for vertex_id in self._is_input_vertices:
vertex = self.get_vertex(vertex_id)
if input_components and (
vertex_id not in input_components
or vertex.display_name not in input_components
):
if input_components and (vertex_id not in input_components or vertex.display_name not in input_components):
continue
if vertex is None:
raise ValueError(f"Vertex {vertex_id} not found")
@ -181,11 +174,7 @@ class Graph:
if vertex is None:
raise ValueError(f"Vertex {vertex_id} not found")
if (
not vertex.result
and not stream
and hasattr(vertex, "consume_async_generator")
):
if not vertex.result and not stream and hasattr(vertex, "consume_async_generator"):
await vertex.consume_async_generator()
if not outputs or (vertex.display_name in outputs or vertex.id in outputs):
vertex_outputs.append(vertex.result)
@ -220,9 +209,7 @@ class Graph:
if isinstance(_input_value, str):
input_value = _input_value
else:
raise ValueError(
f"Invalid input value: {input_value}. Expected string"
)
raise ValueError(f"Invalid input value: {input_value}. Expected string")
run_outputs = await self._run(
inputs={INPUT_FIELD_NAME: input_value},
@ -278,9 +265,7 @@ class Graph:
def build_parent_child_map(self):
parent_child_map = defaultdict(list)
for vertex in self.vertices:
parent_child_map[vertex.id] = [
child.id for child in self.get_successors(vertex)
]
parent_child_map[vertex.id] = [child.id for child in self.get_successors(vertex)]
return parent_child_map
def increment_run_count(self):
@ -465,11 +450,7 @@ class Graph:
"""Updates the edges of a vertex."""
# Vertex has edges, so we need to update the edges
for edge in vertex.edges:
if (
edge not in self.edges
and edge.source_id in self.vertex_map
and edge.target_id in self.vertex_map
):
if edge not in self.edges and edge.source_id in self.vertex_map and edge.target_id in self.vertex_map:
self.edges.append(edge)
def _build_graph(self) -> None:
@ -494,11 +475,7 @@ class Graph:
return
self.vertices.remove(vertex)
self.vertex_map.pop(vertex_id)
self.edges = [
edge
for edge in self.edges
if edge.source_id != vertex_id and edge.target_id != vertex_id
]
self.edges = [edge for edge in self.edges if edge.source_id != vertex_id and edge.target_id != vertex_id]
def _build_vertex_params(self) -> None:
"""Identifies and handles the LLM vertex within the graph."""
@ -519,9 +496,7 @@ class Graph:
return
for vertex in self.vertices:
if not self._validate_vertex(vertex):
raise ValueError(
f"{vertex.display_name} is not connected to any other components"
)
raise ValueError(f"{vertex.display_name} is not connected to any other components")
def _validate_vertex(self, vertex: Vertex) -> bool:
"""Validates a vertex."""
@ -575,9 +550,7 @@ class Graph:
name=f"{vertex.display_name} Run {vertex_task_run_count.get(vertex_id, 0)}",
)
tasks.append(task)
vertex_task_run_count[vertex_id] = (
vertex_task_run_count.get(vertex_id, 0) + 1
)
vertex_task_run_count[vertex_id] = vertex_task_run_count.get(vertex_id, 0) + 1
logger.debug(f"Running layer {layer_index} with {len(tasks)} tasks")
await self._execute_tasks(tasks)
logger.debug("Graph processing complete")
@ -619,9 +592,7 @@ class Graph:
def dfs(vertex):
if state[vertex] == 1:
# We have a cycle
raise ValueError(
"Graph contains a cycle, cannot perform topological sort"
)
raise ValueError("Graph contains a cycle, cannot perform topological sort")
if state[vertex] == 0:
state[vertex] = 1
for edge in vertex.edges:
@ -645,10 +616,7 @@ class Graph:
def get_predecessors(self, vertex):
"""Returns the predecessors of a vertex."""
return [
self.get_vertex(source_id)
for source_id in self.predecessor_map.get(vertex.id, [])
]
return [self.get_vertex(source_id) for source_id in self.predecessor_map.get(vertex.id, [])]
def get_all_successors(self, vertex, recursive=True, flat=True):
# Recursively get the successors of the current vertex
@ -689,10 +657,7 @@ class Graph:
def get_successors(self, vertex):
"""Returns the successors of a vertex."""
return [
self.get_vertex(target_id)
for target_id in self.successor_map.get(vertex.id, [])
]
return [self.get_vertex(target_id) for target_id in self.successor_map.get(vertex.id, [])]
def get_vertex_neighbors(self, vertex: Vertex) -> Dict[Vertex, int]:
"""Returns the neighbors of a vertex."""
@ -738,9 +703,7 @@ class Graph:
edges_added.add((source.id, target.id))
return edges
def _get_vertex_class(
self, node_type: str, node_base_type: str, node_id: str
) -> Type[Vertex]:
def _get_vertex_class(self, node_type: str, node_base_type: str, node_id: str) -> Type[Vertex]:
"""Returns the node class based on the node type."""
# First we check for the node_base_type
node_name = node_id.split("-")[0]
@ -773,18 +736,14 @@ class Graph:
vertex_type: str = vertex_data["type"] # type: ignore
vertex_base_type: str = vertex_data["node"]["template"]["_type"] # type: ignore
VertexClass = self._get_vertex_class(
vertex_type, vertex_base_type, vertex_data["id"]
)
VertexClass = self._get_vertex_class(vertex_type, vertex_base_type, vertex_data["id"])
vertex_instance = VertexClass(vertex, graph=self)
vertex_instance.set_top_level(self.top_level_vertices)
vertices.append(vertex_instance)
return vertices
def get_children_by_vertex_type(
self, vertex: Vertex, vertex_type: str
) -> List[Vertex]:
def get_children_by_vertex_type(self, vertex: Vertex, vertex_type: str) -> List[Vertex]:
"""Returns the children of a vertex based on the vertex type."""
children = []
vertex_types = [vertex.data["type"]]
@ -796,9 +755,7 @@ class Graph:
def __repr__(self):
vertex_ids = [vertex.id for vertex in self.vertices]
edges_repr = "\n".join(
[f"{edge.source_id} --> {edge.target_id}" for edge in self.edges]
)
edges_repr = "\n".join([f"{edge.source_id} --> {edge.target_id}" for edge in self.edges])
return f"Graph:\nNodes: {vertex_ids}\nConnections:\n{edges_repr}"
def sort_up_to_vertex(self, vertex_id: str, is_start: bool = False) -> List[Vertex]:
@ -866,8 +823,7 @@ class Graph:
vertex.id
for vertex in vertices
# if filter_graphs then only vertex.is_input will be considered
if self.in_degree_map[vertex.id] == 0
and (not filter_graphs or vertex.is_input)
if self.in_degree_map[vertex.id] == 0 and (not filter_graphs or vertex.is_input)
)
layers: List[List[str]] = []
visited = set(queue)
@ -941,9 +897,7 @@ class Graph:
return refined_layers
def sort_chat_inputs_first(
self, vertices_layers: List[List[str]]
) -> List[List[str]]:
def sort_chat_inputs_first(self, vertices_layers: List[List[str]]) -> List[List[str]]:
chat_inputs_first = []
for layer in vertices_layers:
for vertex_id in layer:
@ -984,9 +938,7 @@ class Graph:
first_layer = vertices_layers[0]
# save the only the rest
self.vertices_layers = vertices_layers[1:]
self.vertices_to_run = {
vertex_id for vertex_id in chain.from_iterable(vertices_layers)
}
self.vertices_to_run = {vertex_id for vertex_id in chain.from_iterable(vertices_layers)}
# Return just the first layer
return first_layer
@ -997,15 +949,11 @@ class Graph:
self.vertices_to_run.remove(vertex_id)
return should_run
def sort_interface_components_first(
self, vertices_layers: List[List[str]]
) -> List[List[str]]:
def sort_interface_components_first(self, vertices_layers: List[List[str]]) -> List[List[str]]:
"""Sorts the vertices in the graph so that vertices containing ChatInput or ChatOutput come first."""
def contains_interface_component(vertex):
return any(
component.value in vertex for component in InterfaceComponentTypes
)
return any(component.value in vertex for component in InterfaceComponentTypes)
# Sort each inner list so that vertices containing ChatInput or ChatOutput come first
sorted_vertices = [
@ -1017,22 +965,16 @@ class Graph:
]
return sorted_vertices
def sort_by_avg_build_time(
self, vertices_layers: List[List[str]]
) -> List[List[str]]:
def sort_by_avg_build_time(self, vertices_layers: List[List[str]]) -> List[List[str]]:
"""Sorts the vertices in the graph so that vertices with the lowest average build time come first."""
def sort_layer_by_avg_build_time(vertices_ids: List[str]) -> List[str]:
"""Sorts the vertices in the graph so that vertices with the lowest average build time come first."""
if len(vertices_ids) == 1:
return vertices_ids
vertices_ids.sort(
key=lambda vertex_id: self.get_vertex(vertex_id).avg_build_time
)
vertices_ids.sort(key=lambda vertex_id: self.get_vertex(vertex_id).avg_build_time)
return vertices_ids
sorted_vertices = [
sort_layer_by_avg_build_time(layer) for layer in vertices_layers
]
sorted_vertices = [sort_layer_by_avg_build_time(layer) for layer in vertices_layers]
return sorted_vertices

View file

@ -46,10 +46,7 @@ class VertexTypesDict(LazyLoadDictBase):
**{t: types.DocumentLoaderVertex for t in documentloader_creator.to_list()},
**{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()},
**{t: types.OutputParserVertex for t in output_parser_creator.to_list()},
**{
t: types.CustomComponentVertex
for t in custom_component_creator.to_list()
},
**{t: types.CustomComponentVertex for t in custom_component_creator.to_list()},
**{t: types.RetrieverVertex for t in retriever_creator.to_list()},
**{t: types.ChatVertex for t in CHAT_COMPONENTS},
**{t: types.RoutingVertex for t in ROUTING_COMPONENTS},

View file

@ -60,13 +60,8 @@ class Vertex:
self.updated_raw_params = False
self.id: str = data["id"]
self.is_state = False
self.is_input = any(
input_component_name in self.id for input_component_name in INPUT_COMPONENTS
)
self.is_output = any(
output_component_name in self.id
for output_component_name in OUTPUT_COMPONENTS
)
self.is_input = any(input_component_name in self.id for input_component_name in INPUT_COMPONENTS)
self.is_output = any(output_component_name in self.id for output_component_name in OUTPUT_COMPONENTS)
self.has_session_id = None
self._custom_component = None
self.has_external_input = False
@ -106,17 +101,11 @@ class Vertex:
def set_state(self, state: str):
self.state = VertexStates[state]
if (
self.state == VertexStates.INACTIVE
and self.graph.in_degree_map[self.id] < 2
):
if self.state == VertexStates.INACTIVE and self.graph.in_degree_map[self.id] < 2:
# If the vertex is inactive and has only one in degree
# it means that it is not a merge point in the graph
self.graph.inactivated_vertices.add(self.id)
elif (
self.state == VertexStates.ACTIVE
and self.id in self.graph.inactivated_vertices
):
elif self.state == VertexStates.ACTIVE and self.id in self.graph.inactivated_vertices:
self.graph.inactivated_vertices.remove(self.id)
@property
@ -133,9 +122,7 @@ class Vertex:
# If the Vertex.type is a power component
# then we need to return the built object
# instead of the result dict
if self.is_interface_component and not isinstance(
self._built_object, UnbuiltObject
):
if self.is_interface_component and not isinstance(self._built_object, UnbuiltObject):
result = self._built_object
# if it is not a dict or a string and hasattr model_dump then
# return the model_dump
@ -147,11 +134,7 @@ class Vertex:
if isinstance(self._built_result, UnbuiltResult):
return {}
return (
self._built_result
if isinstance(self._built_result, dict)
else {"result": self._built_result}
)
return self._built_result if isinstance(self._built_result, dict) else {"result": self._built_result}
def set_artifacts(self) -> None:
pass
@ -221,31 +204,19 @@ class Vertex:
self.selected_output_type = self.data["node"].get("selected_output_type")
self.is_input = self.data["node"].get("is_input") or self.is_input
self.is_output = self.data["node"].get("is_output") or self.is_output
template_dicts = {
key: value
for key, value in self.data["node"]["template"].items()
if isinstance(value, dict)
}
template_dicts = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
self.has_session_id = "session_id" in template_dicts
self.required_inputs = [
template_dicts[key]["type"]
for key, value in template_dicts.items()
if value["required"]
template_dicts[key]["type"] for key, value in template_dicts.items() if value["required"]
]
self.optional_inputs = [
template_dicts[key]["type"]
for key, value in template_dicts.items()
if not value["required"]
template_dicts[key]["type"] for key, value in template_dicts.items() if not value["required"]
]
# Add the template_dicts[key]["input_types"] to the optional_inputs
self.optional_inputs.extend(
[
input_type
for value in template_dicts.values()
for input_type in value.get("input_types", [])
]
[input_type for value in template_dicts.values() for input_type in value.get("input_types", [])]
)
template_dict = self.data["node"]["template"]
@ -292,11 +263,7 @@ class Vertex:
self.updated_raw_params = False
return
template_dict = {
key: value
for key, value in self.data["node"]["template"].items()
if isinstance(value, dict)
}
template_dict = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
params = {}
for edge in self.edges:
@ -317,10 +284,7 @@ class Vertex:
# we don't know the key of the dict but we need to set the value
# to the vertex that is the source of the edge
param_dict = template_dict[param_key]["value"]
params[param_key] = {
key: self.graph.get_vertex(edge.source_id)
for key in param_dict.keys()
}
params[param_key] = {key: self.graph.get_vertex(edge.source_id) for key in param_dict.keys()}
else:
params[param_key] = self.graph.get_vertex(edge.source_id)
@ -356,11 +320,7 @@ class Vertex:
# list of dicts, so we need to convert it to a dict
# before passing it to the build method
if isinstance(val, list):
params[key] = {
k: v
for item in value.get("value", [])
for k, v in item.items()
}
params[key] = {k: v for item in value.get("value", []) for k, v in item.items()}
elif isinstance(val, dict):
params[key] = val
elif value.get("type") == "int" and val is not None:
@ -485,9 +445,7 @@ class Vertex:
if isinstance(self._built_object, str):
self._built_result = self._built_object
result = await generate_result(
self._built_object, inputs, self.has_external_output, session_id
)
result = await generate_result(self._built_object, inputs, self.has_external_output, session_id)
self._built_result = result
async def _build_each_node_in_params_dict(self, user_id=None):
@ -507,9 +465,7 @@ class Vertex:
elif key not in self.params or self.updated_raw_params:
self.params[key] = value
async def _build_dict_and_update_params(
self, key, nodes_dict: Dict[str, "Vertex"], user_id=None
):
async def _build_dict_and_update_params(self, key, nodes_dict: Dict[str, "Vertex"], user_id=None):
"""
Iterates over a dictionary of nodes, builds each and updates the params dictionary.
"""
@ -532,9 +488,7 @@ class Vertex:
"""
return all(self._is_node(node) for node in value)
async def get_result(
self, requester: Optional["Vertex"] = None, user_id=None, timeout=None
) -> Any:
async def get_result(self, requester: Optional["Vertex"] = None, user_id=None, timeout=None) -> Any:
# PLEASE REVIEW THIS IF STATEMENT
# Check if the Vertex was built already
if self._built:
@ -568,9 +522,7 @@ class Vertex:
self._extend_params_list_with_result(key, result)
self.params[key] = result
async def _build_list_of_nodes_and_update_params(
self, key, nodes: List["Vertex"], user_id=None
):
async def _build_list_of_nodes_and_update_params(self, key, nodes: List["Vertex"], user_id=None):
"""
Iterates over a list of nodes, builds each and updates the params dictionary.
"""
@ -637,9 +589,7 @@ class Vertex:
except Exception as exc:
logger.exception(exc)
raise ValueError(
f"Error building node {self.display_name}: {str(exc)}"
) from exc
raise ValueError(f"Error building node {self.display_name}: {str(exc)}") from exc
def _update_built_object_and_artifacts(self, result):
"""
@ -667,9 +617,7 @@ class Vertex:
logger.warning(message)
elif isinstance(self._built_object, (Iterator, AsyncIterator)):
if self.display_name in ["Text Output"]:
raise ValueError(
f"You are trying to stream to a {self.display_name}. Try using a Chat Output instead."
)
raise ValueError(f"You are trying to stream to a {self.display_name}. Try using a Chat Output instead.")
def _reset(self, params_update: Optional[Dict[str, Any]] = None):
self._built = False
@ -732,24 +680,16 @@ class Vertex:
return self._built_object
# Get the requester edge
requester_edge = next(
(edge for edge in self.edges if edge.target_id == requester.id), None
)
requester_edge = next((edge for edge in self.edges if edge.target_id == requester.id), None)
# Return the result of the requester edge
return (
None
if requester_edge is None
else await requester_edge.get_result(source=self, target=requester)
)
return None if requester_edge is None else await requester_edge.get_result(source=self, target=requester)
def add_edge(self, edge: "ContractEdge") -> None:
if edge not in self.edges:
self.edges.append(edge)
def __repr__(self) -> str:
return (
f"Vertex(display_name={self.display_name}, id={self.id}, data={self.data})"
)
return f"Vertex(display_name={self.display_name}, id={self.id}, data={self.data})"
def __eq__(self, __o: object) -> bool:
try:
@ -770,8 +710,4 @@ class Vertex:
def _built_object_repr(self):
# Add a message with an emoji, stars for sucess,
return (
"Built sucessfully ✨"
if self._built_object is not None
else "Failed to build 😵‍💫"
)
return "Built sucessfully ✨" if self._built_object is not None else "Failed to build 😵‍💫"

View file

@ -123,11 +123,9 @@ class DocumentLoaderVertex(Vertex):
# show how many documents are in the list?
if not isinstance(self._built_object, UnbuiltObject):
avg_length = sum(
len(record.text)
for record in self._built_object
if hasattr(record, "text")
) / len(self._built_object)
avg_length = sum(len(record.text) for record in self._built_object if hasattr(record, "text")) / len(
self._built_object
)
return f"""{self.display_name}({len(self._built_object)} records)
\nAvg. Record Length (characters): {int(avg_length)}
Records: {self._built_object[:3]}..."""
@ -200,9 +198,7 @@ class TextSplitterVertex(Vertex):
# show how many documents are in the list?
if not isinstance(self._built_object, UnbuiltObject):
avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(
self._built_object
)
avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(self._built_object)
return f"""{self.vertex_type}({len(self._built_object)} documents)
\nAvg. Document Length (characters): {int(avg_length)}
\nDocuments: {self._built_object[:3]}..."""
@ -249,27 +245,18 @@ class PromptVertex(Vertex):
user_id = kwargs.get("user_id", None)
tools = kwargs.get("tools", [])
if not self._built or force:
if (
"input_variables" not in self.params
or self.params["input_variables"] is None
):
if "input_variables" not in self.params or self.params["input_variables"] is None:
self.params["input_variables"] = []
# Check if it is a ZeroShotPrompt and needs a tool
if "ShotPrompt" in self.vertex_type:
tools = (
[tool_node.build(user_id=user_id) for tool_node in tools]
if tools is not None
else []
)
tools = [tool_node.build(user_id=user_id) for tool_node in tools] if tools is not None else []
# flatten the list of tools if it is a list of lists
# first check if it is a list
if tools and isinstance(tools, list) and isinstance(tools[0], list):
tools = flatten_list(tools)
self.params["tools"] = tools
prompt_params = [
key
for key, value in self.params.items()
if isinstance(value, str) and key != "format_instructions"
key for key, value in self.params.items() if isinstance(value, str) and key != "format_instructions"
]
else:
prompt_params = ["template"]
@ -279,20 +266,14 @@ class PromptVertex(Vertex):
prompt_text = self.params[param]
variables = extract_input_variables_from_prompt(prompt_text)
self.params["input_variables"].extend(variables)
self.params["input_variables"] = list(
set(self.params["input_variables"])
)
self.params["input_variables"] = list(set(self.params["input_variables"]))
elif isinstance(self.params, dict):
self.params.pop("input_variables", None)
await self._build(user_id=user_id)
def _built_object_repr(self):
if (
not self.artifacts
or self._built_object is None
or not hasattr(self._built_object, "format")
):
if not self.artifacts or self._built_object is None or not hasattr(self._built_object, "format"):
return super()._built_object_repr()
elif isinstance(self._built_object, UnbuiltObject):
return super()._built_object_repr()
@ -304,9 +285,7 @@ class PromptVertex(Vertex):
# so the prompt format doesn't break
artifacts.pop("handle_keys", None)
try:
if not hasattr(self._built_object, "template") and hasattr(
self._built_object, "prompt"
):
if not hasattr(self._built_object, "template") and hasattr(self._built_object, "prompt"):
template = self._built_object.prompt.template
else:
template = self._built_object.template
@ -314,11 +293,7 @@ class PromptVertex(Vertex):
if value:
replace_key = "{" + key + "}"
template = template.replace(replace_key, value)
return (
template
if isinstance(template, str)
else f"{self.vertex_type}({template})"
)
return template if isinstance(template, str) else f"{self.vertex_type}({template})"
except KeyError:
return str(self._built_object)

View file

@ -18,10 +18,7 @@ STARTER_FOLDER_NAME = "Starter Projects"
# can use them as a starting point for their own projects.
def update_projects_components_with_latest_component_versions(
project_data, all_types_dict
):
def update_projects_components_with_latest_component_versions(project_data, all_types_dict):
# project data has a nodes key, which is a list of nodes
# we want to run through each node and see if it exists in the all_types_dict
# if so, we go into the template key and also get the template from all_types_dict
@ -148,13 +145,9 @@ def create_or_update_starter_projects():
project_icon,
project_icon_bg_color,
) = get_project_data(project)
project_data = update_projects_components_with_latest_component_versions(
project_data, all_types_dict
)
project_data = update_projects_components_with_latest_component_versions(project_data, all_types_dict)
if project_name and project_data:
for existing_project in get_all_flows_similar_to_project(
session, project_name
):
for existing_project in get_all_flows_similar_to_project(session, project_name):
session.delete(existing_project)
create_new_project(

View file

@ -99,9 +99,7 @@ class CodeParser:
elif isinstance(node, ast.ImportFrom):
for alias in node.names:
if alias.asname:
self.data["imports"].append(
(node.module, f"{alias.name} as {alias.asname}")
)
self.data["imports"].append((node.module, f"{alias.name} as {alias.asname}"))
else:
self.data["imports"].append((node.module, alias.name))
@ -150,9 +148,7 @@ class CodeParser:
return_type = None
if node.returns:
return_type_str = ast.unparse(node.returns)
eval_env = self.construct_eval_env(
return_type_str, tuple(self.data["imports"])
)
eval_env = self.construct_eval_env(return_type_str, tuple(self.data["imports"]))
try:
return_type = eval(return_type_str, eval_env)
@ -194,22 +190,14 @@ class CodeParser:
num_defaults = len(node.args.defaults)
num_missing_defaults = num_args - num_defaults
missing_defaults = [MissingDefault()] * num_missing_defaults
default_values = [
ast.unparse(default).strip("'") if default else None
for default in node.args.defaults
]
default_values = [ast.unparse(default).strip("'") if default else None for default in node.args.defaults]
# Now check all default values to see if there
# are any "None" values in the middle
default_values = [
None if value == "None" else value for value in default_values
]
default_values = [None if value == "None" else value for value in default_values]
defaults = missing_defaults + default_values
args = [
self.parse_arg(arg, default)
for arg, default in zip(node.args.args, defaults)
]
args = [self.parse_arg(arg, default) for arg, default in zip(node.args.args, defaults)]
return args
def parse_varargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
@ -227,17 +215,11 @@ class CodeParser:
"""
Parses the keyword-only arguments of a function or method node.
"""
kw_defaults = [None] * (
len(node.args.kwonlyargs) - len(node.args.kw_defaults)
) + [
ast.unparse(default) if default else None
for default in node.args.kw_defaults
kw_defaults = [None] * (len(node.args.kwonlyargs) - len(node.args.kw_defaults)) + [
ast.unparse(default) if default else None for default in node.args.kw_defaults
]
args = [
self.parse_arg(arg, default)
for arg, default in zip(node.args.kwonlyargs, kw_defaults)
]
args = [self.parse_arg(arg, default) for arg, default in zip(node.args.kwonlyargs, kw_defaults)]
return args
def parse_kwargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
@ -341,9 +323,7 @@ class CodeParser:
Extracts global variables from the code.
"""
global_var = {
"targets": [
t.id if hasattr(t, "id") else ast.dump(t) for t in node.targets
],
"targets": [t.id if hasattr(t, "id") else ast.dump(t) for t in node.targets],
"value": ast.unparse(node.value),
}
self.data["global_vars"].append(global_var)

View file

@ -80,9 +80,7 @@ class CustomComponent(Component):
if not self.vertex:
raise ValueError("Vertex is not set")
try:
self.vertex.graph.update_state(
name=name, record=value, caller=self.vertex.id
)
self.vertex.graph.update_state(name=name, record=value, caller=self.vertex.id)
except Exception as e:
raise ValueError(f"Error updating state: {e}")
@ -90,9 +88,7 @@ class CustomComponent(Component):
if not self.vertex:
raise ValueError("Vertex is not set")
try:
self.vertex.graph.append_state(
name=name, record=value, caller=self.vertex.id
)
self.vertex.graph.append_state(name=name, record=value, caller=self.vertex.id)
except Exception as e:
raise ValueError(f"Error appending state: {e}")
@ -158,9 +154,7 @@ class CustomComponent(Component):
def tree(self):
return self.get_code_tree(self.code or "")
def to_records(
self, data: Any, keys: Optional[List[str]] = None, silent_errors: bool = False
) -> List[Record]:
def to_records(self, data: Any, keys: Optional[List[str]] = None, silent_errors: bool = False) -> List[Record]:
"""
Converts input data into a list of Record objects.
@ -211,9 +205,7 @@ class CustomComponent(Component):
return records
def create_references_from_records(
self, records: List[Record], include_data: bool = False
) -> str:
def create_references_from_records(self, records: List[Record], include_data: bool = False) -> str:
"""
Create references from a list of records.
@ -252,20 +244,14 @@ class CustomComponent(Component):
if not self.code:
return {}
component_classes = [
cls
for cls in self.tree["classes"]
if self.code_class_base_inheritance in cls["bases"]
]
component_classes = [cls for cls in self.tree["classes"] if self.code_class_base_inheritance in cls["bases"]]
if not component_classes:
return {}
# Assume the first Component class is the one we're interested in
component_class = component_classes[0]
build_methods = [
method
for method in component_class["methods"]
if method["name"] == self.function_entrypoint_name
method for method in component_class["methods"] if method["name"] == self.function_entrypoint_name
]
return build_methods[0] if build_methods else {}
@ -322,9 +308,7 @@ class CustomComponent(Component):
# Retrieve and decrypt the credential by name for the current user
db_service = get_db_service()
with session_getter(db_service) as session:
return credential_service.get_credential(
user_id=self._user_id or "", name=name, session=session
)
return credential_service.get_credential(user_id=self._user_id or "", name=name, session=session)
return get_credential
@ -334,9 +318,7 @@ class CustomComponent(Component):
credential_service = get_credential_service()
db_service = get_db_service()
with session_getter(db_service) as session:
return credential_service.list_credentials(
user_id=self._user_id, session=session
)
return credential_service.list_credentials(user_id=self._user_id, session=session)
def index(self, value: int = 0):
"""Returns a function that returns the value at the given index in the iterable."""
@ -375,11 +357,7 @@ class CustomComponent(Component):
if not self._flows_records:
self.list_flows()
if not flow_id and self._flows_records:
flow_ids = [
flow.data["id"]
for flow in self._flows_records
if flow.data["name"] == flow_name
]
flow_ids = [flow.data["id"] for flow in self._flows_records if flow.data["name"] == flow_name]
if not flow_ids:
raise ValueError(f"Flow {flow_name} not found")
elif len(flow_ids) > 1:
@ -401,9 +379,7 @@ class CustomComponent(Component):
db_service = get_db_service()
with get_session(db_service) as session:
flows = session.exec(
select(Flow)
.where(Flow.user_id == self._user_id)
.where(Flow.is_component == False) # noqa
select(Flow).where(Flow.user_id == self._user_id).where(Flow.is_component == False) # noqa
).all()
flows_records = [flow.to_record() for flow in flows]

View file

@ -34,18 +34,14 @@ class UpdateBuildConfigError(Exception):
pass
def add_output_types(
frontend_node: CustomComponentFrontendNode, return_types: List[str]
):
def add_output_types(frontend_node: CustomComponentFrontendNode, return_types: List[str]):
"""Add output types to the frontend node"""
for return_type in return_types:
if return_type is None:
raise HTTPException(
status_code=400,
detail={
"error": (
"Invalid return type. Please check your code and try again."
),
"error": ("Invalid return type. Please check your code and try again."),
"traceback": traceback.format_exc(),
},
)
@ -77,18 +73,14 @@ def reorder_fields(frontend_node: CustomComponentFrontendNode, field_order: List
frontend_node.field_order = field_order
def add_base_classes(
frontend_node: CustomComponentFrontendNode, return_types: List[str]
):
def add_base_classes(frontend_node: CustomComponentFrontendNode, return_types: List[str]):
"""Add base classes to the frontend node"""
for return_type_instance in return_types:
if return_type_instance is None:
raise HTTPException(
status_code=400,
detail={
"error": (
"Invalid return type. Please check your code and try again."
),
"error": ("Invalid return type. Please check your code and try again."),
"traceback": traceback.format_exc(),
},
)
@ -123,9 +115,7 @@ def get_field_properties(extra_field):
# a required field is a field that does not contain
# optional in field_type
# and a field that does not have a default value
field_required = "optional" not in field_type.lower() and isinstance(
field_value, MissingDefault
)
field_required = "optional" not in field_type.lower() and isinstance(field_value, MissingDefault)
field_value = field_value if not isinstance(field_value, MissingDefault) else None
if not field_required:
@ -171,14 +161,10 @@ def add_new_custom_field(
# If options is a list, then it's a dropdown
# If options is None, then it's a list of strings
is_list = isinstance(field_config.get("options"), list)
field_config["is_list"] = (
is_list or field_config.get("list", False) or field_contains_list
)
field_config["is_list"] = is_list or field_config.get("list", False) or field_contains_list
if "name" in field_config:
warnings.warn(
"The 'name' key in field_config is used to build the object and can't be changed."
)
warnings.warn("The 'name' key in field_config is used to build the object and can't be changed.")
required = field_config.pop("required", field_required)
placeholder = field_config.pop("placeholder", "")
@ -217,9 +203,7 @@ def add_extra_fields(frontend_node, field_config, function_args):
]:
continue
field_name, field_type, field_value, field_required = get_field_properties(
extra_field
)
field_name, field_type, field_value, field_required = get_field_properties(extra_field)
config = _field_config.pop(field_name, {})
frontend_node = add_new_custom_field(
frontend_node,
@ -229,17 +213,13 @@ def add_extra_fields(frontend_node, field_config, function_args):
field_required,
config,
)
if "kwargs" in function_args_names and not all(
key in function_args_names for key in field_config.keys()
):
if "kwargs" in function_args_names and not all(key in function_args_names for key in field_config.keys()):
for field_name, field_config in _field_config.copy().items():
if "name" not in field_config or field_name == "code":
continue
config = _field_config.get(field_name, {})
config = config.model_dump() if isinstance(config, BaseModel) else config
field_name, field_type, field_value, field_required = get_field_properties(
extra_field=config
)
field_name, field_type, field_value, field_required = get_field_properties(extra_field=config)
frontend_node = add_new_custom_field(
frontend_node,
field_name,
@ -277,9 +257,7 @@ def run_build_config(
raise HTTPException(
status_code=400,
detail={
"error": (
"Invalid type convertion. Please check your code and try again."
),
"error": ("Invalid type convertion. Please check your code and try again."),
"traceback": traceback.format_exc(),
},
) from exc
@ -329,7 +307,6 @@ def run_build_config(
return build_config, custom_instance
except Exception as exc:
logger.error(f"Error while building field config: {str(exc)}")
if hasattr(exc, "detail") and "traceback" in exc.detail:
logger.error(exc.detail["traceback"])
@ -358,7 +335,6 @@ def build_frontend_node(template_config):
def add_code_field(frontend_node: CustomComponentFrontendNode, raw_code, field_config):
code_field = TemplateField(
dynamic=True,
required=True,
@ -397,16 +373,10 @@ def build_custom_component_template(
add_extra_fields(frontend_node, field_config, entrypoint_args)
frontend_node = add_code_field(
frontend_node, custom_component.code, field_config.get("code", {})
)
frontend_node = add_code_field(frontend_node, custom_component.code, field_config.get("code", {}))
add_base_classes(
frontend_node, custom_component.get_function_entrypoint_return_type
)
add_output_types(
frontend_node, custom_component.get_function_entrypoint_return_type
)
add_base_classes(frontend_node, custom_component.get_function_entrypoint_return_type)
add_output_types(frontend_node, custom_component.get_function_entrypoint_return_type)
reorder_fields(frontend_node, custom_instance._get_field_order())
@ -417,9 +387,7 @@ def build_custom_component_template(
raise HTTPException(
status_code=400,
detail={
"error": (
f"Something went wrong while building the custom component. Hints: {str(exc)}"
),
"error": (f"Something went wrong while building the custom component. Hints: {str(exc)}"),
"traceback": traceback.format_exc(),
},
) from exc
@ -455,9 +423,7 @@ def build_custom_components(components_paths: List[str]):
custom_component_dict = build_custom_component_list_from_path(path_str)
if custom_component_dict:
category = next(iter(custom_component_dict))
logger.info(
f"Loading {len(custom_component_dict[category])} component(s) from category {category}"
)
logger.info(f"Loading {len(custom_component_dict[category])} component(s) from category {category}")
custom_components_from_file = merge_nested_dicts_with_renaming(
custom_components_from_file, custom_component_dict
)
@ -492,9 +458,7 @@ def update_field_dict(
build_config = dd_build_config
except Exception as exc:
logger.error(f"Error while running update_build_config: {str(exc)}")
raise UpdateBuildConfigError(
f"Error while running update_build_config: {str(exc)}"
) from exc
raise UpdateBuildConfigError(f"Error while running update_build_config: {str(exc)}") from exc
# Let's check if "range_spec" is a RangeSpec object
if "rangeSpec" in field_dict and isinstance(field_dict["rangeSpec"], RangeSpec):

View file

@ -145,13 +145,9 @@ async def instantiate_based_on_type(
return class_object(**params)
async def instantiate_custom_component(
node_type, class_object, params, user_id, vertex
):
async def instantiate_custom_component(node_type, class_object, params, user_id, vertex):
params_copy = params.copy()
class_object: Type["CustomComponent"] = eval_custom_component_code(
params_copy.pop("code")
)
class_object: Type["CustomComponent"] = eval_custom_component_code(params_copy.pop("code"))
custom_component: "CustomComponent" = class_object(
user_id=user_id,
parameters=params_copy,
@ -227,9 +223,7 @@ def instantiate_memory(node_type, class_object, params):
# I want to catch a specific attribute error that happens
# when the object does not have a cursor attribute
except Exception as exc:
if "object has no attribute 'cursor'" in str(
exc
) or 'object has no field "conn"' in str(exc):
if "object has no attribute 'cursor'" in str(exc) or 'object has no field "conn"' in str(exc):
raise AttributeError(
(
"Failed to build connection to database."
@ -272,9 +266,7 @@ def instantiate_agent(node_type, class_object: Type[agent_module.Agent], params:
if class_method := getattr(class_object, method, None):
agent = class_method(**params)
tools = params.get("tools", [])
return AgentExecutor.from_agent_and_tools(
agent=agent, tools=tools, handle_parsing_errors=True
)
return AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, handle_parsing_errors=True)
return load_agent_executor(class_object, params)
@ -330,11 +322,7 @@ def instantiate_embedding(node_type, class_object, params: Dict):
try:
return class_object(**params)
except ValidationError:
params = {
key: value
for key, value in params.items()
if key in class_object.model_fields
}
params = {key: value for key, value in params.items() if key in class_object.model_fields}
return class_object(**params)
@ -346,9 +334,7 @@ def instantiate_vectorstore(class_object: Type[VectorStore], params: Dict):
if "texts" in params:
params["documents"] = params.pop("texts")
if "documents" in params:
params["documents"] = [
doc for doc in params["documents"] if isinstance(doc, Document)
]
params["documents"] = [doc for doc in params["documents"] if isinstance(doc, Document)]
if initializer := vecstore_initializer.get(class_object.__name__):
vecstore = initializer(class_object, params)
else:
@ -363,9 +349,7 @@ def instantiate_vectorstore(class_object: Type[VectorStore], params: Dict):
return vecstore
def instantiate_documentloader(
node_type: str, class_object: Type[BaseLoader], params: Dict
):
def instantiate_documentloader(node_type: str, class_object: Type[BaseLoader], params: Dict):
if "file_filter" in params:
# file_filter will be a string but we need a function
# that will be used to filter the files using file_filter
@ -374,17 +358,13 @@ def instantiate_documentloader(
# in x and if it is, we will return True
file_filter = params.pop("file_filter")
extensions = file_filter.split(",")
params["file_filter"] = lambda x: any(
extension.strip() in x for extension in extensions
)
params["file_filter"] = lambda x: any(extension.strip() in x for extension in extensions)
metadata = params.pop("metadata", None)
if metadata and isinstance(metadata, str):
try:
metadata = orjson.loads(metadata)
except json.JSONDecodeError as exc:
raise ValueError(
"The metadata you provided is not a valid JSON string."
) from exc
raise ValueError("The metadata you provided is not a valid JSON string.") from exc
if node_type == "WebBaseLoader":
if web_path := params.pop("web_path", None):
@ -417,9 +397,7 @@ def instantiate_textsplitter(
"Try changing the chunk_size of the Text Splitter."
) from exc
if (
"separator_type" in params and params["separator_type"] == "Text"
) or "separator_type" not in params:
if ("separator_type" in params and params["separator_type"] == "Text") or "separator_type" not in params:
params.pop("separator_type", None)
# separators might come in as an escaped string like \\n
# so we need to convert it to a string
@ -427,9 +405,7 @@ def instantiate_textsplitter(
if isinstance(params["separators"], str):
params["separators"] = unescape_string(params["separators"])
elif isinstance(params["separators"], list):
params["separators"] = [
unescape_string(separator) for separator in params["separators"]
]
params["separators"] = [unescape_string(separator) for separator in params["separators"]]
text_splitter = class_object(**params)
else:
from langchain.text_splitter import Language
@ -456,8 +432,7 @@ def replace_zero_shot_prompt_with_prompt_template(nodes):
tools = [
tool
for tool in nodes
if tool["type"] != "chatOutputNode"
and "Tool" in tool["data"]["node"]["base_classes"]
if tool["type"] != "chatOutputNode" and "Tool" in tool["data"]["node"]["base_classes"]
]
node["data"] = build_prompt_template(prompt=node["data"], tools=tools)
break
@ -471,9 +446,7 @@ def load_agent_executor(agent_class: type[agent_module.Agent], params, **kwargs)
# agent has hidden args for memory. might need to be support
# memory = params["memory"]
# if allowed_tools is not a list or set, make it a list
if not isinstance(allowed_tools, (list, set)) and isinstance(
allowed_tools, BaseTool
):
if not isinstance(allowed_tools, (list, set)) and isinstance(allowed_tools, BaseTool):
allowed_tools = [allowed_tools]
tool_names = [tool.name for tool in allowed_tools]
# Agent class requires an output_parser but Agent classes
@ -501,10 +474,7 @@ def build_prompt_template(prompt, tools):
format_instructions = prompt["node"]["template"]["format_instructions"]["value"]
tool_strings = "\n".join(
[
f"{tool['data']['node']['name']}: {tool['data']['node']['description']}"
for tool in tools
]
[f"{tool['data']['node']['name']}: {tool['data']['node']['description']}" for tool in tools]
)
tool_names = ", ".join([tool["data"]["node"]["name"] for tool in tools])
format_instructions = format_instructions.format(tool_names=tool_names)

View file

@ -66,12 +66,8 @@ def build_langchain_types_dict(): # sourcery skip: dict-assign-update-to-union
def get_all_types_dict(components_paths):
"""Get all types dictionary combining native and custom components."""
native_components = build_langchain_types_dict()
custom_components_from_file = build_custom_components(
components_paths=components_paths
)
return merge_nested_dicts_with_renaming(
native_components, custom_components_from_file
)
custom_components_from_file = build_custom_components(components_paths=components_paths)
return merge_nested_dicts_with_renaming(native_components, custom_components_from_file)
def get_all_components(components_paths, as_dict=False):

View file

@ -6,9 +6,7 @@ from langflow.graph import Graph
from langflow.processing.process import process_tweaks
def load_flow_from_json(
flow: Union[Path, str, dict], tweaks: Optional[dict] = None
) -> Graph:
def load_flow_from_json(flow: Union[Path, str, dict], tweaks: Optional[dict] = None) -> Graph:
"""
Load flow from a JSON file or a JSON object.
@ -25,9 +23,7 @@ def load_flow_from_json(
elif isinstance(flow, dict):
flow_graph = flow
else:
raise TypeError(
"Input must be either a file path (str) or a JSON object (dict)"
)
raise TypeError("Input must be either a file path (str) or a JSON object (dict)")
graph_data = flow_graph["data"]
if tweaks is not None:

View file

@ -129,9 +129,7 @@ async def process_runnable(runnable: Runnable, inputs: Union[dict, List[dict]]):
elif isinstance(inputs, dict) and hasattr(runnable, "ainvoke"):
result = await runnable.ainvoke(inputs)
else:
raise ValueError(
f"Runnable {runnable} does not support inputs of type {type(inputs)}"
)
raise ValueError(f"Runnable {runnable} does not support inputs of type {type(inputs)}")
# Check if the result is a list of AIMessages
if isinstance(result, list) and all(isinstance(r, AIMessage) for r in result):
result = [r.content for r in result]
@ -140,9 +138,7 @@ async def process_runnable(runnable: Runnable, inputs: Union[dict, List[dict]]):
return result
async def process_inputs_dict(
built_object: Union[Chain, VectorStore, Runnable], inputs: dict
):
async def process_inputs_dict(built_object: Union[Chain, VectorStore, Runnable], inputs: dict):
if isinstance(built_object, Chain):
if inputs is None:
raise ValueError("Inputs must be provided for a Chain")
@ -177,9 +173,7 @@ async def process_inputs_list(built_object: Runnable, inputs: List[dict]):
return await process_runnable(built_object, inputs)
async def generate_result(
built_object: Union[Chain, VectorStore, Runnable], inputs: Union[dict, List[dict]]
):
async def generate_result(built_object: Union[Chain, VectorStore, Runnable], inputs: Union[dict, List[dict]]):
if isinstance(inputs, dict):
result = await process_inputs_dict(built_object, inputs)
elif isinstance(inputs, List) and isinstance(built_object, Runnable):
@ -218,9 +212,7 @@ async def run_graph(
else:
graph_data = graph._graph_data
if session_id is None and session_service is not None:
session_id = session_service.generate_key(
session_id=flow_id, data_graph=graph_data
)
session_id = session_service.generate_key(session_id=flow_id, data_graph=graph_data)
if inputs is None:
inputs = [{}]
@ -244,9 +236,7 @@ def validate_input(
nodes = graph_data.get("data", {}).get("nodes") or graph_data.get("nodes")
if not isinstance(nodes, list):
raise ValueError(
"graph_data should contain a list of nodes under 'data' key or directly under 'nodes' key"
)
raise ValueError("graph_data should contain a list of nodes under 'data' key or directly under 'nodes' key")
return nodes
@ -255,9 +245,7 @@ def apply_tweaks(node: Dict[str, Any], node_tweaks: Dict[str, Any]) -> None:
template_data = node.get("data", {}).get("node", {}).get("template")
if not isinstance(template_data, dict):
logger.warning(
f"Template data for node {node.get('id')} should be a dictionary"
)
logger.warning(f"Template data for node {node.get('id')} should be a dictionary")
return
for tweak_name, tweak_value in node_tweaks.items():
@ -272,9 +260,7 @@ def apply_tweaks_on_vertex(vertex: Vertex, node_tweaks: Dict[str, Any]) -> None:
vertex.params[tweak_name] = tweak_value
def process_tweaks(
graph_data: Dict[str, Any], tweaks: Union["Tweaks", Dict[str, Dict[str, Any]]]
) -> Dict[str, Any]:
def process_tweaks(graph_data: Dict[str, Any], tweaks: Union["Tweaks", Dict[str, Dict[str, Any]]]) -> Dict[str, Any]:
"""
This function is used to tweak the graph data using the node id and the tweaks dict.
@ -310,8 +296,6 @@ def process_tweaks_on_graph(graph: Graph, tweaks: Dict[str, Dict[str, Any]]):
if node_tweaks := tweaks.get(node_id):
apply_tweaks_on_vertex(vertex, node_tweaks)
else:
logger.warning(
"Each node should be a Vertex with an 'id' attribute of type str"
)
logger.warning("Each node should be a Vertex with an 'id' attribute of type str")
return graph

View file

@ -63,9 +63,7 @@ class Record(BaseModel):
return self.data.get(key, self._default_value)
except KeyError:
# Fallback to default behavior to raise AttributeError for undefined attributes
raise AttributeError(
f"'{type(self).__name__}' object has no attribute '{key}'"
)
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{key}'")
def __setattr__(self, key, value):
"""

View file

@ -22,9 +22,7 @@ class FlowBase(SQLModel):
icon_bg_color: Optional[str] = Field(default=None, nullable=True)
data: Optional[Dict] = Field(default=None, nullable=True)
is_component: Optional[bool] = Field(default=False, nullable=True)
updated_at: Optional[datetime] = Field(
default_factory=datetime.utcnow, nullable=True
)
updated_at: Optional[datetime] = Field(default_factory=datetime.utcnow, nullable=True)
folder: Optional[str] = Field(default=None, nullable=True)
@field_validator("icon_bg_color")

View file

@ -10,9 +10,7 @@ if TYPE_CHECKING:
class TransactionModel(BaseModel):
id: Optional[int] = Field(default=None, alias="id")
timestamp: Optional[datetime] = Field(
default_factory=datetime.now, alias="timestamp"
)
timestamp: Optional[datetime] = Field(default_factory=datetime.now, alias="timestamp")
source: str
target: str
target_args: dict
@ -53,12 +51,8 @@ class MessageModel(BaseModel):
@classmethod
def from_record(cls, record: "Record"):
# first check if the record has all the required fields
if not record.data or (
"sender" not in record.data and "sender_name" not in record.data
):
raise ValueError(
"The record does not have the required fields 'sender' and 'sender_name' in the data."
)
if not record.data or ("sender" not in record.data and "sender_name" not in record.data):
raise ValueError("The record does not have the required fields 'sender' and 'sender_name' in the data.")
return cls(
sender=record.sender,
sender_name=record.sender_name,

View file

@ -44,9 +44,7 @@ class MonitorService(Service):
def ensure_tables_exist(self):
for table_name, model in self.table_map.items():
drop_and_create_table_if_schema_mismatch(
str(self.db_path), table_name, model
)
drop_and_create_table_if_schema_mismatch(str(self.db_path), table_name, model)
def add_row(
self,

View file

@ -74,9 +74,7 @@ class TaskService(Service):
result = await result
return task.id, result
async def launch_task(
self, task_func: Callable[..., Any], *args: Any, **kwargs: Any
) -> Any:
async def launch_task(self, task_func: Callable[..., Any], *args: Any, **kwargs: Any) -> Any:
logger.debug(f"Launching task {task_func} with args {args} and kwargs {kwargs}")
logger.debug(f"Using backend {self.backend}")
task = self.backend.launch_task(task_func, *args, **kwargs)

View file

@ -73,9 +73,7 @@ class TemplateField(BaseModel):
refresh_button_text: Optional[str] = None
"""Specifies the text for the refresh button. Defaults to None."""
range_spec: Optional[RangeSpec] = Field(
default=None, serialization_alias="rangeSpec"
)
range_spec: Optional[RangeSpec] = Field(default=None, serialization_alias="rangeSpec")
"""Range specification for the field. Defaults to None."""
title_case: bool = False
@ -124,10 +122,6 @@ class TemplateField(BaseModel):
if not isinstance(value, list):
raise ValueError("file_types must be a list")
return [
(
f".{file_type}"
if isinstance(file_type, str) and not file_type.startswith(".")
else file_type
)
(f".{file_type}" if isinstance(file_type, str) and not file_type.startswith(".") else file_type)
for file_type in value
]

View file

@ -20,12 +20,8 @@ def remove_ansi_escape_codes(text):
return re.sub(r"\x1b\[[0-9;]*[a-zA-Z]", "", text)
def build_template_from_function(
name: str, type_to_loader_dict: Dict, add_function: bool = False
):
classes = [
item.__annotations__["return"].__name__ for item in type_to_loader_dict.values()
]
def build_template_from_function(name: str, type_to_loader_dict: Dict, add_function: bool = False):
classes = [item.__annotations__["return"].__name__ for item in type_to_loader_dict.values()]
# Raise error if name is not in chains
if name not in classes:
@ -46,10 +42,8 @@ def build_template_from_function(
for name_, value_ in value.__repr_args__():
if name_ == "default_factory":
try:
variables[class_field_items]["default"] = (
get_default_factory(
module=_class.__base__.__module__, function=value_
)
variables[class_field_items]["default"] = get_default_factory(
module=_class.__base__.__module__, function=value_
)
except Exception:
variables[class_field_items]["default"] = None
@ -57,9 +51,7 @@ def build_template_from_function(
variables[class_field_items][name_] = value_
variables[class_field_items]["placeholder"] = (
docs.params[class_field_items]
if class_field_items in docs.params
else ""
docs.params[class_field_items] if class_field_items in docs.params else ""
)
# Adding function to base classes to allow
# the output to be a function
@ -74,9 +66,7 @@ def build_template_from_function(
}
def build_template_from_class(
name: str, type_to_cls_dict: Dict, add_function: bool = False
):
def build_template_from_class(name: str, type_to_cls_dict: Dict, add_function: bool = False):
classes = [item.__name__ for item in type_to_cls_dict.values()]
# Raise error if name is not in chains
@ -100,11 +90,9 @@ def build_template_from_class(
for name_, value_ in value.__repr_args__():
if name_ == "default_factory":
try:
variables[class_field_items]["default"] = (
get_default_factory(
module=_class.__base__.__module__,
function=value_,
)
variables[class_field_items]["default"] = get_default_factory(
module=_class.__base__.__module__,
function=value_,
)
except Exception:
variables[class_field_items]["default"] = None
@ -112,9 +100,7 @@ def build_template_from_class(
variables[class_field_items][name_] = value_
variables[class_field_items]["placeholder"] = (
docs.params[class_field_items]
if class_field_items in docs.params
else ""
docs.params[class_field_items] if class_field_items in docs.params else ""
)
base_classes = get_base_classes(_class)
# Adding function to base classes to allow
@ -146,9 +132,7 @@ def build_template_from_method(
# Check if the method exists in this class
if not hasattr(_class, method_name):
raise ValueError(
f"Method {method_name} not found in class {class_name}"
)
raise ValueError(f"Method {method_name} not found in class {class_name}")
# Get the method
method = getattr(_class, method_name)
@ -167,14 +151,8 @@ def build_template_from_method(
"_type": _type,
**{
name: {
"default": (
param.default if param.default != param.empty else None
),
"type": (
param.annotation
if param.annotation != param.empty
else None
),
"default": (param.default if param.default != param.empty else None),
"type": (param.annotation if param.annotation != param.empty else None),
"required": param.default == param.empty,
}
for name, param in params.items()
@ -261,9 +239,7 @@ def sync_to_async(func):
return async_wrapper
def format_dict(
dictionary: Dict[str, Any], class_name: Optional[str] = None
) -> Dict[str, Any]:
def format_dict(dictionary: Dict[str, Any], class_name: Optional[str] = None) -> Dict[str, Any]:
"""
Formats a dictionary by removing certain keys and modifying the
values of other keys.
@ -349,9 +325,7 @@ def check_list_type(_type: str, value: Dict[str, Any]) -> str:
The modified type string.
"""
if any(list_type in _type for list_type in ["List", "Sequence", "Set"]):
_type = (
_type.replace("List[", "").replace("Sequence[", "").replace("Set[", "")[:-1]
)
_type = _type.replace("List[", "").replace("Sequence[", "").replace("Set[", "")[:-1]
value["list"] = True
else:
value["list"] = False
@ -454,9 +428,7 @@ def set_headers_value(value: Dict[str, Any]) -> None:
value["value"] = """{"Authorization": "Bearer <token>"}"""
def add_options_to_field(
value: Dict[str, Any], class_name: Optional[str], key: str
) -> None:
def add_options_to_field(value: Dict[str, Any], class_name: Optional[str], key: str) -> None:
"""
Adds options to the field based on the class name and key.
"""

View file

@ -173,8 +173,9 @@ export default function GenericNode({
const iconColor = nodeColors[types[data.type]];
const iconName =
iconElement || (data.node?.flow ? "group_components" : name);
const iconClassName = `generic-node-icon ${!showNode ? " absolute inset-x-6 h-12 w-12 " : ""
}`;
const iconClassName = `generic-node-icon ${
!showNode ? " absolute inset-x-6 h-12 w-12 " : ""
}`;
if (iconElement && isEmoji) {
return nodeIconFragment(iconElement);
} else {
@ -303,7 +304,7 @@ export default function GenericNode({
numberOfHandles={handles}
showNode={showNode}
openAdvancedModal={false}
onCloseAdvancedModal={() => { }}
onCloseAdvancedModal={() => {}}
selected={selected}
></NodeToolbarComponent>
</NodeToolbar>
@ -426,33 +427,33 @@ export default function GenericNode({
data={data}
color={
data.node?.template[templateField].input_types &&
data.node?.template[templateField].input_types!
.length > 0
data.node?.template[templateField].input_types!
.length > 0
? nodeColors[
data.node?.template[templateField]
.input_types![
data.node?.template[templateField]
.input_types!.length - 1
]
] ??
nodeColors[
types[
data.node?.template[templateField]
.input_types![
data.node?.template[templateField]
.input_types!.length - 1
]
]
]
data.node?.template[templateField]
.input_types![
data.node?.template[templateField]
.input_types!.length - 1
]
] ??
nodeColors[
types[
data.node?.template[templateField]
.input_types![
data.node?.template[templateField]
.input_types!.length - 1
]
]
]
: nodeColors[
data.node?.template[templateField].type!
] ??
nodeColors[
types[
data.node?.template[templateField].type!
]
] ??
nodeColors.unknown
data.node?.template[templateField].type!
] ??
nodeColors[
types[
data.node?.template[templateField].type!
]
] ??
nodeColors.unknown
}
title={getFieldTitle(
data.node?.template!,
@ -496,7 +497,7 @@ export default function GenericNode({
color={nodeColors[types[data.type]] ?? nodeColors.unknown}
title={
data.node?.output_types &&
data.node.output_types.length > 0
data.node.output_types.length > 0
? data.node.output_types.join(" | ")
: data.type
}
@ -515,59 +516,62 @@ export default function GenericNode({
</div>
{showNode && (
<ShadTooltip
content={
buildStatus === BuildStatus.BUILDING ? (
<span> {STATUS_BUILDING} </span>
) : !validationStatus ? (
<span className="flex">{STATUS_BUILD}</span>
) : (
<div className="max-h-100 p-2">
<div>
{lastRunTime && (
<div className="justify-left flex text-muted-foreground font-normal">
<div>{RUN_TIMESTAMP_PREFIX}</div>
<div className="ml-1 text-status-blue">
{lastRunTime}
content={
buildStatus === BuildStatus.BUILDING ? (
<span> {STATUS_BUILDING} </span>
) : !validationStatus ? (
<span className="flex">{STATUS_BUILD}</span>
) : (
<div className="max-h-100 p-2">
<div>
{lastRunTime && (
<div className="justify-left flex font-normal text-muted-foreground">
<div>{RUN_TIMESTAMP_PREFIX}</div>
<div className="ml-1 text-status-blue">
{lastRunTime}
</div>
</div>
)}
</div>
<div className="justify-left flex font-normal text-muted-foreground">
<div>Duration:</div>
<div className="mb-3 ml-1 text-status-blue">
{validationStatus?.data.duration}
</div>
)}
</div>
<div className="justify-left flex text-muted-foreground font-normal">
<div>Duration:</div>
<div className="ml-1 text-status-blue mb-3">
{validationStatus?.data.duration}
</div>
<hr />
<span className="mb-2 mt-2 flex justify-center font-semibold text-muted-foreground">
Output
</span>
<div className="max-h-96 overflow-auto font-normal custom-scroll">
{validationString.split("\n").map((line, index) => (
<div className="font-normal" key={index}>
{line}
</div>
))}
</div>
</div>
<hr />
<span className="flex justify-center text-muted-foreground mt-2 mb-2 font-semibold">
Output
</span>
<div className="max-h-96 overflow-auto custom-scroll font-normal">
{validationString.split("\n").map((line, index) => (
<div className="font-normal" key={index}>{line}</div>
))}
</div>
</div>
)
}
side="bottom"
>
<Button onClick={() => {
if (buildStatus === BuildStatus.BUILDING || isBuilding)
return;
setValidationStatus(null);
buildFlow({ stopNodeId: data.id });
}} variant="secondary" className={"group h-9 px-1.5"}>
<div>
<div
className="generic-node-status-position flex items-center justify-center"
>
)
}
side="bottom"
>
<Button
onClick={() => {
if (buildStatus === BuildStatus.BUILDING || isBuilding)
return;
setValidationStatus(null);
buildFlow({ stopNodeId: data.id });
}}
variant="secondary"
className={"group h-9 px-1.5"}
>
<div>
<div className="generic-node-status-position flex items-center justify-center">
{renderIconStatus(buildStatus, validationStatus)}
</div>
</div>
</Button>
</ShadTooltip>
</div>
</Button>
</ShadTooltip>
)}
</div>
</div>
@ -642,7 +646,7 @@ export default function GenericNode({
}}
>
{(data.node?.description === "" || !data.node?.description) &&
nameEditable
nameEditable
? "Double Click to Edit Description"
: data.node?.description}
</div>
@ -655,7 +659,7 @@ export default function GenericNode({
.map((templateField: string, idx) => (
<div key={idx}>
{data.node!.template[templateField].show &&
!data.node!.template[templateField].advanced ? (
!data.node!.template[templateField].advanced ? (
<ParameterComponent
index={idx.toString()}
key={scapedJSONStringfy({
@ -669,30 +673,30 @@ export default function GenericNode({
data={data}
color={
data.node?.template[templateField].input_types &&
data.node?.template[templateField].input_types!
.length > 0
data.node?.template[templateField].input_types!
.length > 0
? nodeColors[
data.node?.template[templateField].input_types![
data.node?.template[templateField]
.input_types!.length - 1
]
] ??
nodeColors[
types[
data.node?.template[templateField]
.input_types![
data.node?.template[templateField]
.input_types!.length - 1
]
]
]
data.node?.template[templateField].input_types![
data.node?.template[templateField]
.input_types!.length - 1
]
] ??
nodeColors[
types[
data.node?.template[templateField]
.input_types![
data.node?.template[templateField]
.input_types!.length - 1
]
]
]
: nodeColors[
data.node?.template[templateField].type!
] ??
nodeColors[
types[data.node?.template[templateField].type!]
] ??
nodeColors.unknown
data.node?.template[templateField].type!
] ??
nodeColors[
types[data.node?.template[templateField].type!]
] ??
nodeColors.unknown
}
title={getFieldTitle(
data.node?.template!,
@ -744,9 +748,9 @@ export default function GenericNode({
data={data}
color={
(data.node?.output_types &&
data.node.output_types.length > 0
data.node.output_types.length > 0
? nodeColors[data.node.output_types[0]] ??
nodeColors[types[data.node.output_types[0]]]
nodeColors[types[data.node.output_types[0]]]
: nodeColors[types[data.type]]) ?? nodeColors.unknown
}
title={

View file

@ -20,7 +20,12 @@ import { Badge } from "../ui/badge";
import { Button } from "../ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
export default function IOView({ children, open, setOpen, disable }: {
export default function IOView({
children,
open,
setOpen,
disable,
}: {
children: JSX.Element;
open: boolean;
setOpen: (open: boolean) => void;

View file

@ -1,26 +1,25 @@
import { Card, CardContent, CardDescription, CardTitle } from "../ui/card";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { useNavigate } from "react-router-dom";
import IconComponent from "../genericIconComponent";
import { cn } from "../../utils/utils";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { Card, CardContent, CardDescription, CardTitle } from "../ui/card";
export default function NewFlowCardComponent() {
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const navigate = useNavigate();
return (
<Card onClick={() => {
addFlow(true).then((id) => {
navigate("/flow/" + id);
});
}} className="pt-4 w-80 h-64 cursor-pointer bg-background">
<CardContent className="w-full h-full">
<div className="bg-dotted-spacing-6 bg-dotted-muted-foreground bg-dotted-radius-px rounded-md bg-muted w-full h-full flex flex-col items-center align-middle justify-center">
</div>
</CardContent>
<CardDescription className="px-6 pb-4">
<CardTitle className="text-primary text-lg">Blank Flow</CardTitle>
</CardDescription>
</Card>
)
}
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const navigate = useNavigate();
return (
<Card
onClick={() => {
addFlow(true).then((id) => {
navigate("/flow/" + id);
});
}}
className="h-64 w-80 cursor-pointer bg-background pt-4"
>
<CardContent className="h-full w-full">
<div className="flex h-full w-full flex-col items-center justify-center rounded-md bg-muted align-middle bg-dotted-spacing-6 bg-dotted-muted-foreground bg-dotted-radius-px"></div>
</CardContent>
<CardDescription className="px-6 pb-4">
<CardTitle className="text-lg text-primary">Blank Flow</CardTitle>
</CardDescription>
</Card>
);
}

View file

@ -1,16 +1,15 @@
import { useEffect, useMemo, useRef, useState } from "react";
import useFlowStore from "../../stores/flowStore";
import { ChatType } from "../../types/chat";
import IOView from "../IOview";
import ChatTrigger from "../ViewTriggers/chat";
import { Transition } from "@headlessui/react";
import { useEffect, useMemo, useRef, useState } from "react";
import ApiModal from "../../modals/ApiModal";
import ShareModal from "../../modals/shareModal";
import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { useStoreStore } from "../../stores/storeStore";
import { ChatType } from "../../types/chat";
import { classNames } from "../../utils/utils";
import IOView from "../IOview";
import ForwardedIconComponent from "../genericIconComponent";
import { Separator } from "../ui/separator";
import ShareModal from "../../modals/shareModal";
import { useStoreStore } from "../../stores/storeStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { classNames } from "../../utils/utils";
import ApiModal from "../../modals/ApiModal";
export default function FlowToolbar({ flow }: ChatType): JSX.Element {
const [open, setOpen] = useState(false);
@ -51,7 +50,7 @@ export default function FlowToolbar({ flow }: ChatType): JSX.Element {
<button
disabled={!hasApiKey || !validApiKey || !hasStore}
className={classNames(
"relative inline-flex w-full h-full items-center justify-center hover:bg-hover bg-muted hover:bg-background px-5 py-3 text-foreground transition-all duration-500 ease-in-out gap-[4px] text-sm font-semibold ",
"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-500 ease-in-out hover:bg-background hover:bg-hover ",
!hasApiKey || !validApiKey || !hasStore
? " button-disable text-muted-foreground "
: ""
@ -76,67 +75,83 @@ export default function FlowToolbar({ flow }: ChatType): JSX.Element {
return (
<>
<Transition
show={true}
appear={true}
enter="transition ease-out duration-300"
enterFrom="translate-y-96"
enterTo="translate-y-0"
leave="transition ease-in duration-300"
leaveFrom="translate-y-0"
leaveTo="translate-y-96"
show={true}
appear={true}
enter="transition ease-out duration-300"
enterFrom="translate-y-96"
enterTo="translate-y-0"
leave="transition ease-in duration-300"
leaveFrom="translate-y-0"
leaveTo="translate-y-96"
>
<div
className={
"shadow-round-btn-shadow hover:shadow-round-btn-shadow message-button-position flex items-center justify-center rounded-sm bg-muted shadow-md transition-all gap-7 border"
}
>
<div className="flex">
<div className="flex gap-1 text-medium-indigo rounded-sm transition-all w-full h-full">
{hasIO ? (
<IOView open={open} setOpen={setOpen} disable={!hasIO}>
<div className="relative inline-flex w-full items-center justify-center hover:bg-hover transition-all duration-500 ease-in-out px-5 py-3 text-medium-indigo ease-in-out gap-1 text-sm font-semibold transition-all">
<ForwardedIconComponent
name="Zap"
className={"message-button-icon h-5 w-5 transition-all"}
/>
Run
<div
className={
"shadow-round-btn-shadow hover:shadow-round-btn-shadow message-button-position flex items-center justify-center gap-7 rounded-sm border bg-muted shadow-md transition-all"
}
>
<div className="flex">
<div className="flex h-full w-full gap-1 rounded-sm text-medium-indigo transition-all">
{hasIO ? (
<IOView open={open} setOpen={setOpen} disable={!hasIO}>
<div className="relative inline-flex w-full items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-medium-indigo transition-all transition-all duration-500 ease-in-out ease-in-out hover:bg-hover">
<ForwardedIconComponent
name="Zap"
className={"message-button-icon h-5 w-5 transition-all"}
/>
Run
</div>
</IOView>
) : (
<div
className={`relative inline-flex w-full cursor-not-allowed items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-muted-foreground transition-all duration-500 ease-in-out ease-in-out`}
>
<ForwardedIconComponent
name="Zap"
className={
"message-button-icon h-5 w-5 fill-muted-foreground stroke-muted-foreground transition-all"
}
/>
Run
</div>
)}
</div>
</IOView>
) : (
<div className={`relative inline-flex w-full items-center justify-center transition-all duration-500 ease-in-out px-5 py-3 text-muted-foreground ease-in-out gap-1 text-sm font-semibold cursor-not-allowed`}>
<ForwardedIconComponent
name="Zap"
className={"message-button-icon h-5 w-5 transition-all fill-muted-foreground stroke-muted-foreground"}
/>
Run
</div>
)}
</div>
<div>
<Separator orientation="vertical" />
</div>
<div className="flex items-center gap-2 cursor-pointer">
{currentFlow && currentFlow.data && (
<ApiModal flow={currentFlow}>
<div className={classNames("relative inline-flex w-full items-center justify-center hover:bg-hover px-5 py-3 text-foreground transition-all duration-500 ease-in-out gap-1 text-sm font-semibold")}>
<div>
<Separator orientation="vertical" />
</div>
<div className="flex cursor-pointer items-center gap-2">
{currentFlow && currentFlow.data && (
<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-500 ease-in-out hover:bg-hover"
)}
>
<ForwardedIconComponent
name="Code2"
className={" h-5 w-5"}
/>
API
</div>
</ApiModal>
)}
</div>
<div>
<Separator orientation="vertical" />
</div>
<div className="flex items-center gap-2">
<div className={`side-bar-button ${!hasApiKey || !validApiKey || !hasStore ? " cursor-not-allowed" : " cursor-pointer"}`}>{ModalMemo}</div>
</ApiModal>
)}
</div>
<div>
<Separator orientation="vertical" />
</div>
<div className="flex items-center gap-2">
<div
className={`side-bar-button ${
!hasApiKey || !validApiKey || !hasStore
? " cursor-not-allowed"
: " cursor-pointer"
}`}
>
{ModalMemo}
</div>
</div>
</div>
</div>
</div>
</Transition>
</Transition>
</>
);
}

View file

@ -9,18 +9,18 @@ import {
import { useNavigate } from "react-router-dom";
import { Node } from "reactflow";
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
import { SAVED_HOVER } from "../../../../constants/constants";
import ExportModal from "../../../../modals/exportModal";
import FlowSettingsModal from "../../../../modals/flowSettingsModal";
import useAlertStore from "../../../../stores/alertStore";
import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { useStoreStore } from "../../../../stores/storeStore";
import { cn } from "../../../../utils/utils";
import ShadTooltip from "../../../ShadTooltipComponent";
import IconComponent from "../../../genericIconComponent";
import { Button } from "../../../ui/button";
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
import ExportModal from "../../../../modals/exportModal";
import { useStoreStore } from "../../../../stores/storeStore";
export const MenuBar = ({
removeFunction,
@ -121,12 +121,15 @@ export const MenuBar = ({
<IconComponent name="FileUp" className="header-menu-options " />
Import
</DropdownMenuItem>
<ExportModal>
<div className="header-menubar-item">
<IconComponent name="FileDown" className="header-menu-options" />
Export
</div>
</ExportModal>
<ExportModal>
<div className="header-menubar-item">
<IconComponent
name="FileDown"
className="header-menu-options"
/>
Export
</div>
</ExportModal>
<DropdownMenuItem
onClick={() => {
undo();
@ -141,7 +144,9 @@ export const MenuBar = ({
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
/>
) : (
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">Ctrl + </span>
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">
Ctrl +{" "}
</span>
)}
<span className="absolute right-2 top-[0.4em]">Z</span>
</DropdownMenuItem>
@ -159,7 +164,9 @@ export const MenuBar = ({
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
/>
) : (
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">Ctrl + </span>
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">
Ctrl +{" "}
</span>
)}
<span className="absolute right-2 top-[0.4em]">Y</span>
</DropdownMenuItem>

View file

@ -92,7 +92,7 @@ export default function KeypairListComponent({
{isList && index === ref.current.length - 1 ? (
<button
disabled={disabled}
disabled={disabled}
onClick={() => {
let newInputList = _.cloneDeep(ref.current);
newInputList.push({ "": "" });

View file

@ -1,60 +1,107 @@
import { useNavigate } from "react-router-dom";
/// <reference types="vite-plugin-svgr/client" />
//@ts-ignore
import { ReactComponent as TransferFiles } from "../../assets/undraw_transfer_files_re_a2a9.svg"
import { ReactComponent as TransferFiles } from "../../assets/undraw_transfer_files_re_a2a9.svg";
//@ts-ignore
import { ReactComponent as BasicPrompt } from "../../assets/undraw_design_components_9vy6.svg"
import { ReactComponent as BasicPrompt } from "../../assets/undraw_design_components_9vy6.svg";
//@ts-ignore
import { ReactComponent as ChatWithHistory } from "../../assets/undraw_mobile_messages_re_yx8w.svg"
import { ReactComponent as ChatWithHistory } from "../../assets/undraw_mobile_messages_re_yx8w.svg";
//@ts-ignore
import { ReactComponent as Assistant } from "../../assets/undraw_team_collaboration_re_ow29.svg"
import { ReactComponent as Assistant } from "../../assets/undraw_team_collaboration_re_ow29.svg";
//@ts-ignore
import { ReactComponent as APIRequest } from "../../assets/undraw_real_time_analytics_re_yliv.svg"
import { ReactComponent as APIRequest } from "../../assets/undraw_real_time_analytics_re_yliv.svg";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { FlowType } from "../../types/flow"
import { FlowType } from "../../types/flow";
import { updateIds } from "../../utils/reactflowUtils";
import ShadTooltip from "../ShadTooltipComponent"
import { Card, CardContent, CardDescription, CardFooter, CardTitle } from "../ui/card"
import { Card, CardContent, CardDescription, CardTitle } from "../ui/card";
export default function UndrawCardComponent({
flow
}: { flow: FlowType }) {
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const navigate = useNavigate();
export default function UndrawCardComponent({ flow }: { flow: FlowType }) {
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const navigate = useNavigate();
function selectImage() {
switch (flow.name) {
case "Data Ingestion":
return <TransferFiles style={{ width: '80%', height: '80%', preserveAspectRatio: 'xMidYMid meet' }} />
case "Basic Prompting":
return <BasicPrompt style={{ width: '80%', height: '80%', preserveAspectRatio: 'xMidYMid meet' }} />
case "Chat with memory":
return <ChatWithHistory style={{ width: '70%', height: '70%', preserveAspectRatio: 'xMidYMid meet' }} />
case "API requests":
return <APIRequest style={{ width: '70%', height: '70%', preserveAspectRatio: 'xMidYMid meet' }} />
case "Assistant":
return <Assistant style={{ width: '80%', height: '80%', preserveAspectRatio: 'xMidYMid meet' }} />
default:
return <TransferFiles style={{ width: '80%', height: '80%', preserveAspectRatio: 'xMidYMid meet' }} />
}
function selectImage() {
switch (flow.name) {
case "Data Ingestion":
return (
<TransferFiles
style={{
width: "80%",
height: "80%",
preserveAspectRatio: "xMidYMid meet",
}}
/>
);
case "Basic Prompting":
return (
<BasicPrompt
style={{
width: "80%",
height: "80%",
preserveAspectRatio: "xMidYMid meet",
}}
/>
);
case "Chat with memory":
return (
<ChatWithHistory
style={{
width: "70%",
height: "70%",
preserveAspectRatio: "xMidYMid meet",
}}
/>
);
case "API requests":
return (
<APIRequest
style={{
width: "70%",
height: "70%",
preserveAspectRatio: "xMidYMid meet",
}}
/>
);
case "Assistant":
return (
<Assistant
style={{
width: "80%",
height: "80%",
preserveAspectRatio: "xMidYMid meet",
}}
/>
);
default:
return (
<TransferFiles
style={{
width: "80%",
height: "80%",
preserveAspectRatio: "xMidYMid meet",
}}
/>
);
}
}
return (
<Card onClick={() => {
updateIds(flow.data!);
addFlow(true, flow).then((id) => {
navigate("/flow/" + id);
});
}} className="pt-4 w-80 h-64 cursor-pointer bg-background">
<CardContent className="w-full h-full">
<div className="rounded-md p-1 bg-muted w-full h-full flex flex-col items-center align-middle justify-center">
{selectImage()}
</div>
</CardContent>
<CardDescription className="px-6 pb-4">
<CardTitle className="text-primary text-lg">{flow.name}</CardTitle>
</CardDescription>
</Card>
)
}
return (
<Card
onClick={() => {
updateIds(flow.data!);
addFlow(true, flow).then((id) => {
navigate("/flow/" + id);
});
}}
className="h-64 w-80 cursor-pointer bg-background pt-4"
>
<CardContent className="h-full w-full">
<div className="flex h-full w-full flex-col items-center justify-center rounded-md bg-muted p-1 align-middle">
{selectImage()}
</div>
</CardContent>
<CardDescription className="px-6 pb-4">
<CardTitle className="text-lg text-primary">{flow.name}</CardTitle>
</CardDescription>
</Card>
);
}

View file

@ -750,14 +750,14 @@ Vector Store
Assistant
*/
export const EXAMPLES_MOCK:FlowType[] = [
export const EXAMPLES_MOCK: FlowType[] = [
{
name: "Working with data",
id: "Working with data Description",
data: {
nodes: [],
edges: [],
viewport: { zoom: 1, x: 1, y: 1 }
viewport: { zoom: 1, x: 1, y: 1 },
},
description: "This flow represents the first process in our application.",
folder: STARTER_FOLDER_NAME,
@ -769,7 +769,7 @@ export const EXAMPLES_MOCK:FlowType[] = [
data: {
nodes: [],
edges: [],
viewport: { zoom: 1, x: 1, y: 1 }
viewport: { zoom: 1, x: 1, y: 1 },
},
description: "This flow represents the first process in our application.",
folder: STARTER_FOLDER_NAME,
@ -781,7 +781,7 @@ export const EXAMPLES_MOCK:FlowType[] = [
data: {
nodes: [],
edges: [],
viewport: { zoom: 1, x: 1, y: 1 }
viewport: { zoom: 1, x: 1, y: 1 },
},
description: "This flow represents the first process in our application.",
folder: STARTER_FOLDER_NAME,
@ -793,7 +793,7 @@ export const EXAMPLES_MOCK:FlowType[] = [
data: {
nodes: [],
edges: [],
viewport: { zoom: 1, x: 1, y: 1 }
viewport: { zoom: 1, x: 1, y: 1 },
},
description: "This flow represents the first process in our application.",
folder: STARTER_FOLDER_NAME,
@ -805,7 +805,7 @@ export const EXAMPLES_MOCK:FlowType[] = [
data: {
nodes: [],
edges: [],
viewport: { zoom: 1, x: 1, y: 1 }
viewport: { zoom: 1, x: 1, y: 1 },
},
description: "This flow represents the first process in our application.",
folder: STARTER_FOLDER_NAME,

View file

@ -12,6 +12,7 @@ import ReactFlow, {
updateEdge,
} from "reactflow";
import GenericNode from "../../../../CustomNodes/GenericNode";
import FlowToolbar from "../../../../components/chatComponent";
import {
INVALID_SELECTION_ERROR_ALERT,
UPLOAD_ALERT_LIST,
@ -37,7 +38,6 @@ import { getRandomName, isWrappedWithClass } from "../../../../utils/utils";
import ConnectionLineComponent from "../ConnectionLineComponent";
import SelectionMenu from "../SelectionMenuComponent";
import ExtraSidebar from "../extraSidebarComponent";
import FlowToolbar from "../../../../components/chatComponent";
const nodeTypes = {
genericNode: GenericNode,

View file

@ -267,7 +267,7 @@ export default function ExtraSidebar(): JSX.Element {
<>
{index === 0 && (
<div className="pt-0.5">
<div className="p-2 px-4 font-semibold text-sm" key={index}>
<div className="p-2 px-4 text-sm font-semibold" key={index}>
Native Components
</div>
</div>
@ -283,7 +283,7 @@ export default function ExtraSidebar(): JSX.Element {
{/* BUG ON THIS ICON */}
<SparklesIcon
strokeWidth={1.5}
className="text-primary w-[22px]"
className="w-[22px] text-primary"
/>
<span className="components-disclosure-title">
@ -296,7 +296,7 @@ export default function ExtraSidebar(): JSX.Element {
</div>
</div>
</a>
<div className="p-2 px-4 font-semibold text-sm" key={index}>
<div className="p-2 px-4 text-sm font-semibold" key={index}>
Legacy Components
</div>
</>

View file

@ -13,6 +13,7 @@ import {
import ConfirmationModal from "../../../../modals/ConfirmationModal";
import EditNodeModal from "../../../../modals/EditNodeModal";
import ShareModal from "../../../../modals/shareModal";
import useAlertStore from "../../../../stores/alertStore";
import { useDarkStore } from "../../../../stores/darkStore";
import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
@ -27,7 +28,6 @@ import {
updateFlowPosition,
} from "../../../../utils/reactflowUtils";
import { classNames, cn } from "../../../../utils/utils";
import useAlertStore from "../../../../stores/alertStore";
export default function NodeToolbarComponent({
data,
@ -97,8 +97,8 @@ export default function NodeToolbarComponent({
(state) => state.setLastCopiedSelection
);
const setSuccessData = useAlertStore(state => state.setSuccessData);
const setNoticeData = useAlertStore(state => state.setNoticeData);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setNoticeData = useAlertStore((state) => state.setNoticeData);
useEffect(() => {
setFlowComponent(createFlowComponent(cloneDeep(data), version));
@ -166,10 +166,8 @@ export default function NodeToolbarComponent({
{
x: 50,
y: 10,
paneX: nodes.find((node) => node.id === data.id)?.position
.x,
paneY: nodes.find((node) => node.id === data.id)?.position
.y,
paneX: nodes.find((node) => node.id === data.id)?.position.x,
paneY: nodes.find((node) => node.id === data.id)?.position.y,
}
);
break;
@ -229,7 +227,7 @@ export default function NodeToolbarComponent({
const [openModal, setOpenModal] = useState(false);
const hasCode = Object.keys(data.node!.template).includes("code");
useEffect(() => {
function onKeyDown(event: KeyboardEvent) {
if (
@ -260,18 +258,17 @@ export default function NodeToolbarComponent({
event.preventDefault();
setShowconfirmShare((state) => !state);
}
if (
selected &&
(event.ctrlKey || event.metaKey) &&
event.key === "q"
) {
if (selected && (event.ctrlKey || event.metaKey) && event.key === "q") {
event.preventDefault();
if (isMinimal) {
setShowState(show => !show)
setShowState((show) => !show);
setShowNode(data.showNode ?? true ? false : true);
return
return;
}
setNoticeData({title: "Minimization are only available for nodes with one handle or fewer."});
setNoticeData({
title:
"Minimization are only available for nodes with one handle or fewer.",
});
}
if (
selected &&
@ -281,7 +278,7 @@ export default function NodeToolbarComponent({
) {
event.preventDefault();
if (hasCode) return setOpenModal((state) => !state);
setNoticeData({title: `You can not access ${data.id} code`})
setNoticeData({ title: `You can not access ${data.id} code` });
}
if (
selected &&
@ -292,11 +289,7 @@ export default function NodeToolbarComponent({
event.preventDefault();
setShowModalAdvanced((state) => !state);
}
if (
selected &&
(event.ctrlKey || event.metaKey) &&
event.key === "s"
) {
if (selected && (event.ctrlKey || event.metaKey) && event.key === "s") {
if (isSaved) {
event.preventDefault();
return setShowOverrideModal((state) => !state);
@ -304,7 +297,7 @@ export default function NodeToolbarComponent({
if (hasCode) {
event.preventDefault();
saveComponent(cloneDeep(data), false);
setSuccessData({title: `${data.id} saved successfully`})
setSuccessData({ title: `${data.id} saved successfully` });
}
}
if (
@ -317,7 +310,9 @@ export default function NodeToolbarComponent({
if (data.node?.documentation) {
return openInNewTab(data.node?.documentation);
}
setNoticeData({title: `${data.id} docs is not available at the moment.`})
setNoticeData({
title: `${data.id} docs is not available at the moment.`,
});
}
}
@ -349,7 +344,8 @@ export default function NodeToolbarComponent({
<ShadTooltip content={"Save"} side="top">
<button
className={classNames(
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10", (hasCode ? " " : " rounded-l-md ")
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10",
hasCode ? " " : " rounded-l-md "
)}
onClick={(event) => {
event.preventDefault();
@ -359,10 +355,7 @@ export default function NodeToolbarComponent({
saveComponent(cloneDeep(data), false);
}}
>
<IconComponent
name="SaveAll"
className="h-4 w-4"
/>
<IconComponent name="SaveAll" className="h-4 w-4" />
</button>
</ShadTooltip>
@ -425,28 +418,35 @@ export default function NodeToolbarComponent({
<span className="">Edit</span>{" "}
{navigator.userAgent.toUpperCase().includes("MAC") ? (
<IconComponent
name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
></IconComponent>
name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
></IconComponent>
) : (
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">Ctrl + </span>
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">
Ctrl +{" "}
</span>
)}
<span className="absolute right-2 top-[0.46em]">E</span>
</div>
</SelectItem>
)}
)}
<SelectItem value={"duplicate"}>
<div className="flex" data-testid="save-button-modal">
<IconComponent name="Copy" className="relative top-0.5 mr-2 h-4 w-4" />
<IconComponent
name="Copy"
className="relative top-0.5 mr-2 h-4 w-4"
/>
Duplicate
{navigator.userAgent.toUpperCase().includes("MAC") ? (
<IconComponent
<IconComponent
name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
></IconComponent>
) : (
<span className="absolute right-[1.20rem] top-[0.40em] stroke-2">Ctrl + </span>
)}
) : (
<span className="absolute right-[1.20rem] top-[0.40em] stroke-2">
Ctrl +{" "}
</span>
)}
<span className="absolute right-2 top-[0.4em]">D</span>
</div>{" "}
</SelectItem>
@ -458,13 +458,15 @@ export default function NodeToolbarComponent({
/>{" "}
<span className="">Copy</span>{" "}
{navigator.userAgent.toUpperCase().includes("MAC") ? (
<IconComponent
<IconComponent
name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
></IconComponent>
) : (
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">Ctrl + </span>
)}
) : (
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">
Ctrl +{" "}
</span>
)}
<span className="absolute right-2 top-[0.4em]">C</span>
</div>
</SelectItem>
@ -481,13 +483,14 @@ export default function NodeToolbarComponent({
Share{" "}
{navigator.userAgent.toUpperCase().includes("MAC") ? (
<IconComponent
name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
></IconComponent>
name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
></IconComponent>
) : (
<span className="absolute right-[2.10rem] top-[0.43em] stroke-2">Ctrl</span>
<span className="absolute right-[2.10rem] top-[0.43em] stroke-2">
Ctrl
</span>
)}
<IconComponent
name="ArrowBigUp"
className="absolute right-[1.09rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
@ -518,13 +521,15 @@ export default function NodeToolbarComponent({
/>{" "}
<span className="">Docs</span>{" "}
{navigator.userAgent.toUpperCase().includes("MAC") ? (
<IconComponent
<IconComponent
name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
></IconComponent>
) : (
<span className="absolute right-[2.10rem] top-[0.43em] stroke-2">Ctrl</span>
)}
) : (
<span className="absolute right-[2.10rem] top-[0.43em] stroke-2">
Ctrl
</span>
)}
<IconComponent
name="ArrowBigUp"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
@ -542,11 +547,13 @@ export default function NodeToolbarComponent({
{showNode ? "Minimize" : "Expand"}
{navigator.userAgent.toUpperCase().includes("MAC") ? (
<IconComponent
name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
></IconComponent>
name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
></IconComponent>
) : (
<span className="absolute right-[1.30rem] top-[0.40em] stroke-2">Ctrl + </span>
<span className="absolute right-[1.30rem] top-[0.40em] stroke-2">
Ctrl +{" "}
</span>
)}
<span className="absolute right-2 top-[0.46em]">Q</span>
</div>
@ -562,11 +569,13 @@ export default function NodeToolbarComponent({
<span className="">Ungroup</span>{" "}
{navigator.userAgent.toUpperCase().includes("MAC") ? (
<IconComponent
name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
></IconComponent>
name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
></IconComponent>
) : (
<span className="absolute right-[1.30rem] top-[0.40em] stroke-2">Ctrl + </span>
<span className="absolute right-[1.30rem] top-[0.40em] stroke-2">
Ctrl +{" "}
</span>
)}
<span className="absolute right-2 top-[0.43em]">U</span>
</div>
@ -601,12 +610,12 @@ export default function NodeToolbarComponent({
index={6}
onConfirm={(index, user) => {
saveComponent(cloneDeep(data), true);
setSuccessData({title: `${data.id} successfully overridden!`})
setSuccessData({ title: `${data.id} successfully overridden!` });
}}
onClose={setShowOverrideModal}
onCancel={() => {
saveComponent(cloneDeep(data), false)
setSuccessData({title: "New node successfully saved!"})
saveComponent(cloneDeep(data), false);
setSuccessData({ title: "New node successfully saved!" });
}}
>
<ConfirmationModal.Content>

View file

@ -2,15 +2,14 @@ import { Group, ToyBrick } from "lucide-react";
import { useEffect, useState } from "react";
import { Outlet, useLocation, useNavigate } from "react-router-dom";
import DropdownButton from "../../components/DropdownButtonComponent";
import NewFlowCardComponent from "../../components/NewFLowCard2";;
import ExampleCardComponent from "../../components/exampleComponent";
import NewFlowCardComponent from "../../components/NewFLowCard2";
import IconComponent from "../../components/genericIconComponent";
import PageLayout from "../../components/pageLayout";
import SidebarNav from "../../components/sidebarComponent";
import { Button } from "../../components/ui/button";
import UndrawCardComponent from "../../components/undrawCards";
import { CONSOLE_ERROR_MSG } from "../../constants/alerts_constants";
import {
EXAMPLES_MOCK,
MY_COLLECTION_DESC,
USER_PROJECTS_HEADER,
} from "../../constants/constants";
@ -18,7 +17,6 @@ import BaseModal from "../../modals/baseModal";
import useAlertStore from "../../stores/alertStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { downloadFlows } from "../../utils/reactflowUtils";
import UndrawCardComponent from "../../components/undrawCards";
export default function HomePage(): JSX.Element {
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
@ -120,9 +118,7 @@ export default function HomePage(): JSX.Element {
</div>
</div>
<BaseModal size="three-cards" open={openModal} setOpen={setOpenModal}>
<BaseModal.Header
description={"Select a template below"}
>
<BaseModal.Header description={"Select a template below"}>
<span className="pr-2" data-testid="modal-title">
Get Started
</span>
@ -133,7 +129,7 @@ export default function HomePage(): JSX.Element {
/> */}
</BaseModal.Header>
<BaseModal.Content>
<div className=" h-full w-full grid grid-cols-3 gap-3 overflow-auto p-4 custom-scroll">
<div className=" grid h-full w-full grid-cols-3 gap-3 overflow-auto p-4 custom-scroll">
{examples.map((example, idx) => {
return <UndrawCardComponent key={idx} flow={example} />;
})}

View file

@ -87,7 +87,7 @@ module.exports = {
"beta-foreground": "var(--beta-foreground)",
"chat-bot-icon": "var(--chat-bot-icon)",
"chat-user-icon": "var(--chat-user-icon)",
"ice": "var(--ice)",
ice: "var(--ice)",
hover: "var(--hover)",
white: "var(--white)",
border: "hsl(var(--border))",
@ -224,6 +224,6 @@ module.exports = {
}),
require("@tailwindcss/typography"),
require("daisyui"),
require('tailwindcss-dotted-background'),
require("tailwindcss-dotted-background"),
],
};

View file

@ -1,4 +1,5 @@
import pytest
from langflow.services.database.models.api_key import ApiKeyCreate
@ -21,7 +22,6 @@ def test_get_api_keys(client, logged_in_headers, api_key):
assert any("test-api-key" in api_key["name"] for api_key in data["api_keys"])
# assert all api keys in data["api_keys"] are masked
assert all("**" in api_key["api_key"] for api_key in data["api_keys"])
# Add more assertions as needed based on the expected data structure and content
def test_create_api_key(client, logged_in_headers):

View file

@ -38,9 +38,7 @@ def test_create_or_update_starter_projects(client):
num_projects = len(load_starter_projects())
# Get the number of projects in the database
num_db_projects = session.exec(
select(func.count(Flow.id)).where(Flow.folder == STARTER_FOLDER_NAME)
).one()
num_db_projects = session.exec(select(func.count(Flow.id)).where(Flow.folder == STARTER_FOLDER_NAME)).one()
# Check that the number of projects in the database is the same as the number of projects returned by load_starter_projects
assert num_db_projects == num_projects
@ -56,21 +54,16 @@ async def test_starter_project_can_run_successfully(client):
num_projects = len(load_starter_projects())
# Get the number of projects in the database
num_db_projects = session.exec(
select(func.count(Flow.id)).where(Flow.folder == STARTER_FOLDER_NAME)
).one()
num_db_projects = session.exec(select(func.count(Flow.id)).where(Flow.folder == STARTER_FOLDER_NAME)).one()
# Check that the number of projects in the database is the same as the number of projects returned by load_starter_projects
assert num_db_projects == num_projects
# Get all the starter projects
projects = session.exec(
select(Flow).where(Flow.folder == STARTER_FOLDER_NAME)
).all()
projects = session.exec(select(Flow).where(Flow.folder == STARTER_FOLDER_NAME)).all()
graphs: list[Graph] = [
(project.name, Graph.from_payload(project.data, flow_id=project.id))
for project in projects
(project.name, Graph.from_payload(project.data, flow_id=project.id)) for project in projects
]
assert len(graphs) == len(projects)
for name, graph in graphs:

View file

@ -268,13 +268,9 @@ async def test_load_langchain_object_with_cached_session(client, basic_graph_dat
# Provide a non-existent session_id
session_service = get_session_service()
session_id1 = "non-existent-session-id"
graph1, artifacts1 = await session_service.load_session(
session_id1, basic_graph_data
)
graph1, artifacts1 = await session_service.load_session(session_id1, basic_graph_data)
# Use the new session_id to get the langchain_object again
graph2, artifacts2 = await session_service.load_session(
session_id1, basic_graph_data
)
graph2, artifacts2 = await session_service.load_session(session_id1, basic_graph_data)
assert graph1 == graph2
assert artifacts1 == artifacts2
@ -286,15 +282,11 @@ async def test_load_langchain_object_with_no_cached_session(client, basic_graph_
session_service = get_session_service()
session_id1 = "non-existent-session-id"
session_id = session_service.build_key(session_id1, basic_graph_data)
graph1, artifacts1 = await session_service.load_session(
session_id, basic_graph_data
)
graph1, artifacts1 = await session_service.load_session(session_id, basic_graph_data)
# Clear the cache
session_service.clear_session(session_id)
# Use the new session_id to get the langchain_object again
graph2, artifacts2 = await session_service.load_session(
session_id, basic_graph_data
)
graph2, artifacts2 = await session_service.load_session(session_id, basic_graph_data)
assert id(graph1) != id(graph2)
# Since the cache was cleared, objects should be different
@ -305,12 +297,8 @@ async def test_load_langchain_object_without_session_id(client, basic_graph_data
# Provide a non-existent session_id
session_service = get_session_service()
session_id1 = None
graph1, artifacts1 = await session_service.load_session(
session_id1, basic_graph_data
)
graph1, artifacts1 = await session_service.load_session(session_id1, basic_graph_data)
# Use the new session_id to get the langchain_object again
graph2, artifacts2 = await session_service.load_session(
session_id1, basic_graph_data
)
graph2, artifacts2 = await session_service.load_session(session_id1, basic_graph_data)
assert graph1 == graph2