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): def has_api_terms(word: str):
return "api" in word and ( return "api" in word and ("key" in word or ("token" in word and "tokens" not in word))
"key" in word or ("token" in word and "tokens" not in word)
)
def remove_api_keys(flow: dict): def remove_api_keys(flow: dict):
@ -32,11 +30,7 @@ def remove_api_keys(flow: dict):
node_data = node.get("data").get("node") node_data = node.get("data").get("node")
template = node_data.get("template") template = node_data.get("template")
for value in template.values(): for value in template.values():
if ( if isinstance(value, dict) and has_api_terms(value["name"]) and value.get("password"):
isinstance(value, dict)
and has_api_terms(value["name"])
and value.get("password")
):
value["value"] = None value["value"] = None
return flow return flow
@ -57,9 +51,7 @@ def build_input_keys_response(langchain_object, artifacts):
input_keys_response["input_keys"][key] = value input_keys_response["input_keys"][key] = value
# If the object has memory, that memory will have a memory_variables attribute # If the object has memory, that memory will have a memory_variables attribute
# memory variables should be removed from the input keys # memory variables should be removed from the input keys
if hasattr(langchain_object, "memory") and hasattr( if hasattr(langchain_object, "memory") and hasattr(langchain_object.memory, "memory_variables"):
langchain_object.memory, "memory_variables"
):
# Remove memory variables from input keys # Remove memory variables from input keys
input_keys_response["input_keys"] = { input_keys_response["input_keys"] = {
key: value key: value
@ -69,9 +61,7 @@ def build_input_keys_response(langchain_object, artifacts):
# Add memory variables to memory_keys # Add memory variables to memory_keys
input_keys_response["memory_keys"] = langchain_object.memory.memory_variables input_keys_response["memory_keys"] = langchain_object.memory.memory_variables
if hasattr(langchain_object, "prompt") and hasattr( if hasattr(langchain_object, "prompt") and hasattr(langchain_object.prompt, "template"):
langchain_object.prompt, "template"
):
input_keys_response["template"] = langchain_object.prompt.template input_keys_response["template"] = langchain_object.prompt.template
return input_keys_response 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): def is_valid_data(frontend_node, raw_frontend_data):
"""Check if the data is valid for processing.""" """Check if the data is valid for processing."""
return ( return frontend_node and "template" in frontend_node and raw_frontend_data_is_valid(raw_frontend_data)
frontend_node
and "template" in frontend_node
and raw_frontend_data_is_valid(raw_frontend_data)
)
def update_template_values(frontend_template, raw_template): 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 # If the path is not in the cache dir, return empty string
# This is to prevent access to files outside the cache dir # This is to prevent access to files outside the cache dir
# If the path is not a file, return empty string # If the path is not a file, return empty string
if not path.exists() or not str(path).startswith( if not path.exists() or not str(path).startswith(user_cache_dir("langflow", "langflow")):
user_cache_dir("langflow", "langflow")
):
return "" return ""
return file_path return file_path
@ -183,9 +167,7 @@ async def check_langflow_version(component: StoreComponentCreate):
langflow_version = get_lf_version_from_pypi() langflow_version = get_lf_version_from_pypi()
if langflow_version is None: if langflow_version is None:
raise HTTPException( raise HTTPException(status_code=500, detail="Unable to verify the latest version of Langflow")
status_code=500, detail="Unable to verify the latest version of Langflow"
)
elif langflow_version != component.last_tested_version: elif langflow_version != component.last_tested_version:
warnings.warn( warnings.warn(
f"Your version of Langflow ({component.last_tested_version}) is outdated. " 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 # Handle variables starting with a number
if var[0].isdigit(): if var[0].isdigit():
invalid_chars.append(var[0]) invalid_chars.append(var[0])
new_var, invalid_chars, wrong_variables = fix_variable( new_var, invalid_chars, wrong_variables = fix_variable(var[1:], invalid_chars, wrong_variables)
var[1:], invalid_chars, wrong_variables
)
# Temporarily replace {{ and }} to avoid treating them as invalid # Temporarily replace {{ and }} to avoid treating them as invalid
new_var = new_var.replace("{{", "ᴛᴇᴍᴘᴏᴘᴇɴ").replace("}}", "ᴛᴇᴍᴘᴄʟᴏsᴇ") 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 return wrong_variables, empty_variables
def check_for_errors( def check_for_errors(input_variables, fixed_variables, wrong_variables, empty_variables):
input_variables, fixed_variables, wrong_variables, empty_variables
):
if any(var for var in input_variables if var not in fixed_variables): if any(var for var in input_variables if var not in fixed_variables):
error_message = ( error_message = (
f"Error: Input variables contain invalid characters or formats. \n" 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): if is_json_like(var):
continue continue
new_var, wrong_variables, empty_variables = fix_variable( new_var, wrong_variables, empty_variables = fix_variable(var, invalid_chars, wrong_variables)
var, invalid_chars, wrong_variables wrong_variables, empty_variables = check_variable(var, INVALID_CHARACTERS, wrong_variables, empty_variables)
)
wrong_variables, empty_variables = check_variable(
var, INVALID_CHARACTERS, wrong_variables, empty_variables
)
fixed_variables.append(new_var) fixed_variables.append(new_var)
variables_to_check.append(var) variables_to_check.append(var)
check_for_errors( check_for_errors(variables_to_check, fixed_variables, wrong_variables, empty_variables)
variables_to_check, fixed_variables, wrong_variables, empty_variables
)
return fixed_variables return fixed_variables

View file

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

View file

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

View file

@ -45,9 +45,7 @@ class ChatComponent(CustomComponent):
return [] return []
if not session_id or not sender or not sender_name: if not session_id or not sender or not sender_name:
raise ValueError( raise ValueError("All of session_id, sender, and sender_name must be provided.")
"All of session_id, sender, and sender_name must be provided."
)
if isinstance(message, Record): if isinstance(message, Record):
record = message record = message
record.data.update( 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 # Check if there are invalid characters in the input_variables
input_variables = check_input_variables(input_variables) input_variables = check_input_variables(input_variables)
if any(var in INVALID_NAMES for var in input_variables): if any(var in INVALID_NAMES for var in input_variables):
raise ValueError( raise ValueError(f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. ")
f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. "
)
try: try:
PromptTemplate(template=prompt_template, input_variables=input_variables) 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 raise HTTPException(status_code=500, detail=str(exc)) from exc
def remove_old_variables_from_template( def remove_old_variables_from_template(old_custom_fields, input_variables, custom_fields, template, name):
old_custom_fields, input_variables, custom_fields, template, name
):
for variable in old_custom_fields: for variable in old_custom_fields:
if variable not in input_variables: if variable not in input_variables:
try: try:

View file

@ -56,9 +56,7 @@ class APIRequest(CustomComponent):
data = body if body else None data = body if body else None
payload = json.dumps(data) payload = json.dumps(data)
try: try:
response = await client.request( response = await client.request(method, url, headers=headers, content=payload, timeout=timeout)
method, url, headers=headers, content=payload, timeout=timeout
)
try: try:
result = response.json() result = response.json()
except Exception: except Exception:
@ -108,9 +106,6 @@ class APIRequest(CustomComponent):
bodies = [body.data] bodies = [body.data]
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
results = await asyncio.gather( 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 return results

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -9,6 +9,7 @@ class ChatOutput(ChatComponent):
display_name = "Chat Output" display_name = "Chat Output"
description = "Used to send a message to the chat." description = "Used to send a message to the chat."
icon = "ChatOutput" icon = "ChatOutput"
def build( def build(
self, self,
sender: Optional[str] = "Machine", 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): class RecursiveCharacterTextSplitterComponent(CustomComponent):
display_name: str = "Recursive Character Text Splitter" display_name: str = "Recursive Character Text Splitter"
description: str = "Split text into chunks of a specified length." description: str = "Split text into chunks of a specified length."
documentation: str = ( documentation: str = "https://docs.langflow.org/components/text-splitters#recursivecharactertextsplitter"
"https://docs.langflow.org/components/text-splitters#recursivecharactertextsplitter"
)
def build_config(self): def build_config(self):
return { return {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -46,10 +46,7 @@ class VertexTypesDict(LazyLoadDictBase):
**{t: types.DocumentLoaderVertex for t in documentloader_creator.to_list()}, **{t: types.DocumentLoaderVertex for t in documentloader_creator.to_list()},
**{t: types.TextSplitterVertex for t in textsplitter_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.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.RetrieverVertex for t in retriever_creator.to_list()},
**{t: types.ChatVertex for t in CHAT_COMPONENTS}, **{t: types.ChatVertex for t in CHAT_COMPONENTS},
**{t: types.RoutingVertex for t in ROUTING_COMPONENTS}, **{t: types.RoutingVertex for t in ROUTING_COMPONENTS},

View file

@ -60,13 +60,8 @@ class Vertex:
self.updated_raw_params = False self.updated_raw_params = False
self.id: str = data["id"] self.id: str = data["id"]
self.is_state = False self.is_state = False
self.is_input = any( self.is_input = any(input_component_name in self.id for input_component_name in INPUT_COMPONENTS)
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_output = any(
output_component_name in self.id
for output_component_name in OUTPUT_COMPONENTS
)
self.has_session_id = None self.has_session_id = None
self._custom_component = None self._custom_component = None
self.has_external_input = False self.has_external_input = False
@ -106,17 +101,11 @@ class Vertex:
def set_state(self, state: str): def set_state(self, state: str):
self.state = VertexStates[state] self.state = VertexStates[state]
if ( if self.state == VertexStates.INACTIVE and self.graph.in_degree_map[self.id] < 2:
self.state == VertexStates.INACTIVE
and self.graph.in_degree_map[self.id] < 2
):
# If the vertex is inactive and has only one in degree # If the vertex is inactive and has only one in degree
# it means that it is not a merge point in the graph # it means that it is not a merge point in the graph
self.graph.inactivated_vertices.add(self.id) self.graph.inactivated_vertices.add(self.id)
elif ( elif self.state == VertexStates.ACTIVE and self.id in self.graph.inactivated_vertices:
self.state == VertexStates.ACTIVE
and self.id in self.graph.inactivated_vertices
):
self.graph.inactivated_vertices.remove(self.id) self.graph.inactivated_vertices.remove(self.id)
@property @property
@ -133,9 +122,7 @@ class Vertex:
# If the Vertex.type is a power component # If the Vertex.type is a power component
# then we need to return the built object # then we need to return the built object
# instead of the result dict # instead of the result dict
if self.is_interface_component and not isinstance( if self.is_interface_component and not isinstance(self._built_object, UnbuiltObject):
self._built_object, UnbuiltObject
):
result = self._built_object result = self._built_object
# if it is not a dict or a string and hasattr model_dump then # if it is not a dict or a string and hasattr model_dump then
# return the model_dump # return the model_dump
@ -147,11 +134,7 @@ class Vertex:
if isinstance(self._built_result, UnbuiltResult): if isinstance(self._built_result, UnbuiltResult):
return {} return {}
return ( return self._built_result if isinstance(self._built_result, dict) else {"result": self._built_result}
self._built_result
if isinstance(self._built_result, dict)
else {"result": self._built_result}
)
def set_artifacts(self) -> None: def set_artifacts(self) -> None:
pass pass
@ -221,31 +204,19 @@ class Vertex:
self.selected_output_type = self.data["node"].get("selected_output_type") 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_input = self.data["node"].get("is_input") or self.is_input
self.is_output = self.data["node"].get("is_output") or self.is_output self.is_output = self.data["node"].get("is_output") or self.is_output
template_dicts = { template_dicts = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
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.has_session_id = "session_id" in template_dicts
self.required_inputs = [ self.required_inputs = [
template_dicts[key]["type"] template_dicts[key]["type"] for key, value in template_dicts.items() if value["required"]
for key, value in template_dicts.items()
if value["required"]
] ]
self.optional_inputs = [ self.optional_inputs = [
template_dicts[key]["type"] template_dicts[key]["type"] for key, value in template_dicts.items() if not value["required"]
for key, value in template_dicts.items()
if not value["required"]
] ]
# Add the template_dicts[key]["input_types"] to the optional_inputs # Add the template_dicts[key]["input_types"] to the optional_inputs
self.optional_inputs.extend( 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"] template_dict = self.data["node"]["template"]
@ -292,11 +263,7 @@ class Vertex:
self.updated_raw_params = False self.updated_raw_params = False
return return
template_dict = { template_dict = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
key: value
for key, value in self.data["node"]["template"].items()
if isinstance(value, dict)
}
params = {} params = {}
for edge in self.edges: 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 # 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 # to the vertex that is the source of the edge
param_dict = template_dict[param_key]["value"] param_dict = template_dict[param_key]["value"]
params[param_key] = { params[param_key] = {key: self.graph.get_vertex(edge.source_id) for key in param_dict.keys()}
key: self.graph.get_vertex(edge.source_id)
for key in param_dict.keys()
}
else: else:
params[param_key] = self.graph.get_vertex(edge.source_id) 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 # list of dicts, so we need to convert it to a dict
# before passing it to the build method # before passing it to the build method
if isinstance(val, list): if isinstance(val, list):
params[key] = { params[key] = {k: v for item in value.get("value", []) for k, v in item.items()}
k: v
for item in value.get("value", [])
for k, v in item.items()
}
elif isinstance(val, dict): elif isinstance(val, dict):
params[key] = val params[key] = val
elif value.get("type") == "int" and val is not None: elif value.get("type") == "int" and val is not None:
@ -485,9 +445,7 @@ class Vertex:
if isinstance(self._built_object, str): if isinstance(self._built_object, str):
self._built_result = self._built_object self._built_result = self._built_object
result = await generate_result( result = await generate_result(self._built_object, inputs, self.has_external_output, session_id)
self._built_object, inputs, self.has_external_output, session_id
)
self._built_result = result self._built_result = result
async def _build_each_node_in_params_dict(self, user_id=None): 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: elif key not in self.params or self.updated_raw_params:
self.params[key] = value self.params[key] = value
async def _build_dict_and_update_params( async def _build_dict_and_update_params(self, key, nodes_dict: Dict[str, "Vertex"], user_id=None):
self, key, nodes_dict: Dict[str, "Vertex"], user_id=None
):
""" """
Iterates over a dictionary of nodes, builds each and updates the params dictionary. 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) return all(self._is_node(node) for node in value)
async def get_result( async def get_result(self, requester: Optional["Vertex"] = None, user_id=None, timeout=None) -> Any:
self, requester: Optional["Vertex"] = None, user_id=None, timeout=None
) -> Any:
# PLEASE REVIEW THIS IF STATEMENT # PLEASE REVIEW THIS IF STATEMENT
# Check if the Vertex was built already # Check if the Vertex was built already
if self._built: if self._built:
@ -568,9 +522,7 @@ class Vertex:
self._extend_params_list_with_result(key, result) self._extend_params_list_with_result(key, result)
self.params[key] = result self.params[key] = result
async def _build_list_of_nodes_and_update_params( async def _build_list_of_nodes_and_update_params(self, key, nodes: List["Vertex"], user_id=None):
self, key, nodes: List["Vertex"], user_id=None
):
""" """
Iterates over a list of nodes, builds each and updates the params dictionary. Iterates over a list of nodes, builds each and updates the params dictionary.
""" """
@ -637,9 +589,7 @@ class Vertex:
except Exception as exc: except Exception as exc:
logger.exception(exc) logger.exception(exc)
raise ValueError( raise ValueError(f"Error building node {self.display_name}: {str(exc)}") from exc
f"Error building node {self.display_name}: {str(exc)}"
) from exc
def _update_built_object_and_artifacts(self, result): def _update_built_object_and_artifacts(self, result):
""" """
@ -667,9 +617,7 @@ class Vertex:
logger.warning(message) logger.warning(message)
elif isinstance(self._built_object, (Iterator, AsyncIterator)): elif isinstance(self._built_object, (Iterator, AsyncIterator)):
if self.display_name in ["Text Output"]: if self.display_name in ["Text Output"]:
raise ValueError( raise ValueError(f"You are trying to stream to a {self.display_name}. Try using a Chat Output instead.")
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): def _reset(self, params_update: Optional[Dict[str, Any]] = None):
self._built = False self._built = False
@ -732,24 +680,16 @@ class Vertex:
return self._built_object return self._built_object
# Get the requester edge # Get the requester edge
requester_edge = next( requester_edge = next((edge for edge in self.edges if edge.target_id == requester.id), None)
(edge for edge in self.edges if edge.target_id == requester.id), None
)
# Return the result of the requester edge # Return the result of the requester edge
return ( return None if requester_edge is None else await requester_edge.get_result(source=self, target=requester)
None
if requester_edge is None
else await requester_edge.get_result(source=self, target=requester)
)
def add_edge(self, edge: "ContractEdge") -> None: def add_edge(self, edge: "ContractEdge") -> None:
if edge not in self.edges: if edge not in self.edges:
self.edges.append(edge) self.edges.append(edge)
def __repr__(self) -> str: def __repr__(self) -> str:
return ( return f"Vertex(display_name={self.display_name}, id={self.id}, data={self.data})"
f"Vertex(display_name={self.display_name}, id={self.id}, data={self.data})"
)
def __eq__(self, __o: object) -> bool: def __eq__(self, __o: object) -> bool:
try: try:
@ -770,8 +710,4 @@ class Vertex:
def _built_object_repr(self): def _built_object_repr(self):
# Add a message with an emoji, stars for sucess, # Add a message with an emoji, stars for sucess,
return ( return "Built sucessfully ✨" if self._built_object is not None else "Failed to build 😵‍💫"
"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? # show how many documents are in the list?
if not isinstance(self._built_object, UnbuiltObject): if not isinstance(self._built_object, UnbuiltObject):
avg_length = sum( avg_length = sum(len(record.text) for record in self._built_object if hasattr(record, "text")) / len(
len(record.text) self._built_object
for record in self._built_object )
if hasattr(record, "text")
) / len(self._built_object)
return f"""{self.display_name}({len(self._built_object)} records) return f"""{self.display_name}({len(self._built_object)} records)
\nAvg. Record Length (characters): {int(avg_length)} \nAvg. Record Length (characters): {int(avg_length)}
Records: {self._built_object[:3]}...""" Records: {self._built_object[:3]}..."""
@ -200,9 +198,7 @@ class TextSplitterVertex(Vertex):
# show how many documents are in the list? # show how many documents are in the list?
if not isinstance(self._built_object, UnbuiltObject): if not isinstance(self._built_object, UnbuiltObject):
avg_length = sum(len(doc.page_content) for doc in self._built_object) / len( avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(self._built_object)
self._built_object
)
return f"""{self.vertex_type}({len(self._built_object)} documents) return f"""{self.vertex_type}({len(self._built_object)} documents)
\nAvg. Document Length (characters): {int(avg_length)} \nAvg. Document Length (characters): {int(avg_length)}
\nDocuments: {self._built_object[:3]}...""" \nDocuments: {self._built_object[:3]}..."""
@ -249,27 +245,18 @@ class PromptVertex(Vertex):
user_id = kwargs.get("user_id", None) user_id = kwargs.get("user_id", None)
tools = kwargs.get("tools", []) tools = kwargs.get("tools", [])
if not self._built or force: if not self._built or force:
if ( if "input_variables" not in self.params or self.params["input_variables"] is None:
"input_variables" not in self.params
or self.params["input_variables"] is None
):
self.params["input_variables"] = [] self.params["input_variables"] = []
# Check if it is a ZeroShotPrompt and needs a tool # Check if it is a ZeroShotPrompt and needs a tool
if "ShotPrompt" in self.vertex_type: if "ShotPrompt" in self.vertex_type:
tools = ( tools = [tool_node.build(user_id=user_id) for tool_node in tools] if tools is not None else []
[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 # flatten the list of tools if it is a list of lists
# first check if it is a list # first check if it is a list
if tools and isinstance(tools, list) and isinstance(tools[0], list): if tools and isinstance(tools, list) and isinstance(tools[0], list):
tools = flatten_list(tools) tools = flatten_list(tools)
self.params["tools"] = tools self.params["tools"] = tools
prompt_params = [ prompt_params = [
key key for key, value in self.params.items() if isinstance(value, str) and key != "format_instructions"
for key, value in self.params.items()
if isinstance(value, str) and key != "format_instructions"
] ]
else: else:
prompt_params = ["template"] prompt_params = ["template"]
@ -279,20 +266,14 @@ class PromptVertex(Vertex):
prompt_text = self.params[param] prompt_text = self.params[param]
variables = extract_input_variables_from_prompt(prompt_text) variables = extract_input_variables_from_prompt(prompt_text)
self.params["input_variables"].extend(variables) self.params["input_variables"].extend(variables)
self.params["input_variables"] = list( self.params["input_variables"] = list(set(self.params["input_variables"]))
set(self.params["input_variables"])
)
elif isinstance(self.params, dict): elif isinstance(self.params, dict):
self.params.pop("input_variables", None) self.params.pop("input_variables", None)
await self._build(user_id=user_id) await self._build(user_id=user_id)
def _built_object_repr(self): def _built_object_repr(self):
if ( if not self.artifacts or self._built_object is None or not hasattr(self._built_object, "format"):
not self.artifacts
or self._built_object is None
or not hasattr(self._built_object, "format")
):
return super()._built_object_repr() return super()._built_object_repr()
elif isinstance(self._built_object, UnbuiltObject): elif isinstance(self._built_object, UnbuiltObject):
return super()._built_object_repr() return super()._built_object_repr()
@ -304,9 +285,7 @@ class PromptVertex(Vertex):
# so the prompt format doesn't break # so the prompt format doesn't break
artifacts.pop("handle_keys", None) artifacts.pop("handle_keys", None)
try: try:
if not hasattr(self._built_object, "template") and hasattr( if not hasattr(self._built_object, "template") and hasattr(self._built_object, "prompt"):
self._built_object, "prompt"
):
template = self._built_object.prompt.template template = self._built_object.prompt.template
else: else:
template = self._built_object.template template = self._built_object.template
@ -314,11 +293,7 @@ class PromptVertex(Vertex):
if value: if value:
replace_key = "{" + key + "}" replace_key = "{" + key + "}"
template = template.replace(replace_key, value) template = template.replace(replace_key, value)
return ( return template if isinstance(template, str) else f"{self.vertex_type}({template})"
template
if isinstance(template, str)
else f"{self.vertex_type}({template})"
)
except KeyError: except KeyError:
return str(self._built_object) 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. # can use them as a starting point for their own projects.
def update_projects_components_with_latest_component_versions( def update_projects_components_with_latest_component_versions(project_data, all_types_dict):
project_data, all_types_dict
):
# project data has a nodes key, which is a list of nodes # 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 # 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 # 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,
project_icon_bg_color, project_icon_bg_color,
) = get_project_data(project) ) = get_project_data(project)
project_data = update_projects_components_with_latest_component_versions( project_data = update_projects_components_with_latest_component_versions(project_data, all_types_dict)
project_data, all_types_dict
)
if project_name and project_data: if project_name and project_data:
for existing_project in get_all_flows_similar_to_project( for existing_project in get_all_flows_similar_to_project(session, project_name):
session, project_name
):
session.delete(existing_project) session.delete(existing_project)
create_new_project( create_new_project(

View file

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

View file

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

View file

@ -34,18 +34,14 @@ class UpdateBuildConfigError(Exception):
pass pass
def add_output_types( def add_output_types(frontend_node: CustomComponentFrontendNode, return_types: List[str]):
frontend_node: CustomComponentFrontendNode, return_types: List[str]
):
"""Add output types to the frontend node""" """Add output types to the frontend node"""
for return_type in return_types: for return_type in return_types:
if return_type is None: if return_type is None:
raise HTTPException( raise HTTPException(
status_code=400, status_code=400,
detail={ detail={
"error": ( "error": ("Invalid return type. Please check your code and try again."),
"Invalid return type. Please check your code and try again."
),
"traceback": traceback.format_exc(), "traceback": traceback.format_exc(),
}, },
) )
@ -77,18 +73,14 @@ def reorder_fields(frontend_node: CustomComponentFrontendNode, field_order: List
frontend_node.field_order = field_order frontend_node.field_order = field_order
def add_base_classes( def add_base_classes(frontend_node: CustomComponentFrontendNode, return_types: List[str]):
frontend_node: CustomComponentFrontendNode, return_types: List[str]
):
"""Add base classes to the frontend node""" """Add base classes to the frontend node"""
for return_type_instance in return_types: for return_type_instance in return_types:
if return_type_instance is None: if return_type_instance is None:
raise HTTPException( raise HTTPException(
status_code=400, status_code=400,
detail={ detail={
"error": ( "error": ("Invalid return type. Please check your code and try again."),
"Invalid return type. Please check your code and try again."
),
"traceback": traceback.format_exc(), "traceback": traceback.format_exc(),
}, },
) )
@ -123,9 +115,7 @@ def get_field_properties(extra_field):
# a required field is a field that does not contain # a required field is a field that does not contain
# optional in field_type # optional in field_type
# and a field that does not have a default value # and a field that does not have a default value
field_required = "optional" not in field_type.lower() and isinstance( field_required = "optional" not in field_type.lower() and isinstance(field_value, MissingDefault)
field_value, MissingDefault
)
field_value = field_value if not isinstance(field_value, MissingDefault) else None field_value = field_value if not isinstance(field_value, MissingDefault) else None
if not field_required: 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 a list, then it's a dropdown
# If options is None, then it's a list of strings # If options is None, then it's a list of strings
is_list = isinstance(field_config.get("options"), list) is_list = isinstance(field_config.get("options"), list)
field_config["is_list"] = ( field_config["is_list"] = is_list or field_config.get("list", False) or field_contains_list
is_list or field_config.get("list", False) or field_contains_list
)
if "name" in field_config: if "name" in field_config:
warnings.warn( warnings.warn("The 'name' key in field_config is used to build the object and can't be changed.")
"The 'name' key in field_config is used to build the object and can't be changed."
)
required = field_config.pop("required", field_required) required = field_config.pop("required", field_required)
placeholder = field_config.pop("placeholder", "") placeholder = field_config.pop("placeholder", "")
@ -217,9 +203,7 @@ def add_extra_fields(frontend_node, field_config, function_args):
]: ]:
continue continue
field_name, field_type, field_value, field_required = get_field_properties( field_name, field_type, field_value, field_required = get_field_properties(extra_field)
extra_field
)
config = _field_config.pop(field_name, {}) config = _field_config.pop(field_name, {})
frontend_node = add_new_custom_field( frontend_node = add_new_custom_field(
frontend_node, frontend_node,
@ -229,17 +213,13 @@ def add_extra_fields(frontend_node, field_config, function_args):
field_required, field_required,
config, config,
) )
if "kwargs" in function_args_names and not all( if "kwargs" in function_args_names and not all(key in function_args_names for key in field_config.keys()):
key in function_args_names for key in field_config.keys()
):
for field_name, field_config in _field_config.copy().items(): for field_name, field_config in _field_config.copy().items():
if "name" not in field_config or field_name == "code": if "name" not in field_config or field_name == "code":
continue continue
config = _field_config.get(field_name, {}) config = _field_config.get(field_name, {})
config = config.model_dump() if isinstance(config, BaseModel) else config config = config.model_dump() if isinstance(config, BaseModel) else config
field_name, field_type, field_value, field_required = get_field_properties( field_name, field_type, field_value, field_required = get_field_properties(extra_field=config)
extra_field=config
)
frontend_node = add_new_custom_field( frontend_node = add_new_custom_field(
frontend_node, frontend_node,
field_name, field_name,
@ -277,9 +257,7 @@ def run_build_config(
raise HTTPException( raise HTTPException(
status_code=400, status_code=400,
detail={ detail={
"error": ( "error": ("Invalid type convertion. Please check your code and try again."),
"Invalid type convertion. Please check your code and try again."
),
"traceback": traceback.format_exc(), "traceback": traceback.format_exc(),
}, },
) from exc ) from exc
@ -329,7 +307,6 @@ def run_build_config(
return build_config, custom_instance return build_config, custom_instance
except Exception as exc: except Exception as exc:
logger.error(f"Error while building field config: {str(exc)}") logger.error(f"Error while building field config: {str(exc)}")
if hasattr(exc, "detail") and "traceback" in exc.detail: if hasattr(exc, "detail") and "traceback" in exc.detail:
logger.error(exc.detail["traceback"]) 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): def add_code_field(frontend_node: CustomComponentFrontendNode, raw_code, field_config):
code_field = TemplateField( code_field = TemplateField(
dynamic=True, dynamic=True,
required=True, required=True,
@ -397,16 +373,10 @@ def build_custom_component_template(
add_extra_fields(frontend_node, field_config, entrypoint_args) add_extra_fields(frontend_node, field_config, entrypoint_args)
frontend_node = add_code_field( frontend_node = add_code_field(frontend_node, custom_component.code, field_config.get("code", {}))
frontend_node, custom_component.code, field_config.get("code", {})
)
add_base_classes( add_base_classes(frontend_node, custom_component.get_function_entrypoint_return_type)
frontend_node, custom_component.get_function_entrypoint_return_type add_output_types(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()) reorder_fields(frontend_node, custom_instance._get_field_order())
@ -417,9 +387,7 @@ def build_custom_component_template(
raise HTTPException( raise HTTPException(
status_code=400, status_code=400,
detail={ detail={
"error": ( "error": (f"Something went wrong while building the custom component. Hints: {str(exc)}"),
f"Something went wrong while building the custom component. Hints: {str(exc)}"
),
"traceback": traceback.format_exc(), "traceback": traceback.format_exc(),
}, },
) from 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) custom_component_dict = build_custom_component_list_from_path(path_str)
if custom_component_dict: if custom_component_dict:
category = next(iter(custom_component_dict)) category = next(iter(custom_component_dict))
logger.info( logger.info(f"Loading {len(custom_component_dict[category])} component(s) from category {category}")
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 = merge_nested_dicts_with_renaming(
custom_components_from_file, custom_component_dict custom_components_from_file, custom_component_dict
) )
@ -492,9 +458,7 @@ def update_field_dict(
build_config = dd_build_config build_config = dd_build_config
except Exception as exc: except Exception as exc:
logger.error(f"Error while running update_build_config: {str(exc)}") logger.error(f"Error while running update_build_config: {str(exc)}")
raise UpdateBuildConfigError( raise UpdateBuildConfigError(f"Error while running update_build_config: {str(exc)}") from exc
f"Error while running update_build_config: {str(exc)}"
) from exc
# Let's check if "range_spec" is a RangeSpec object # Let's check if "range_spec" is a RangeSpec object
if "rangeSpec" in field_dict and isinstance(field_dict["rangeSpec"], RangeSpec): 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) return class_object(**params)
async def instantiate_custom_component( async def instantiate_custom_component(node_type, class_object, params, user_id, vertex):
node_type, class_object, params, user_id, vertex
):
params_copy = params.copy() params_copy = params.copy()
class_object: Type["CustomComponent"] = eval_custom_component_code( class_object: Type["CustomComponent"] = eval_custom_component_code(params_copy.pop("code"))
params_copy.pop("code")
)
custom_component: "CustomComponent" = class_object( custom_component: "CustomComponent" = class_object(
user_id=user_id, user_id=user_id,
parameters=params_copy, 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 # I want to catch a specific attribute error that happens
# when the object does not have a cursor attribute # when the object does not have a cursor attribute
except Exception as exc: except Exception as exc:
if "object has no attribute 'cursor'" in str( if "object has no attribute 'cursor'" in str(exc) or 'object has no field "conn"' in str(exc):
exc
) or 'object has no field "conn"' in str(exc):
raise AttributeError( raise AttributeError(
( (
"Failed to build connection to database." "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): if class_method := getattr(class_object, method, None):
agent = class_method(**params) agent = class_method(**params)
tools = params.get("tools", []) tools = params.get("tools", [])
return AgentExecutor.from_agent_and_tools( return AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, handle_parsing_errors=True)
agent=agent, tools=tools, handle_parsing_errors=True
)
return load_agent_executor(class_object, params) return load_agent_executor(class_object, params)
@ -330,11 +322,7 @@ def instantiate_embedding(node_type, class_object, params: Dict):
try: try:
return class_object(**params) return class_object(**params)
except ValidationError: except ValidationError:
params = { params = {key: value for key, value in params.items() if key in class_object.model_fields}
key: value
for key, value in params.items()
if key in class_object.model_fields
}
return class_object(**params) return class_object(**params)
@ -346,9 +334,7 @@ def instantiate_vectorstore(class_object: Type[VectorStore], params: Dict):
if "texts" in params: if "texts" in params:
params["documents"] = params.pop("texts") params["documents"] = params.pop("texts")
if "documents" in params: if "documents" in params:
params["documents"] = [ params["documents"] = [doc for doc in params["documents"] if isinstance(doc, Document)]
doc for doc in params["documents"] if isinstance(doc, Document)
]
if initializer := vecstore_initializer.get(class_object.__name__): if initializer := vecstore_initializer.get(class_object.__name__):
vecstore = initializer(class_object, params) vecstore = initializer(class_object, params)
else: else:
@ -363,9 +349,7 @@ def instantiate_vectorstore(class_object: Type[VectorStore], params: Dict):
return vecstore return vecstore
def instantiate_documentloader( def instantiate_documentloader(node_type: str, class_object: Type[BaseLoader], params: Dict):
node_type: str, class_object: Type[BaseLoader], params: Dict
):
if "file_filter" in params: if "file_filter" in params:
# file_filter will be a string but we need a function # file_filter will be a string but we need a function
# that will be used to filter the files using file_filter # 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 # in x and if it is, we will return True
file_filter = params.pop("file_filter") file_filter = params.pop("file_filter")
extensions = file_filter.split(",") extensions = file_filter.split(",")
params["file_filter"] = lambda x: any( params["file_filter"] = lambda x: any(extension.strip() in x for extension in extensions)
extension.strip() in x for extension in extensions
)
metadata = params.pop("metadata", None) metadata = params.pop("metadata", None)
if metadata and isinstance(metadata, str): if metadata and isinstance(metadata, str):
try: try:
metadata = orjson.loads(metadata) metadata = orjson.loads(metadata)
except json.JSONDecodeError as exc: except json.JSONDecodeError as exc:
raise ValueError( raise ValueError("The metadata you provided is not a valid JSON string.") from exc
"The metadata you provided is not a valid JSON string."
) from exc
if node_type == "WebBaseLoader": if node_type == "WebBaseLoader":
if web_path := params.pop("web_path", None): if web_path := params.pop("web_path", None):
@ -417,9 +397,7 @@ def instantiate_textsplitter(
"Try changing the chunk_size of the Text Splitter." "Try changing the chunk_size of the Text Splitter."
) from exc ) from exc
if ( if ("separator_type" in params and params["separator_type"] == "Text") or "separator_type" not in params:
"separator_type" in params and params["separator_type"] == "Text"
) or "separator_type" not in params:
params.pop("separator_type", None) params.pop("separator_type", None)
# separators might come in as an escaped string like \\n # separators might come in as an escaped string like \\n
# so we need to convert it to a string # so we need to convert it to a string
@ -427,9 +405,7 @@ def instantiate_textsplitter(
if isinstance(params["separators"], str): if isinstance(params["separators"], str):
params["separators"] = unescape_string(params["separators"]) params["separators"] = unescape_string(params["separators"])
elif isinstance(params["separators"], list): elif isinstance(params["separators"], list):
params["separators"] = [ params["separators"] = [unescape_string(separator) for separator in params["separators"]]
unescape_string(separator) for separator in params["separators"]
]
text_splitter = class_object(**params) text_splitter = class_object(**params)
else: else:
from langchain.text_splitter import Language from langchain.text_splitter import Language
@ -456,8 +432,7 @@ def replace_zero_shot_prompt_with_prompt_template(nodes):
tools = [ tools = [
tool tool
for tool in nodes for tool in nodes
if tool["type"] != "chatOutputNode" if tool["type"] != "chatOutputNode" and "Tool" in tool["data"]["node"]["base_classes"]
and "Tool" in tool["data"]["node"]["base_classes"]
] ]
node["data"] = build_prompt_template(prompt=node["data"], tools=tools) node["data"] = build_prompt_template(prompt=node["data"], tools=tools)
break 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 # agent has hidden args for memory. might need to be support
# memory = params["memory"] # memory = params["memory"]
# if allowed_tools is not a list or set, make it a list # if allowed_tools is not a list or set, make it a list
if not isinstance(allowed_tools, (list, set)) and isinstance( if not isinstance(allowed_tools, (list, set)) and isinstance(allowed_tools, BaseTool):
allowed_tools, BaseTool
):
allowed_tools = [allowed_tools] allowed_tools = [allowed_tools]
tool_names = [tool.name for tool in allowed_tools] tool_names = [tool.name for tool in allowed_tools]
# Agent class requires an output_parser but Agent classes # 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"] format_instructions = prompt["node"]["template"]["format_instructions"]["value"]
tool_strings = "\n".join( 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]) tool_names = ", ".join([tool["data"]["node"]["name"] for tool in tools])
format_instructions = format_instructions.format(tool_names=tool_names) 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): def get_all_types_dict(components_paths):
"""Get all types dictionary combining native and custom components.""" """Get all types dictionary combining native and custom components."""
native_components = build_langchain_types_dict() native_components = build_langchain_types_dict()
custom_components_from_file = build_custom_components( custom_components_from_file = build_custom_components(components_paths=components_paths)
components_paths=components_paths return merge_nested_dicts_with_renaming(native_components, custom_components_from_file)
)
return merge_nested_dicts_with_renaming(
native_components, custom_components_from_file
)
def get_all_components(components_paths, as_dict=False): 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 from langflow.processing.process import process_tweaks
def load_flow_from_json( def load_flow_from_json(flow: Union[Path, str, dict], tweaks: Optional[dict] = None) -> Graph:
flow: Union[Path, str, dict], tweaks: Optional[dict] = None
) -> Graph:
""" """
Load flow from a JSON file or a JSON object. Load flow from a JSON file or a JSON object.
@ -25,9 +23,7 @@ def load_flow_from_json(
elif isinstance(flow, dict): elif isinstance(flow, dict):
flow_graph = flow flow_graph = flow
else: else:
raise TypeError( raise TypeError("Input must be either a file path (str) or a JSON object (dict)")
"Input must be either a file path (str) or a JSON object (dict)"
)
graph_data = flow_graph["data"] graph_data = flow_graph["data"]
if tweaks is not None: 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"): elif isinstance(inputs, dict) and hasattr(runnable, "ainvoke"):
result = await runnable.ainvoke(inputs) result = await runnable.ainvoke(inputs)
else: else:
raise ValueError( raise ValueError(f"Runnable {runnable} does not support inputs of type {type(inputs)}")
f"Runnable {runnable} does not support inputs of type {type(inputs)}"
)
# Check if the result is a list of AIMessages # Check if the result is a list of AIMessages
if isinstance(result, list) and all(isinstance(r, AIMessage) for r in result): if isinstance(result, list) and all(isinstance(r, AIMessage) for r in result):
result = [r.content 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 return result
async def process_inputs_dict( async def process_inputs_dict(built_object: Union[Chain, VectorStore, Runnable], inputs: dict):
built_object: Union[Chain, VectorStore, Runnable], inputs: dict
):
if isinstance(built_object, Chain): if isinstance(built_object, Chain):
if inputs is None: if inputs is None:
raise ValueError("Inputs must be provided for a Chain") 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) return await process_runnable(built_object, inputs)
async def generate_result( async def generate_result(built_object: Union[Chain, VectorStore, Runnable], inputs: Union[dict, List[dict]]):
built_object: Union[Chain, VectorStore, Runnable], inputs: Union[dict, List[dict]]
):
if isinstance(inputs, dict): if isinstance(inputs, dict):
result = await process_inputs_dict(built_object, inputs) result = await process_inputs_dict(built_object, inputs)
elif isinstance(inputs, List) and isinstance(built_object, Runnable): elif isinstance(inputs, List) and isinstance(built_object, Runnable):
@ -218,9 +212,7 @@ async def run_graph(
else: else:
graph_data = graph._graph_data graph_data = graph._graph_data
if session_id is None and session_service is not None: if session_id is None and session_service is not None:
session_id = session_service.generate_key( session_id = session_service.generate_key(session_id=flow_id, data_graph=graph_data)
session_id=flow_id, data_graph=graph_data
)
if inputs is None: if inputs is None:
inputs = [{}] inputs = [{}]
@ -244,9 +236,7 @@ def validate_input(
nodes = graph_data.get("data", {}).get("nodes") or graph_data.get("nodes") nodes = graph_data.get("data", {}).get("nodes") or graph_data.get("nodes")
if not isinstance(nodes, list): if not isinstance(nodes, list):
raise ValueError( raise ValueError("graph_data should contain a list of nodes under 'data' key or directly under 'nodes' key")
"graph_data should contain a list of nodes under 'data' key or directly under 'nodes' key"
)
return nodes 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") template_data = node.get("data", {}).get("node", {}).get("template")
if not isinstance(template_data, dict): if not isinstance(template_data, dict):
logger.warning( logger.warning(f"Template data for node {node.get('id')} should be a dictionary")
f"Template data for node {node.get('id')} should be a dictionary"
)
return return
for tweak_name, tweak_value in node_tweaks.items(): 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 vertex.params[tweak_name] = tweak_value
def process_tweaks( def process_tweaks(graph_data: Dict[str, Any], tweaks: Union["Tweaks", Dict[str, Dict[str, Any]]]) -> Dict[str, Any]:
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. 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): if node_tweaks := tweaks.get(node_id):
apply_tweaks_on_vertex(vertex, node_tweaks) apply_tweaks_on_vertex(vertex, node_tweaks)
else: else:
logger.warning( logger.warning("Each node should be a Vertex with an 'id' attribute of type str")
"Each node should be a Vertex with an 'id' attribute of type str"
)
return graph return graph

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -73,9 +73,7 @@ class TemplateField(BaseModel):
refresh_button_text: Optional[str] = None refresh_button_text: Optional[str] = None
"""Specifies the text for the refresh button. Defaults to None.""" """Specifies the text for the refresh button. Defaults to None."""
range_spec: Optional[RangeSpec] = Field( range_spec: Optional[RangeSpec] = Field(default=None, serialization_alias="rangeSpec")
default=None, serialization_alias="rangeSpec"
)
"""Range specification for the field. Defaults to None.""" """Range specification for the field. Defaults to None."""
title_case: bool = False title_case: bool = False
@ -124,10 +122,6 @@ class TemplateField(BaseModel):
if not isinstance(value, list): if not isinstance(value, list):
raise ValueError("file_types must be a list") raise ValueError("file_types must be a list")
return [ 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 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) return re.sub(r"\x1b\[[0-9;]*[a-zA-Z]", "", text)
def build_template_from_function( def build_template_from_function(name: str, type_to_loader_dict: Dict, add_function: bool = False):
name: str, type_to_loader_dict: Dict, add_function: bool = False classes = [item.__annotations__["return"].__name__ for item in type_to_loader_dict.values()]
):
classes = [
item.__annotations__["return"].__name__ for item in type_to_loader_dict.values()
]
# Raise error if name is not in chains # Raise error if name is not in chains
if name not in classes: if name not in classes:
@ -46,10 +42,8 @@ def build_template_from_function(
for name_, value_ in value.__repr_args__(): for name_, value_ in value.__repr_args__():
if name_ == "default_factory": if name_ == "default_factory":
try: try:
variables[class_field_items]["default"] = ( variables[class_field_items]["default"] = get_default_factory(
get_default_factory( module=_class.__base__.__module__, function=value_
module=_class.__base__.__module__, function=value_
)
) )
except Exception: except Exception:
variables[class_field_items]["default"] = None 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][name_] = value_
variables[class_field_items]["placeholder"] = ( variables[class_field_items]["placeholder"] = (
docs.params[class_field_items] docs.params[class_field_items] if class_field_items in docs.params else ""
if class_field_items in docs.params
else ""
) )
# Adding function to base classes to allow # Adding function to base classes to allow
# the output to be a function # the output to be a function
@ -74,9 +66,7 @@ def build_template_from_function(
} }
def build_template_from_class( def build_template_from_class(name: str, type_to_cls_dict: Dict, add_function: bool = False):
name: str, type_to_cls_dict: Dict, add_function: bool = False
):
classes = [item.__name__ for item in type_to_cls_dict.values()] classes = [item.__name__ for item in type_to_cls_dict.values()]
# Raise error if name is not in chains # Raise error if name is not in chains
@ -100,11 +90,9 @@ def build_template_from_class(
for name_, value_ in value.__repr_args__(): for name_, value_ in value.__repr_args__():
if name_ == "default_factory": if name_ == "default_factory":
try: try:
variables[class_field_items]["default"] = ( variables[class_field_items]["default"] = get_default_factory(
get_default_factory( module=_class.__base__.__module__,
module=_class.__base__.__module__, function=value_,
function=value_,
)
) )
except Exception: except Exception:
variables[class_field_items]["default"] = None 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][name_] = value_
variables[class_field_items]["placeholder"] = ( variables[class_field_items]["placeholder"] = (
docs.params[class_field_items] docs.params[class_field_items] if class_field_items in docs.params else ""
if class_field_items in docs.params
else ""
) )
base_classes = get_base_classes(_class) base_classes = get_base_classes(_class)
# Adding function to base classes to allow # Adding function to base classes to allow
@ -146,9 +132,7 @@ def build_template_from_method(
# Check if the method exists in this class # Check if the method exists in this class
if not hasattr(_class, method_name): if not hasattr(_class, method_name):
raise ValueError( raise ValueError(f"Method {method_name} not found in class {class_name}")
f"Method {method_name} not found in class {class_name}"
)
# Get the method # Get the method
method = getattr(_class, method_name) method = getattr(_class, method_name)
@ -167,14 +151,8 @@ def build_template_from_method(
"_type": _type, "_type": _type,
**{ **{
name: { name: {
"default": ( "default": (param.default if param.default != param.empty else None),
param.default if param.default != param.empty else None "type": (param.annotation if param.annotation != param.empty else None),
),
"type": (
param.annotation
if param.annotation != param.empty
else None
),
"required": param.default == param.empty, "required": param.default == param.empty,
} }
for name, param in params.items() for name, param in params.items()
@ -261,9 +239,7 @@ def sync_to_async(func):
return async_wrapper return async_wrapper
def format_dict( def format_dict(dictionary: Dict[str, Any], class_name: Optional[str] = None) -> Dict[str, Any]:
dictionary: Dict[str, Any], class_name: Optional[str] = None
) -> Dict[str, Any]:
""" """
Formats a dictionary by removing certain keys and modifying the Formats a dictionary by removing certain keys and modifying the
values of other keys. values of other keys.
@ -349,9 +325,7 @@ def check_list_type(_type: str, value: Dict[str, Any]) -> str:
The modified type string. The modified type string.
""" """
if any(list_type in _type for list_type in ["List", "Sequence", "Set"]): if any(list_type in _type for list_type in ["List", "Sequence", "Set"]):
_type = ( _type = _type.replace("List[", "").replace("Sequence[", "").replace("Set[", "")[:-1]
_type.replace("List[", "").replace("Sequence[", "").replace("Set[", "")[:-1]
)
value["list"] = True value["list"] = True
else: else:
value["list"] = False value["list"] = False
@ -454,9 +428,7 @@ def set_headers_value(value: Dict[str, Any]) -> None:
value["value"] = """{"Authorization": "Bearer <token>"}""" value["value"] = """{"Authorization": "Bearer <token>"}"""
def add_options_to_field( def add_options_to_field(value: Dict[str, Any], class_name: Optional[str], key: str) -> None:
value: Dict[str, Any], class_name: Optional[str], key: str
) -> None:
""" """
Adds options to the field based on the class name and key. 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 iconColor = nodeColors[types[data.type]];
const iconName = const iconName =
iconElement || (data.node?.flow ? "group_components" : name); 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) { if (iconElement && isEmoji) {
return nodeIconFragment(iconElement); return nodeIconFragment(iconElement);
} else { } else {
@ -303,7 +304,7 @@ export default function GenericNode({
numberOfHandles={handles} numberOfHandles={handles}
showNode={showNode} showNode={showNode}
openAdvancedModal={false} openAdvancedModal={false}
onCloseAdvancedModal={() => { }} onCloseAdvancedModal={() => {}}
selected={selected} selected={selected}
></NodeToolbarComponent> ></NodeToolbarComponent>
</NodeToolbar> </NodeToolbar>
@ -426,33 +427,33 @@ export default function GenericNode({
data={data} data={data}
color={ color={
data.node?.template[templateField].input_types && data.node?.template[templateField].input_types &&
data.node?.template[templateField].input_types! data.node?.template[templateField].input_types!
.length > 0 .length > 0
? nodeColors[ ? nodeColors[
data.node?.template[templateField] data.node?.template[templateField]
.input_types![ .input_types![
data.node?.template[templateField] data.node?.template[templateField]
.input_types!.length - 1 .input_types!.length - 1
] ]
] ?? ] ??
nodeColors[ nodeColors[
types[ types[
data.node?.template[templateField] data.node?.template[templateField]
.input_types![ .input_types![
data.node?.template[templateField] data.node?.template[templateField]
.input_types!.length - 1 .input_types!.length - 1
] ]
] ]
] ]
: nodeColors[ : nodeColors[
data.node?.template[templateField].type! data.node?.template[templateField].type!
] ?? ] ??
nodeColors[ nodeColors[
types[ types[
data.node?.template[templateField].type! data.node?.template[templateField].type!
] ]
] ?? ] ??
nodeColors.unknown nodeColors.unknown
} }
title={getFieldTitle( title={getFieldTitle(
data.node?.template!, data.node?.template!,
@ -496,7 +497,7 @@ export default function GenericNode({
color={nodeColors[types[data.type]] ?? nodeColors.unknown} color={nodeColors[types[data.type]] ?? nodeColors.unknown}
title={ title={
data.node?.output_types && data.node?.output_types &&
data.node.output_types.length > 0 data.node.output_types.length > 0
? data.node.output_types.join(" | ") ? data.node.output_types.join(" | ")
: data.type : data.type
} }
@ -515,59 +516,62 @@ export default function GenericNode({
</div> </div>
{showNode && ( {showNode && (
<ShadTooltip <ShadTooltip
content={ content={
buildStatus === BuildStatus.BUILDING ? ( buildStatus === BuildStatus.BUILDING ? (
<span> {STATUS_BUILDING} </span> <span> {STATUS_BUILDING} </span>
) : !validationStatus ? ( ) : !validationStatus ? (
<span className="flex">{STATUS_BUILD}</span> <span className="flex">{STATUS_BUILD}</span>
) : ( ) : (
<div className="max-h-100 p-2"> <div className="max-h-100 p-2">
<div> <div>
{lastRunTime && ( {lastRunTime && (
<div className="justify-left flex text-muted-foreground font-normal"> <div className="justify-left flex font-normal text-muted-foreground">
<div>{RUN_TIMESTAMP_PREFIX}</div> <div>{RUN_TIMESTAMP_PREFIX}</div>
<div className="ml-1 text-status-blue"> <div className="ml-1 text-status-blue">
{lastRunTime} {lastRunTime}
</div>
</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>
</div> <hr />
<div className="justify-left flex text-muted-foreground font-normal"> <span className="mb-2 mt-2 flex justify-center font-semibold text-muted-foreground">
<div>Duration:</div> Output
<div className="ml-1 text-status-blue mb-3"> </span>
{validationStatus?.data.duration} <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>
</div> </div>
<hr /> )
<span className="flex justify-center text-muted-foreground mt-2 mb-2 font-semibold"> }
Output side="bottom"
</span> >
<div className="max-h-96 overflow-auto custom-scroll font-normal"> <Button
{validationString.split("\n").map((line, index) => ( onClick={() => {
<div className="font-normal" key={index}>{line}</div> if (buildStatus === BuildStatus.BUILDING || isBuilding)
))} return;
</div> setValidationStatus(null);
</div> buildFlow({ stopNodeId: data.id });
) }}
} variant="secondary"
side="bottom" className={"group h-9 px-1.5"}
> >
<Button onClick={() => { <div>
if (buildStatus === BuildStatus.BUILDING || isBuilding) <div className="generic-node-status-position flex items-center justify-center">
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)} {renderIconStatus(buildStatus, validationStatus)}
</div> </div>
</div> </div>
</Button> </Button>
</ShadTooltip> </ShadTooltip>
)} )}
</div> </div>
</div> </div>
@ -642,7 +646,7 @@ export default function GenericNode({
}} }}
> >
{(data.node?.description === "" || !data.node?.description) && {(data.node?.description === "" || !data.node?.description) &&
nameEditable nameEditable
? "Double Click to Edit Description" ? "Double Click to Edit Description"
: data.node?.description} : data.node?.description}
</div> </div>
@ -655,7 +659,7 @@ export default function GenericNode({
.map((templateField: string, idx) => ( .map((templateField: string, idx) => (
<div key={idx}> <div key={idx}>
{data.node!.template[templateField].show && {data.node!.template[templateField].show &&
!data.node!.template[templateField].advanced ? ( !data.node!.template[templateField].advanced ? (
<ParameterComponent <ParameterComponent
index={idx.toString()} index={idx.toString()}
key={scapedJSONStringfy({ key={scapedJSONStringfy({
@ -669,30 +673,30 @@ export default function GenericNode({
data={data} data={data}
color={ color={
data.node?.template[templateField].input_types && data.node?.template[templateField].input_types &&
data.node?.template[templateField].input_types! data.node?.template[templateField].input_types!
.length > 0 .length > 0
? nodeColors[ ? nodeColors[
data.node?.template[templateField].input_types![ data.node?.template[templateField].input_types![
data.node?.template[templateField] data.node?.template[templateField]
.input_types!.length - 1 .input_types!.length - 1
] ]
] ?? ] ??
nodeColors[ nodeColors[
types[ types[
data.node?.template[templateField] data.node?.template[templateField]
.input_types![ .input_types![
data.node?.template[templateField] data.node?.template[templateField]
.input_types!.length - 1 .input_types!.length - 1
] ]
] ]
] ]
: nodeColors[ : nodeColors[
data.node?.template[templateField].type! data.node?.template[templateField].type!
] ?? ] ??
nodeColors[ nodeColors[
types[data.node?.template[templateField].type!] types[data.node?.template[templateField].type!]
] ?? ] ??
nodeColors.unknown nodeColors.unknown
} }
title={getFieldTitle( title={getFieldTitle(
data.node?.template!, data.node?.template!,
@ -744,9 +748,9 @@ export default function GenericNode({
data={data} data={data}
color={ color={
(data.node?.output_types && (data.node?.output_types &&
data.node.output_types.length > 0 data.node.output_types.length > 0
? nodeColors[data.node.output_types[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 : nodeColors[types[data.type]]) ?? nodeColors.unknown
} }
title={ title={

View file

@ -20,7 +20,12 @@ import { Badge } from "../ui/badge";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs"; 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; children: JSX.Element;
open: boolean; open: boolean;
setOpen: (open: boolean) => void; 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 { useNavigate } from "react-router-dom";
import IconComponent from "../genericIconComponent"; import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { cn } from "../../utils/utils"; import { Card, CardContent, CardDescription, CardTitle } from "../ui/card";
export default function NewFlowCardComponent() { export default function NewFlowCardComponent() {
const addFlow = useFlowsManagerStore((state) => state.addFlow); const addFlow = useFlowsManagerStore((state) => state.addFlow);
const navigate = useNavigate(); const navigate = useNavigate();
return ( return (
<Card onClick={() => { <Card
addFlow(true).then((id) => { onClick={() => {
navigate("/flow/" + id); 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"> className="h-64 w-80 cursor-pointer bg-background pt-4"
>
</div> <CardContent className="h-full w-full">
</CardContent> <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>
<CardDescription className="px-6 pb-4"> </CardContent>
<CardTitle className="text-primary text-lg">Blank Flow</CardTitle> <CardDescription className="px-6 pb-4">
</CardDescription> <CardTitle className="text-lg text-primary">Blank Flow</CardTitle>
</Card> </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 { 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 ForwardedIconComponent from "../genericIconComponent";
import { Separator } from "../ui/separator"; 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 { export default function FlowToolbar({ flow }: ChatType): JSX.Element {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@ -51,7 +50,7 @@ export default function FlowToolbar({ flow }: ChatType): JSX.Element {
<button <button
disabled={!hasApiKey || !validApiKey || !hasStore} disabled={!hasApiKey || !validApiKey || !hasStore}
className={classNames( 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 !hasApiKey || !validApiKey || !hasStore
? " button-disable text-muted-foreground " ? " button-disable text-muted-foreground "
: "" : ""
@ -76,67 +75,83 @@ export default function FlowToolbar({ flow }: ChatType): JSX.Element {
return ( return (
<> <>
<Transition <Transition
show={true} show={true}
appear={true} appear={true}
enter="transition ease-out duration-300" enter="transition ease-out duration-300"
enterFrom="translate-y-96" enterFrom="translate-y-96"
enterTo="translate-y-0" enterTo="translate-y-0"
leave="transition ease-in duration-300" leave="transition ease-in duration-300"
leaveFrom="translate-y-0" leaveFrom="translate-y-0"
leaveTo="translate-y-96" leaveTo="translate-y-96"
> >
<div <div
className={ 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" "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">
<div className="flex gap-1 text-medium-indigo rounded-sm transition-all w-full h-full"> <div className="flex h-full w-full gap-1 rounded-sm text-medium-indigo transition-all">
{hasIO ? ( {hasIO ? (
<IOView open={open} setOpen={setOpen} disable={!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"> <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 <ForwardedIconComponent
name="Zap" name="Zap"
className={"message-button-icon h-5 w-5 transition-all"} className={"message-button-icon h-5 w-5 transition-all"}
/> />
Run 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> </div>
</IOView> <div>
) : ( <Separator orientation="vertical" />
<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`}> </div>
<ForwardedIconComponent <div className="flex cursor-pointer items-center gap-2">
name="Zap" {currentFlow && currentFlow.data && (
className={"message-button-icon h-5 w-5 transition-all fill-muted-foreground stroke-muted-foreground"} <ApiModal flow={currentFlow}>
/> <div
Run className={classNames(
</div> "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"
)} )}
</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")}>
<ForwardedIconComponent <ForwardedIconComponent
name="Code2" name="Code2"
className={" h-5 w-5"} className={" h-5 w-5"}
/> />
API API
</div> </div>
</ApiModal> </ApiModal>
)} )}
</div> </div>
<div> <div>
<Separator orientation="vertical" /> <Separator orientation="vertical" />
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className={`side-bar-button ${!hasApiKey || !validApiKey || !hasStore ? " cursor-not-allowed" : " cursor-pointer"}`}>{ModalMemo}</div> <div
className={`side-bar-button ${
!hasApiKey || !validApiKey || !hasStore
? " cursor-not-allowed"
: " cursor-pointer"
}`}
>
{ModalMemo}
</div>
</div>
</div> </div>
</div> </div>
</div> </Transition>
</Transition>
</> </>
); );
} }

View file

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

View file

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

View file

@ -1,60 +1,107 @@
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
/// <reference types="vite-plugin-svgr/client" /> /// <reference types="vite-plugin-svgr/client" />
//@ts-ignore //@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 //@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 //@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 //@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 //@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 useFlowsManagerStore from "../../stores/flowsManagerStore";
import { FlowType } from "../../types/flow" import { FlowType } from "../../types/flow";
import { updateIds } from "../../utils/reactflowUtils"; import { updateIds } from "../../utils/reactflowUtils";
import ShadTooltip from "../ShadTooltipComponent" import { Card, CardContent, CardDescription, CardTitle } from "../ui/card";
import { Card, CardContent, CardDescription, CardFooter, CardTitle } from "../ui/card"
export default function UndrawCardComponent({ export default function UndrawCardComponent({ flow }: { flow: FlowType }) {
flow const addFlow = useFlowsManagerStore((state) => state.addFlow);
}: { flow: FlowType }) { const navigate = useNavigate();
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const navigate = useNavigate();
function selectImage() { function selectImage() {
switch (flow.name) { switch (flow.name) {
case "Data Ingestion": case "Data Ingestion":
return <TransferFiles style={{ width: '80%', height: '80%', preserveAspectRatio: 'xMidYMid meet' }} /> return (
case "Basic Prompting": <TransferFiles
return <BasicPrompt style={{ width: '80%', height: '80%', preserveAspectRatio: 'xMidYMid meet' }} /> style={{
case "Chat with memory": width: "80%",
return <ChatWithHistory style={{ width: '70%', height: '70%', preserveAspectRatio: 'xMidYMid meet' }} /> height: "80%",
case "API requests": preserveAspectRatio: "xMidYMid meet",
return <APIRequest style={{ width: '70%', height: '70%', preserveAspectRatio: 'xMidYMid meet' }} /> }}
case "Assistant": />
return <Assistant style={{ width: '80%', height: '80%', preserveAspectRatio: 'xMidYMid meet' }} /> );
default: case "Basic Prompting":
return <TransferFiles style={{ width: '80%', height: '80%', preserveAspectRatio: 'xMidYMid meet' }} /> 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 ( return (
<Card onClick={() => { <Card
updateIds(flow.data!); onClick={() => {
addFlow(true, flow).then((id) => { updateIds(flow.data!);
navigate("/flow/" + id); 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"> className="h-64 w-80 cursor-pointer bg-background pt-4"
{selectImage()} >
</div> <CardContent className="h-full w-full">
</CardContent> <div className="flex h-full w-full flex-col items-center justify-center rounded-md bg-muted p-1 align-middle">
<CardDescription className="px-6 pb-4"> {selectImage()}
<CardTitle className="text-primary text-lg">{flow.name}</CardTitle> </div>
</CardDescription> </CardContent>
</Card> <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 Assistant
*/ */
export const EXAMPLES_MOCK:FlowType[] = [ export const EXAMPLES_MOCK: FlowType[] = [
{ {
name: "Working with data", name: "Working with data",
id: "Working with data Description", id: "Working with data Description",
data: { data: {
nodes: [], nodes: [],
edges: [], 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.", description: "This flow represents the first process in our application.",
folder: STARTER_FOLDER_NAME, folder: STARTER_FOLDER_NAME,
@ -769,7 +769,7 @@ export const EXAMPLES_MOCK:FlowType[] = [
data: { data: {
nodes: [], nodes: [],
edges: [], 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.", description: "This flow represents the first process in our application.",
folder: STARTER_FOLDER_NAME, folder: STARTER_FOLDER_NAME,
@ -781,7 +781,7 @@ export const EXAMPLES_MOCK:FlowType[] = [
data: { data: {
nodes: [], nodes: [],
edges: [], 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.", description: "This flow represents the first process in our application.",
folder: STARTER_FOLDER_NAME, folder: STARTER_FOLDER_NAME,
@ -793,7 +793,7 @@ export const EXAMPLES_MOCK:FlowType[] = [
data: { data: {
nodes: [], nodes: [],
edges: [], 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.", description: "This flow represents the first process in our application.",
folder: STARTER_FOLDER_NAME, folder: STARTER_FOLDER_NAME,
@ -805,7 +805,7 @@ export const EXAMPLES_MOCK:FlowType[] = [
data: { data: {
nodes: [], nodes: [],
edges: [], 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.", description: "This flow represents the first process in our application.",
folder: STARTER_FOLDER_NAME, folder: STARTER_FOLDER_NAME,

View file

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

View file

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

View file

@ -13,6 +13,7 @@ import {
import ConfirmationModal from "../../../../modals/ConfirmationModal"; import ConfirmationModal from "../../../../modals/ConfirmationModal";
import EditNodeModal from "../../../../modals/EditNodeModal"; import EditNodeModal from "../../../../modals/EditNodeModal";
import ShareModal from "../../../../modals/shareModal"; import ShareModal from "../../../../modals/shareModal";
import useAlertStore from "../../../../stores/alertStore";
import { useDarkStore } from "../../../../stores/darkStore"; import { useDarkStore } from "../../../../stores/darkStore";
import useFlowStore from "../../../../stores/flowStore"; import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
@ -27,7 +28,6 @@ import {
updateFlowPosition, updateFlowPosition,
} from "../../../../utils/reactflowUtils"; } from "../../../../utils/reactflowUtils";
import { classNames, cn } from "../../../../utils/utils"; import { classNames, cn } from "../../../../utils/utils";
import useAlertStore from "../../../../stores/alertStore";
export default function NodeToolbarComponent({ export default function NodeToolbarComponent({
data, data,
@ -97,8 +97,8 @@ export default function NodeToolbarComponent({
(state) => state.setLastCopiedSelection (state) => state.setLastCopiedSelection
); );
const setSuccessData = useAlertStore(state => state.setSuccessData); const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setNoticeData = useAlertStore(state => state.setNoticeData); const setNoticeData = useAlertStore((state) => state.setNoticeData);
useEffect(() => { useEffect(() => {
setFlowComponent(createFlowComponent(cloneDeep(data), version)); setFlowComponent(createFlowComponent(cloneDeep(data), version));
@ -166,10 +166,8 @@ export default function NodeToolbarComponent({
{ {
x: 50, x: 50,
y: 10, y: 10,
paneX: nodes.find((node) => node.id === data.id)?.position paneX: nodes.find((node) => node.id === data.id)?.position.x,
.x, paneY: nodes.find((node) => node.id === data.id)?.position.y,
paneY: nodes.find((node) => node.id === data.id)?.position
.y,
} }
); );
break; break;
@ -260,18 +258,17 @@ export default function NodeToolbarComponent({
event.preventDefault(); event.preventDefault();
setShowconfirmShare((state) => !state); setShowconfirmShare((state) => !state);
} }
if ( if (selected && (event.ctrlKey || event.metaKey) && event.key === "q") {
selected &&
(event.ctrlKey || event.metaKey) &&
event.key === "q"
) {
event.preventDefault(); event.preventDefault();
if (isMinimal) { if (isMinimal) {
setShowState(show => !show) setShowState((show) => !show);
setShowNode(data.showNode ?? true ? false : true); 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 ( if (
selected && selected &&
@ -281,7 +278,7 @@ export default function NodeToolbarComponent({
) { ) {
event.preventDefault(); event.preventDefault();
if (hasCode) return setOpenModal((state) => !state); 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 ( if (
selected && selected &&
@ -292,11 +289,7 @@ export default function NodeToolbarComponent({
event.preventDefault(); event.preventDefault();
setShowModalAdvanced((state) => !state); setShowModalAdvanced((state) => !state);
} }
if ( if (selected && (event.ctrlKey || event.metaKey) && event.key === "s") {
selected &&
(event.ctrlKey || event.metaKey) &&
event.key === "s"
) {
if (isSaved) { if (isSaved) {
event.preventDefault(); event.preventDefault();
return setShowOverrideModal((state) => !state); return setShowOverrideModal((state) => !state);
@ -304,7 +297,7 @@ export default function NodeToolbarComponent({
if (hasCode) { if (hasCode) {
event.preventDefault(); event.preventDefault();
saveComponent(cloneDeep(data), false); saveComponent(cloneDeep(data), false);
setSuccessData({title: `${data.id} saved successfully`}) setSuccessData({ title: `${data.id} saved successfully` });
} }
} }
if ( if (
@ -317,7 +310,9 @@ export default function NodeToolbarComponent({
if (data.node?.documentation) { if (data.node?.documentation) {
return openInNewTab(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"> <ShadTooltip content={"Save"} side="top">
<button <button
className={classNames( 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) => { onClick={(event) => {
event.preventDefault(); event.preventDefault();
@ -359,10 +355,7 @@ export default function NodeToolbarComponent({
saveComponent(cloneDeep(data), false); saveComponent(cloneDeep(data), false);
}} }}
> >
<IconComponent <IconComponent name="SaveAll" className="h-4 w-4" />
name="SaveAll"
className="h-4 w-4"
/>
</button> </button>
</ShadTooltip> </ShadTooltip>
@ -425,11 +418,13 @@ export default function NodeToolbarComponent({
<span className="">Edit</span>{" "} <span className="">Edit</span>{" "}
{navigator.userAgent.toUpperCase().includes("MAC") ? ( {navigator.userAgent.toUpperCase().includes("MAC") ? (
<IconComponent <IconComponent
name="Command" name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2" className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
></IconComponent> ></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> <span className="absolute right-2 top-[0.46em]">E</span>
</div> </div>
@ -437,16 +432,21 @@ export default function NodeToolbarComponent({
)} )}
<SelectItem value={"duplicate"}> <SelectItem value={"duplicate"}>
<div className="flex" data-testid="save-button-modal"> <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 Duplicate
{navigator.userAgent.toUpperCase().includes("MAC") ? ( {navigator.userAgent.toUpperCase().includes("MAC") ? (
<IconComponent <IconComponent
name="Command" name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2" className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
></IconComponent> ></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> <span className="absolute right-2 top-[0.4em]">D</span>
</div>{" "} </div>{" "}
</SelectItem> </SelectItem>
@ -458,13 +458,15 @@ export default function NodeToolbarComponent({
/>{" "} />{" "}
<span className="">Copy</span>{" "} <span className="">Copy</span>{" "}
{navigator.userAgent.toUpperCase().includes("MAC") ? ( {navigator.userAgent.toUpperCase().includes("MAC") ? (
<IconComponent <IconComponent
name="Command" name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2" className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
></IconComponent> ></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> <span className="absolute right-2 top-[0.4em]">C</span>
</div> </div>
</SelectItem> </SelectItem>
@ -481,13 +483,14 @@ export default function NodeToolbarComponent({
Share{" "} Share{" "}
{navigator.userAgent.toUpperCase().includes("MAC") ? ( {navigator.userAgent.toUpperCase().includes("MAC") ? (
<IconComponent <IconComponent
name="Command" name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2" className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
></IconComponent> ></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 <IconComponent
name="ArrowBigUp" name="ArrowBigUp"
className="absolute right-[1.09rem] top-[0.65em] h-3.5 w-3.5 stroke-2" 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>{" "} <span className="">Docs</span>{" "}
{navigator.userAgent.toUpperCase().includes("MAC") ? ( {navigator.userAgent.toUpperCase().includes("MAC") ? (
<IconComponent <IconComponent
name="Command" name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2" className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
></IconComponent> ></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 <IconComponent
name="ArrowBigUp" name="ArrowBigUp"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2" 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"} {showNode ? "Minimize" : "Expand"}
{navigator.userAgent.toUpperCase().includes("MAC") ? ( {navigator.userAgent.toUpperCase().includes("MAC") ? (
<IconComponent <IconComponent
name="Command" name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2" className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
></IconComponent> ></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> <span className="absolute right-2 top-[0.46em]">Q</span>
</div> </div>
@ -562,11 +569,13 @@ export default function NodeToolbarComponent({
<span className="">Ungroup</span>{" "} <span className="">Ungroup</span>{" "}
{navigator.userAgent.toUpperCase().includes("MAC") ? ( {navigator.userAgent.toUpperCase().includes("MAC") ? (
<IconComponent <IconComponent
name="Command" name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2" className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
></IconComponent> ></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> <span className="absolute right-2 top-[0.43em]">U</span>
</div> </div>
@ -601,12 +610,12 @@ export default function NodeToolbarComponent({
index={6} index={6}
onConfirm={(index, user) => { onConfirm={(index, user) => {
saveComponent(cloneDeep(data), true); saveComponent(cloneDeep(data), true);
setSuccessData({title: `${data.id} successfully overridden!`}) setSuccessData({ title: `${data.id} successfully overridden!` });
}} }}
onClose={setShowOverrideModal} onClose={setShowOverrideModal}
onCancel={() => { onCancel={() => {
saveComponent(cloneDeep(data), false) saveComponent(cloneDeep(data), false);
setSuccessData({title: "New node successfully saved!"}) setSuccessData({ title: "New node successfully saved!" });
}} }}
> >
<ConfirmationModal.Content> <ConfirmationModal.Content>

View file

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

View file

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

View file

@ -1,4 +1,5 @@
import pytest import pytest
from langflow.services.database.models.api_key import ApiKeyCreate 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 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 api keys in data["api_keys"] are masked
assert all("**" in api_key["api_key"] for api_key in data["api_keys"]) 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): 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()) num_projects = len(load_starter_projects())
# Get the number of projects in the database # Get the number of projects in the database
num_db_projects = session.exec( num_db_projects = session.exec(select(func.count(Flow.id)).where(Flow.folder == STARTER_FOLDER_NAME)).one()
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 # 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 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()) num_projects = len(load_starter_projects())
# Get the number of projects in the database # Get the number of projects in the database
num_db_projects = session.exec( num_db_projects = session.exec(select(func.count(Flow.id)).where(Flow.folder == STARTER_FOLDER_NAME)).one()
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 # 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 assert num_db_projects == num_projects
# Get all the starter projects # Get all the starter projects
projects = session.exec( projects = session.exec(select(Flow).where(Flow.folder == STARTER_FOLDER_NAME)).all()
select(Flow).where(Flow.folder == STARTER_FOLDER_NAME)
).all()
graphs: list[Graph] = [ graphs: list[Graph] = [
(project.name, Graph.from_payload(project.data, flow_id=project.id)) (project.name, Graph.from_payload(project.data, flow_id=project.id)) for project in projects
for project in projects
] ]
assert len(graphs) == len(projects) assert len(graphs) == len(projects)
for name, graph in graphs: 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 # Provide a non-existent session_id
session_service = get_session_service() session_service = get_session_service()
session_id1 = "non-existent-session-id" session_id1 = "non-existent-session-id"
graph1, artifacts1 = await session_service.load_session( graph1, artifacts1 = await session_service.load_session(session_id1, basic_graph_data)
session_id1, basic_graph_data
)
# Use the new session_id to get the langchain_object again # Use the new session_id to get the langchain_object again
graph2, artifacts2 = await session_service.load_session( graph2, artifacts2 = await session_service.load_session(session_id1, basic_graph_data)
session_id1, basic_graph_data
)
assert graph1 == graph2 assert graph1 == graph2
assert artifacts1 == artifacts2 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_service = get_session_service()
session_id1 = "non-existent-session-id" session_id1 = "non-existent-session-id"
session_id = session_service.build_key(session_id1, basic_graph_data) session_id = session_service.build_key(session_id1, basic_graph_data)
graph1, artifacts1 = await session_service.load_session( graph1, artifacts1 = await session_service.load_session(session_id, basic_graph_data)
session_id, basic_graph_data
)
# Clear the cache # Clear the cache
session_service.clear_session(session_id) session_service.clear_session(session_id)
# Use the new session_id to get the langchain_object again # Use the new session_id to get the langchain_object again
graph2, artifacts2 = await session_service.load_session( graph2, artifacts2 = await session_service.load_session(session_id, basic_graph_data)
session_id, basic_graph_data
)
assert id(graph1) != id(graph2) assert id(graph1) != id(graph2)
# Since the cache was cleared, objects should be different # 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 # Provide a non-existent session_id
session_service = get_session_service() session_service = get_session_service()
session_id1 = None session_id1 = None
graph1, artifacts1 = await session_service.load_session( graph1, artifacts1 = await session_service.load_session(session_id1, basic_graph_data)
session_id1, basic_graph_data
)
# Use the new session_id to get the langchain_object again # Use the new session_id to get the langchain_object again
graph2, artifacts2 = await session_service.load_session( graph2, artifacts2 = await session_service.load_session(session_id1, basic_graph_data)
session_id1, basic_graph_data
)
assert graph1 == graph2 assert graph1 == graph2