Formatting
This commit is contained in:
parent
fd7da8720f
commit
39434eadc4
58 changed files with 630 additions and 1027 deletions
|
|
@ -20,9 +20,7 @@ API_WORDS = ["api", "key", "token"]
|
|||
|
||||
|
||||
def has_api_terms(word: str):
|
||||
return "api" in word and (
|
||||
"key" in word or ("token" in word and "tokens" not in word)
|
||||
)
|
||||
return "api" in word and ("key" in word or ("token" in word and "tokens" not in word))
|
||||
|
||||
|
||||
def remove_api_keys(flow: dict):
|
||||
|
|
@ -32,11 +30,7 @@ def remove_api_keys(flow: dict):
|
|||
node_data = node.get("data").get("node")
|
||||
template = node_data.get("template")
|
||||
for value in template.values():
|
||||
if (
|
||||
isinstance(value, dict)
|
||||
and has_api_terms(value["name"])
|
||||
and value.get("password")
|
||||
):
|
||||
if isinstance(value, dict) and has_api_terms(value["name"]) and value.get("password"):
|
||||
value["value"] = None
|
||||
|
||||
return flow
|
||||
|
|
@ -57,9 +51,7 @@ def build_input_keys_response(langchain_object, artifacts):
|
|||
input_keys_response["input_keys"][key] = value
|
||||
# If the object has memory, that memory will have a memory_variables attribute
|
||||
# memory variables should be removed from the input keys
|
||||
if hasattr(langchain_object, "memory") and hasattr(
|
||||
langchain_object.memory, "memory_variables"
|
||||
):
|
||||
if hasattr(langchain_object, "memory") and hasattr(langchain_object.memory, "memory_variables"):
|
||||
# Remove memory variables from input keys
|
||||
input_keys_response["input_keys"] = {
|
||||
key: value
|
||||
|
|
@ -69,9 +61,7 @@ def build_input_keys_response(langchain_object, artifacts):
|
|||
# Add memory variables to memory_keys
|
||||
input_keys_response["memory_keys"] = langchain_object.memory.memory_variables
|
||||
|
||||
if hasattr(langchain_object, "prompt") and hasattr(
|
||||
langchain_object.prompt, "template"
|
||||
):
|
||||
if hasattr(langchain_object, "prompt") and hasattr(langchain_object.prompt, "template"):
|
||||
input_keys_response["template"] = langchain_object.prompt.template
|
||||
|
||||
return input_keys_response
|
||||
|
|
@ -106,11 +96,7 @@ def raw_frontend_data_is_valid(raw_frontend_data):
|
|||
def is_valid_data(frontend_node, raw_frontend_data):
|
||||
"""Check if the data is valid for processing."""
|
||||
|
||||
return (
|
||||
frontend_node
|
||||
and "template" in frontend_node
|
||||
and raw_frontend_data_is_valid(raw_frontend_data)
|
||||
)
|
||||
return frontend_node and "template" in frontend_node and raw_frontend_data_is_valid(raw_frontend_data)
|
||||
|
||||
|
||||
def update_template_values(frontend_template, raw_template):
|
||||
|
|
@ -150,9 +136,7 @@ def get_file_path_value(file_path):
|
|||
# If the path is not in the cache dir, return empty string
|
||||
# This is to prevent access to files outside the cache dir
|
||||
# If the path is not a file, return empty string
|
||||
if not path.exists() or not str(path).startswith(
|
||||
user_cache_dir("langflow", "langflow")
|
||||
):
|
||||
if not path.exists() or not str(path).startswith(user_cache_dir("langflow", "langflow")):
|
||||
return ""
|
||||
return file_path
|
||||
|
||||
|
|
@ -183,9 +167,7 @@ async def check_langflow_version(component: StoreComponentCreate):
|
|||
|
||||
langflow_version = get_lf_version_from_pypi()
|
||||
if langflow_version is None:
|
||||
raise HTTPException(
|
||||
status_code=500, detail="Unable to verify the latest version of Langflow"
|
||||
)
|
||||
raise HTTPException(status_code=500, detail="Unable to verify the latest version of Langflow")
|
||||
elif langflow_version != component.last_tested_version:
|
||||
warnings.warn(
|
||||
f"Your version of Langflow ({component.last_tested_version}) is outdated. "
|
||||
|
|
|
|||
|
|
@ -103,9 +103,7 @@ def fix_variable(var, invalid_chars, wrong_variables):
|
|||
# Handle variables starting with a number
|
||||
if var[0].isdigit():
|
||||
invalid_chars.append(var[0])
|
||||
new_var, invalid_chars, wrong_variables = fix_variable(
|
||||
var[1:], invalid_chars, wrong_variables
|
||||
)
|
||||
new_var, invalid_chars, wrong_variables = fix_variable(var[1:], invalid_chars, wrong_variables)
|
||||
|
||||
# Temporarily replace {{ and }} to avoid treating them as invalid
|
||||
new_var = new_var.replace("{{", "ᴛᴇᴍᴘᴏᴘᴇɴ").replace("}}", "ᴛᴇᴍᴘᴄʟᴏsᴇ")
|
||||
|
|
@ -132,9 +130,7 @@ def check_variable(var, invalid_chars, wrong_variables, empty_variables):
|
|||
return wrong_variables, empty_variables
|
||||
|
||||
|
||||
def check_for_errors(
|
||||
input_variables, fixed_variables, wrong_variables, empty_variables
|
||||
):
|
||||
def check_for_errors(input_variables, fixed_variables, wrong_variables, empty_variables):
|
||||
if any(var for var in input_variables if var not in fixed_variables):
|
||||
error_message = (
|
||||
f"Error: Input variables contain invalid characters or formats. \n"
|
||||
|
|
@ -159,17 +155,11 @@ def check_input_variables(input_variables):
|
|||
if is_json_like(var):
|
||||
continue
|
||||
|
||||
new_var, wrong_variables, empty_variables = fix_variable(
|
||||
var, invalid_chars, wrong_variables
|
||||
)
|
||||
wrong_variables, empty_variables = check_variable(
|
||||
var, INVALID_CHARACTERS, wrong_variables, empty_variables
|
||||
)
|
||||
new_var, wrong_variables, empty_variables = fix_variable(var, invalid_chars, wrong_variables)
|
||||
wrong_variables, empty_variables = check_variable(var, INVALID_CHARACTERS, wrong_variables, empty_variables)
|
||||
fixed_variables.append(new_var)
|
||||
variables_to_check.append(var)
|
||||
|
||||
check_for_errors(
|
||||
variables_to_check, fixed_variables, wrong_variables, empty_variables
|
||||
)
|
||||
check_for_errors(variables_to_check, fixed_variables, wrong_variables, empty_variables)
|
||||
|
||||
return fixed_variables
|
||||
|
|
|
|||
|
|
@ -122,9 +122,7 @@ async def build_vertex(
|
|||
else:
|
||||
raise ValueError(f"No result found for vertex {vertex_id}")
|
||||
next_vertices_ids = vertex.successors_ids
|
||||
next_vertices_ids = [
|
||||
v for v in next_vertices_ids if graph.should_run_vertex(v)
|
||||
]
|
||||
next_vertices_ids = [v for v in next_vertices_ids if graph.should_run_vertex(v)]
|
||||
|
||||
result_data_response = ResultDataResponse(**result_dict.model_dump())
|
||||
|
||||
|
|
|
|||
|
|
@ -52,9 +52,7 @@ def get_all(
|
|||
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.post(
|
||||
"/run/{flow_id}", response_model=RunResponse, response_model_exclude_none=True
|
||||
)
|
||||
@router.post("/run/{flow_id}", response_model=RunResponse, response_model_exclude_none=True)
|
||||
async def run_flow_with_caching(
|
||||
session: Annotated[Session, Depends(get_session)],
|
||||
flow_id: str,
|
||||
|
|
@ -105,9 +103,7 @@ async def run_flow_with_caching(
|
|||
"""
|
||||
try:
|
||||
if inputs is not None:
|
||||
input_values: list[dict[str, Union[str, list[str]]]] = [
|
||||
_input.model_dump() for _input in inputs
|
||||
]
|
||||
input_values: list[dict[str, Union[str, list[str]]]] = [_input.model_dump() for _input in inputs]
|
||||
else:
|
||||
input_values = [{}]
|
||||
|
||||
|
|
@ -115,9 +111,7 @@ async def run_flow_with_caching(
|
|||
outputs = []
|
||||
|
||||
if session_id:
|
||||
session_data = await session_service.load_session(
|
||||
session_id, flow_id=flow_id
|
||||
)
|
||||
session_data = await session_service.load_session(session_id, flow_id=flow_id)
|
||||
graph, artifacts = session_data if session_data else (None, None)
|
||||
task_result: Any = None
|
||||
if not graph:
|
||||
|
|
@ -136,11 +130,7 @@ async def run_flow_with_caching(
|
|||
else:
|
||||
# Get the flow that matches the flow_id and belongs to the user
|
||||
# flow = session.query(Flow).filter(Flow.id == flow_id).filter(Flow.user_id == api_key_user.id).first()
|
||||
flow = session.exec(
|
||||
select(Flow)
|
||||
.where(Flow.id == flow_id)
|
||||
.where(Flow.user_id == api_key_user.id)
|
||||
).first()
|
||||
flow = session.exec(select(Flow).where(Flow.id == flow_id).where(Flow.user_id == api_key_user.id)).first()
|
||||
if flow is None:
|
||||
raise ValueError(f"Flow {flow_id} not found")
|
||||
|
||||
|
|
@ -164,18 +154,12 @@ async def run_flow_with_caching(
|
|||
# StatementError('(builtins.ValueError) badly formed hexadecimal UUID string')
|
||||
if "badly formed hexadecimal UUID string" in str(exc):
|
||||
# This means the Flow ID is not a valid UUID which means it can't find the flow
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
||||
) from exc
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
|
||||
except ValueError as exc:
|
||||
if f"Flow {flow_id} not found" in str(exc):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
||||
) from exc
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)
|
||||
) from exc
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.post(
|
||||
|
|
@ -204,8 +188,7 @@ async def process(
|
|||
"""
|
||||
# Raise a depreciation warning
|
||||
logger.warning(
|
||||
"The /process endpoint is deprecated and will be removed in a future version. "
|
||||
"Please use /run instead."
|
||||
"The /process endpoint is deprecated and will be removed in a future version. " "Please use /run instead."
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
|
|
@ -277,16 +260,12 @@ async def custom_component(
|
|||
|
||||
built_frontend_node = build_custom_component_template(component, user_id=user.id)
|
||||
|
||||
built_frontend_node = update_frontend_node_with_template_values(
|
||||
built_frontend_node, raw_code.frontend_node
|
||||
)
|
||||
built_frontend_node = update_frontend_node_with_template_values(built_frontend_node, raw_code.frontend_node)
|
||||
return built_frontend_node
|
||||
|
||||
|
||||
@router.post("/custom_component/reload", status_code=HTTPStatus.OK)
|
||||
async def reload_custom_component(
|
||||
path: str, user: User = Depends(get_current_active_user)
|
||||
):
|
||||
async def reload_custom_component(path: str, user: User = Depends(get_current_active_user)):
|
||||
from langflow.interface.custom.utils import build_custom_component_template
|
||||
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -46,9 +46,7 @@ def post_validate_prompt(prompt_request: ValidatePromptRequest):
|
|||
)
|
||||
if not prompt_request.custom_fields:
|
||||
prompt_request.custom_fields = defaultdict(list)
|
||||
old_custom_fields = get_old_custom_fields(
|
||||
prompt_request.custom_fields, prompt_request.name
|
||||
)
|
||||
old_custom_fields = get_old_custom_fields(prompt_request.custom_fields, prompt_request.name)
|
||||
|
||||
add_new_variables_to_template(
|
||||
input_variables,
|
||||
|
|
@ -65,9 +63,7 @@ def post_validate_prompt(prompt_request: ValidatePromptRequest):
|
|||
prompt_request.name,
|
||||
)
|
||||
|
||||
update_input_variables_field(
|
||||
input_variables, prompt_request.frontend_node.template
|
||||
)
|
||||
update_input_variables_field(input_variables, prompt_request.frontend_node.template)
|
||||
|
||||
return PromptValidationResponse(
|
||||
input_variables=input_variables,
|
||||
|
|
|
|||
|
|
@ -45,9 +45,7 @@ class ChatComponent(CustomComponent):
|
|||
return []
|
||||
|
||||
if not session_id or not sender or not sender_name:
|
||||
raise ValueError(
|
||||
"All of session_id, sender, and sender_name must be provided."
|
||||
)
|
||||
raise ValueError("All of session_id, sender, and sender_name must be provided.")
|
||||
if isinstance(message, Record):
|
||||
record = message
|
||||
record.data.update(
|
||||
|
|
|
|||
|
|
@ -67,9 +67,7 @@ def validate_prompt(prompt_template: str, silent_errors: bool = False) -> list[s
|
|||
# Check if there are invalid characters in the input_variables
|
||||
input_variables = check_input_variables(input_variables)
|
||||
if any(var in INVALID_NAMES for var in input_variables):
|
||||
raise ValueError(
|
||||
f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. "
|
||||
)
|
||||
raise ValueError(f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. ")
|
||||
|
||||
try:
|
||||
PromptTemplate(template=prompt_template, input_variables=input_variables)
|
||||
|
|
@ -118,9 +116,7 @@ def add_new_variables_to_template(input_variables, custom_fields, template, name
|
|||
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||
|
||||
|
||||
def remove_old_variables_from_template(
|
||||
old_custom_fields, input_variables, custom_fields, template, name
|
||||
):
|
||||
def remove_old_variables_from_template(old_custom_fields, input_variables, custom_fields, template, name):
|
||||
for variable in old_custom_fields:
|
||||
if variable not in input_variables:
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -56,9 +56,7 @@ class APIRequest(CustomComponent):
|
|||
data = body if body else None
|
||||
payload = json.dumps(data)
|
||||
try:
|
||||
response = await client.request(
|
||||
method, url, headers=headers, content=payload, timeout=timeout
|
||||
)
|
||||
response = await client.request(method, url, headers=headers, content=payload, timeout=timeout)
|
||||
try:
|
||||
result = response.json()
|
||||
except Exception:
|
||||
|
|
@ -108,9 +106,6 @@ class APIRequest(CustomComponent):
|
|||
bodies = [body.data]
|
||||
async with httpx.AsyncClient() as client:
|
||||
results = await asyncio.gather(
|
||||
*[
|
||||
self.make_request(client, method, u, headers, rec, timeout)
|
||||
for u, rec in zip(urls, bodies)
|
||||
]
|
||||
*[self.make_request(client, method, u, headers, rec, timeout) for u, rec in zip(urls, bodies)]
|
||||
)
|
||||
return results
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from langflow.memory import delete_messages, get_messages
|
|||
class ClearMessageHistoryComponent(CustomComponent):
|
||||
display_name = "Clear Message History"
|
||||
description = "A component to clear the message history."
|
||||
icon="ClearMessageHistory"
|
||||
icon = "ClearMessageHistory"
|
||||
beta: bool = True
|
||||
|
||||
def build_config(self):
|
||||
|
|
|
|||
|
|
@ -38,18 +38,12 @@ class RunFlowComponent(CustomComponent):
|
|||
return []
|
||||
records = []
|
||||
for message in messages:
|
||||
message_dict = (
|
||||
message if isinstance(message, dict) else message.model_dump()
|
||||
)
|
||||
record = Record(
|
||||
text=message_dict.get("text", ""), data={"result": result_data}
|
||||
)
|
||||
message_dict = message if isinstance(message, dict) else message.model_dump()
|
||||
record = Record(text=message_dict.get("text", ""), data={"result": result_data})
|
||||
records.append(record)
|
||||
return records
|
||||
|
||||
async def build(
|
||||
self, input_value: Text, flow_name: str, tweaks: NestedDict
|
||||
) -> List[Record]:
|
||||
async def build(self, input_value: Text, flow_name: str, tweaks: NestedDict) -> List[Record]:
|
||||
results: List[Optional[ResultData]] = await self.run_flow(
|
||||
input_value=input_value, flow_name=flow_name, tweaks=tweaks
|
||||
)
|
||||
|
|
|
|||
|
|
@ -93,11 +93,6 @@ class TextToRecordComponent(CustomComponent):
|
|||
if mode == "Text":
|
||||
data = kwargs
|
||||
else:
|
||||
data = {
|
||||
k: v
|
||||
for key, d in kwargs.items()
|
||||
for k, v in d.items()
|
||||
if key not in ["mode", "n_keys", "keys"]
|
||||
}
|
||||
data = {k: v for key, d in kwargs.items() for k, v in d.items() if key not in ["mode", "n_keys", "keys"]}
|
||||
record = Record(data=data)
|
||||
return record
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ class PythonFunctionComponent(CustomComponent):
|
|||
display_name = "Python Function"
|
||||
description = "Define a Python function."
|
||||
icon = "Python"
|
||||
|
||||
def build_config(self):
|
||||
return {
|
||||
"function_code": {
|
||||
|
|
|
|||
|
|
@ -23,9 +23,7 @@ class PromptComponent(CustomComponent):
|
|||
) -> Text:
|
||||
prompt_template = PromptTemplate.from_template(Text(template))
|
||||
kwargs = dict_values_to_string(kwargs)
|
||||
kwargs = {
|
||||
k: "\n".join(v) if isinstance(v, list) else v for k, v in kwargs.items()
|
||||
}
|
||||
kwargs = {k: "\n".join(v) if isinstance(v, list) else v for k, v in kwargs.items()}
|
||||
try:
|
||||
formated_prompt = prompt_template.format(**kwargs)
|
||||
except Exception as exc:
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ class ChatOutput(ChatComponent):
|
|||
display_name = "Chat Output"
|
||||
description = "Used to send a message to the chat."
|
||||
icon = "ChatOutput"
|
||||
|
||||
def build(
|
||||
self,
|
||||
sender: Optional[str] = "Machine",
|
||||
|
|
|
|||
|
|
@ -11,9 +11,7 @@ from langflow.utils.util import build_loader_repr_from_records, unescape_string
|
|||
class RecursiveCharacterTextSplitterComponent(CustomComponent):
|
||||
display_name: str = "Recursive Character Text Splitter"
|
||||
description: str = "Split text into chunks of a specified length."
|
||||
documentation: str = (
|
||||
"https://docs.langflow.org/components/text-splitters#recursivecharactertextsplitter"
|
||||
)
|
||||
documentation: str = "https://docs.langflow.org/components/text-splitters#recursivecharactertextsplitter"
|
||||
|
||||
def build_config(self):
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -84,8 +84,7 @@ class ChromaComponent(CustomComponent):
|
|||
|
||||
if chroma_server_host is not None:
|
||||
chroma_settings = chromadb.config.Settings(
|
||||
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins
|
||||
or None,
|
||||
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins or None,
|
||||
chroma_server_host=chroma_server_host,
|
||||
chroma_server_port=chroma_server_port or None,
|
||||
chroma_server_grpc_port=chroma_server_grpc_port or None,
|
||||
|
|
@ -106,9 +105,7 @@ class ChromaComponent(CustomComponent):
|
|||
documents.append(_input)
|
||||
if documents is not None and embedding is not None:
|
||||
if len(documents) == 0:
|
||||
raise ValueError(
|
||||
"If documents are provided, there must be at least one document."
|
||||
)
|
||||
raise ValueError("If documents are provided, there must be at least one document.")
|
||||
chroma = Chroma.from_documents(
|
||||
documents=documents, # type: ignore
|
||||
persist_directory=index_directory,
|
||||
|
|
|
|||
|
|
@ -9,9 +9,7 @@ from langflow.schema.schema import Record
|
|||
|
||||
class MongoDBAtlasComponent(CustomComponent):
|
||||
display_name = "MongoDB Atlas"
|
||||
description = (
|
||||
"Construct a `MongoDB Atlas Vector Search` vector store from raw documents."
|
||||
)
|
||||
description = "Construct a `MongoDB Atlas Vector Search` vector store from raw documents."
|
||||
icon = "MongoDB"
|
||||
|
||||
def build_config(self):
|
||||
|
|
@ -39,9 +37,7 @@ class MongoDBAtlasComponent(CustomComponent):
|
|||
try:
|
||||
from pymongo import MongoClient
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Please install pymongo to use MongoDB Atlas Vector Store"
|
||||
)
|
||||
raise ImportError("Please install pymongo to use MongoDB Atlas Vector Store")
|
||||
try:
|
||||
mongo_client: MongoClient = MongoClient(mongodb_atlas_cluster_uri)
|
||||
collection = mongo_client[db_name][collection_name]
|
||||
|
|
|
|||
|
|
@ -66,9 +66,7 @@ class RedisComponent(CustomComponent):
|
|||
documents.append(_input)
|
||||
if not documents:
|
||||
if schema is None:
|
||||
raise ValueError(
|
||||
"If no documents are provided, a schema must be provided."
|
||||
)
|
||||
raise ValueError("If no documents are provided, a schema must be provided.")
|
||||
redis_vs = Redis.from_existing_index(
|
||||
embedding=embedding,
|
||||
index_name=redis_index_name,
|
||||
|
|
|
|||
|
|
@ -35,9 +35,7 @@ class SupabaseComponent(CustomComponent):
|
|||
supabase_url: str = "",
|
||||
table_name: str = "",
|
||||
) -> Union[VectorStore, SupabaseVectorStore, BaseRetriever]:
|
||||
supabase: Client = create_client(
|
||||
supabase_url, supabase_key=supabase_service_key
|
||||
)
|
||||
supabase: Client = create_client(supabase_url, supabase_key=supabase_service_key)
|
||||
documents = []
|
||||
for _input in inputs or []:
|
||||
if isinstance(_input, Record):
|
||||
|
|
|
|||
|
|
@ -15,9 +15,7 @@ from langflow.schema.schema import Record
|
|||
class VectaraComponent(CustomComponent):
|
||||
display_name: str = "Vectara"
|
||||
description: str = "Implementation of Vector Store using Vectara"
|
||||
documentation = (
|
||||
"https://python.langchain.com/docs/integrations/vectorstores/vectara"
|
||||
)
|
||||
documentation = "https://python.langchain.com/docs/integrations/vectorstores/vectara"
|
||||
icon = "Vectara"
|
||||
field_config = {
|
||||
"vectara_customer_id": {
|
||||
|
|
|
|||
|
|
@ -12,9 +12,7 @@ from langflow.schema.schema import Record
|
|||
class WeaviateVectorStoreComponent(CustomComponent):
|
||||
display_name: str = "Weaviate"
|
||||
description: str = "Implementation of Vector Store using Weaviate"
|
||||
documentation = (
|
||||
"https://python.langchain.com/docs/integrations/vectorstores/weaviate"
|
||||
)
|
||||
documentation = "https://python.langchain.com/docs/integrations/vectorstores/weaviate"
|
||||
field_config = {
|
||||
"url": {"display_name": "Weaviate URL", "value": "http://localhost:8080"},
|
||||
"api_key": {
|
||||
|
|
|
|||
|
|
@ -16,9 +16,7 @@ class PGVectorComponent(CustomComponent):
|
|||
|
||||
display_name: str = "PGVector"
|
||||
description: str = "Implementation of Vector Store using PostgreSQL"
|
||||
documentation = (
|
||||
"https://python.langchain.com/docs/integrations/vectorstores/pgvector"
|
||||
)
|
||||
documentation = "https://python.langchain.com/docs/integrations/vectorstores/pgvector"
|
||||
|
||||
def build_config(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -13,9 +13,7 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
class SourceHandle(BaseModel):
|
||||
baseClasses: List[str] = Field(
|
||||
..., description="List of base classes for the source handle."
|
||||
)
|
||||
baseClasses: List[str] = Field(..., description="List of base classes for the source handle.")
|
||||
dataType: str = Field(..., description="Data type for the source handle.")
|
||||
id: str = Field(..., description="Unique identifier for the source handle.")
|
||||
|
||||
|
|
@ -23,9 +21,7 @@ class SourceHandle(BaseModel):
|
|||
class TargetHandle(BaseModel):
|
||||
fieldName: str = Field(..., description="Field name for the target handle.")
|
||||
id: str = Field(..., description="Unique identifier for the target handle.")
|
||||
inputTypes: Optional[List[str]] = Field(
|
||||
None, description="List of input types for the target handle."
|
||||
)
|
||||
inputTypes: Optional[List[str]] = Field(None, description="List of input types for the target handle.")
|
||||
type: str = Field(..., description="Type of the target handle.")
|
||||
|
||||
|
||||
|
|
@ -54,24 +50,16 @@ class Edge:
|
|||
|
||||
def validate_handles(self, source, target) -> None:
|
||||
if self.target_handle.inputTypes is None:
|
||||
self.valid_handles = (
|
||||
self.target_handle.type in self.source_handle.baseClasses
|
||||
)
|
||||
self.valid_handles = self.target_handle.type in self.source_handle.baseClasses
|
||||
else:
|
||||
self.valid_handles = (
|
||||
any(
|
||||
baseClass in self.target_handle.inputTypes
|
||||
for baseClass in self.source_handle.baseClasses
|
||||
)
|
||||
any(baseClass in self.target_handle.inputTypes for baseClass in self.source_handle.baseClasses)
|
||||
or self.target_handle.type in self.source_handle.baseClasses
|
||||
)
|
||||
if not self.valid_handles:
|
||||
logger.debug(self.source_handle)
|
||||
logger.debug(self.target_handle)
|
||||
raise ValueError(
|
||||
f"Edge between {source.vertex_type} and {target.vertex_type} "
|
||||
f"has invalid handles"
|
||||
)
|
||||
raise ValueError(f"Edge between {source.vertex_type} and {target.vertex_type} " f"has invalid handles")
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.source_id = state["source_id"]
|
||||
|
|
@ -88,11 +76,7 @@ class Edge:
|
|||
# Both lists contain strings and sometimes a string contains the value we are
|
||||
# looking for e.g. comgin_out=["Chain"] and target_reqs=["LLMChain"]
|
||||
# so we need to check if any of the strings in source_types is in target_reqs
|
||||
self.valid = any(
|
||||
output in target_req
|
||||
for output in self.source_types
|
||||
for target_req in self.target_reqs
|
||||
)
|
||||
self.valid = any(output in target_req for output in self.source_types for target_req in self.target_reqs)
|
||||
# Get what type of input the target node is expecting
|
||||
|
||||
self.matched_type = next(
|
||||
|
|
@ -103,10 +87,7 @@ class Edge:
|
|||
if no_matched_type:
|
||||
logger.debug(self.source_types)
|
||||
logger.debug(self.target_reqs)
|
||||
raise ValueError(
|
||||
f"Edge between {source.vertex_type} and {target.vertex_type} "
|
||||
f"has no matched type"
|
||||
)
|
||||
raise ValueError(f"Edge between {source.vertex_type} and {target.vertex_type} " f"has no matched type")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
|
|
@ -118,13 +99,9 @@ class Edge:
|
|||
return hash(self.__repr__())
|
||||
|
||||
def __eq__(self, __o: object) -> bool:
|
||||
|
||||
if not isinstance(__o, Edge):
|
||||
return False
|
||||
return (
|
||||
self._source_handle == __o._source_handle
|
||||
and self._target_handle == __o._target_handle
|
||||
)
|
||||
return self._source_handle == __o._source_handle and self._target_handle == __o._target_handle
|
||||
|
||||
|
||||
class ContractEdge(Edge):
|
||||
|
|
@ -181,9 +158,7 @@ class ContractEdge(Edge):
|
|||
return f"{self.source_id} -[{self.target_param}]-> {self.target_id}"
|
||||
|
||||
|
||||
def log_transaction(
|
||||
edge: ContractEdge, source: "Vertex", target: "Vertex", status, error=None
|
||||
):
|
||||
def log_transaction(edge: ContractEdge, source: "Vertex", target: "Vertex", status, error=None):
|
||||
try:
|
||||
monitor_service = get_monitor_service()
|
||||
clean_params = build_clean_params(target)
|
||||
|
|
|
|||
|
|
@ -74,9 +74,7 @@ class Graph:
|
|||
"""Returns the state of the graph."""
|
||||
return self.state_manager.get_state(name, run_id=self._run_id)
|
||||
|
||||
def update_state(
|
||||
self, name: str, record: Union[str, Record], caller: Optional[str] = None
|
||||
) -> None:
|
||||
def update_state(self, name: str, record: Union[str, Record], caller: Optional[str] = None) -> None:
|
||||
"""Updates the state of the graph."""
|
||||
if caller:
|
||||
# If there is a caller which is a vertex_id, I want to activate
|
||||
|
|
@ -108,9 +106,7 @@ class Graph:
|
|||
def reset_activated_vertices(self):
|
||||
self.activated_vertices = []
|
||||
|
||||
def append_state(
|
||||
self, name: str, record: Union[str, Record], caller: Optional[str] = None
|
||||
) -> None:
|
||||
def append_state(self, name: str, record: Union[str, Record], caller: Optional[str] = None) -> None:
|
||||
"""Appends the state of the graph."""
|
||||
if caller:
|
||||
self.activate_state_vertices(name, caller)
|
||||
|
|
@ -155,10 +151,7 @@ class Graph:
|
|||
"""Runs the graph with the given inputs."""
|
||||
for vertex_id in self._is_input_vertices:
|
||||
vertex = self.get_vertex(vertex_id)
|
||||
if input_components and (
|
||||
vertex_id not in input_components
|
||||
or vertex.display_name not in input_components
|
||||
):
|
||||
if input_components and (vertex_id not in input_components or vertex.display_name not in input_components):
|
||||
continue
|
||||
if vertex is None:
|
||||
raise ValueError(f"Vertex {vertex_id} not found")
|
||||
|
|
@ -181,11 +174,7 @@ class Graph:
|
|||
if vertex is None:
|
||||
raise ValueError(f"Vertex {vertex_id} not found")
|
||||
|
||||
if (
|
||||
not vertex.result
|
||||
and not stream
|
||||
and hasattr(vertex, "consume_async_generator")
|
||||
):
|
||||
if not vertex.result and not stream and hasattr(vertex, "consume_async_generator"):
|
||||
await vertex.consume_async_generator()
|
||||
if not outputs or (vertex.display_name in outputs or vertex.id in outputs):
|
||||
vertex_outputs.append(vertex.result)
|
||||
|
|
@ -220,9 +209,7 @@ class Graph:
|
|||
if isinstance(_input_value, str):
|
||||
input_value = _input_value
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Invalid input value: {input_value}. Expected string"
|
||||
)
|
||||
raise ValueError(f"Invalid input value: {input_value}. Expected string")
|
||||
|
||||
run_outputs = await self._run(
|
||||
inputs={INPUT_FIELD_NAME: input_value},
|
||||
|
|
@ -278,9 +265,7 @@ class Graph:
|
|||
def build_parent_child_map(self):
|
||||
parent_child_map = defaultdict(list)
|
||||
for vertex in self.vertices:
|
||||
parent_child_map[vertex.id] = [
|
||||
child.id for child in self.get_successors(vertex)
|
||||
]
|
||||
parent_child_map[vertex.id] = [child.id for child in self.get_successors(vertex)]
|
||||
return parent_child_map
|
||||
|
||||
def increment_run_count(self):
|
||||
|
|
@ -465,11 +450,7 @@ class Graph:
|
|||
"""Updates the edges of a vertex."""
|
||||
# Vertex has edges, so we need to update the edges
|
||||
for edge in vertex.edges:
|
||||
if (
|
||||
edge not in self.edges
|
||||
and edge.source_id in self.vertex_map
|
||||
and edge.target_id in self.vertex_map
|
||||
):
|
||||
if edge not in self.edges and edge.source_id in self.vertex_map and edge.target_id in self.vertex_map:
|
||||
self.edges.append(edge)
|
||||
|
||||
def _build_graph(self) -> None:
|
||||
|
|
@ -494,11 +475,7 @@ class Graph:
|
|||
return
|
||||
self.vertices.remove(vertex)
|
||||
self.vertex_map.pop(vertex_id)
|
||||
self.edges = [
|
||||
edge
|
||||
for edge in self.edges
|
||||
if edge.source_id != vertex_id and edge.target_id != vertex_id
|
||||
]
|
||||
self.edges = [edge for edge in self.edges if edge.source_id != vertex_id and edge.target_id != vertex_id]
|
||||
|
||||
def _build_vertex_params(self) -> None:
|
||||
"""Identifies and handles the LLM vertex within the graph."""
|
||||
|
|
@ -519,9 +496,7 @@ class Graph:
|
|||
return
|
||||
for vertex in self.vertices:
|
||||
if not self._validate_vertex(vertex):
|
||||
raise ValueError(
|
||||
f"{vertex.display_name} is not connected to any other components"
|
||||
)
|
||||
raise ValueError(f"{vertex.display_name} is not connected to any other components")
|
||||
|
||||
def _validate_vertex(self, vertex: Vertex) -> bool:
|
||||
"""Validates a vertex."""
|
||||
|
|
@ -575,9 +550,7 @@ class Graph:
|
|||
name=f"{vertex.display_name} Run {vertex_task_run_count.get(vertex_id, 0)}",
|
||||
)
|
||||
tasks.append(task)
|
||||
vertex_task_run_count[vertex_id] = (
|
||||
vertex_task_run_count.get(vertex_id, 0) + 1
|
||||
)
|
||||
vertex_task_run_count[vertex_id] = vertex_task_run_count.get(vertex_id, 0) + 1
|
||||
logger.debug(f"Running layer {layer_index} with {len(tasks)} tasks")
|
||||
await self._execute_tasks(tasks)
|
||||
logger.debug("Graph processing complete")
|
||||
|
|
@ -619,9 +592,7 @@ class Graph:
|
|||
def dfs(vertex):
|
||||
if state[vertex] == 1:
|
||||
# We have a cycle
|
||||
raise ValueError(
|
||||
"Graph contains a cycle, cannot perform topological sort"
|
||||
)
|
||||
raise ValueError("Graph contains a cycle, cannot perform topological sort")
|
||||
if state[vertex] == 0:
|
||||
state[vertex] = 1
|
||||
for edge in vertex.edges:
|
||||
|
|
@ -645,10 +616,7 @@ class Graph:
|
|||
|
||||
def get_predecessors(self, vertex):
|
||||
"""Returns the predecessors of a vertex."""
|
||||
return [
|
||||
self.get_vertex(source_id)
|
||||
for source_id in self.predecessor_map.get(vertex.id, [])
|
||||
]
|
||||
return [self.get_vertex(source_id) for source_id in self.predecessor_map.get(vertex.id, [])]
|
||||
|
||||
def get_all_successors(self, vertex, recursive=True, flat=True):
|
||||
# Recursively get the successors of the current vertex
|
||||
|
|
@ -689,10 +657,7 @@ class Graph:
|
|||
|
||||
def get_successors(self, vertex):
|
||||
"""Returns the successors of a vertex."""
|
||||
return [
|
||||
self.get_vertex(target_id)
|
||||
for target_id in self.successor_map.get(vertex.id, [])
|
||||
]
|
||||
return [self.get_vertex(target_id) for target_id in self.successor_map.get(vertex.id, [])]
|
||||
|
||||
def get_vertex_neighbors(self, vertex: Vertex) -> Dict[Vertex, int]:
|
||||
"""Returns the neighbors of a vertex."""
|
||||
|
|
@ -738,9 +703,7 @@ class Graph:
|
|||
edges_added.add((source.id, target.id))
|
||||
return edges
|
||||
|
||||
def _get_vertex_class(
|
||||
self, node_type: str, node_base_type: str, node_id: str
|
||||
) -> Type[Vertex]:
|
||||
def _get_vertex_class(self, node_type: str, node_base_type: str, node_id: str) -> Type[Vertex]:
|
||||
"""Returns the node class based on the node type."""
|
||||
# First we check for the node_base_type
|
||||
node_name = node_id.split("-")[0]
|
||||
|
|
@ -773,18 +736,14 @@ class Graph:
|
|||
vertex_type: str = vertex_data["type"] # type: ignore
|
||||
vertex_base_type: str = vertex_data["node"]["template"]["_type"] # type: ignore
|
||||
|
||||
VertexClass = self._get_vertex_class(
|
||||
vertex_type, vertex_base_type, vertex_data["id"]
|
||||
)
|
||||
VertexClass = self._get_vertex_class(vertex_type, vertex_base_type, vertex_data["id"])
|
||||
vertex_instance = VertexClass(vertex, graph=self)
|
||||
vertex_instance.set_top_level(self.top_level_vertices)
|
||||
vertices.append(vertex_instance)
|
||||
|
||||
return vertices
|
||||
|
||||
def get_children_by_vertex_type(
|
||||
self, vertex: Vertex, vertex_type: str
|
||||
) -> List[Vertex]:
|
||||
def get_children_by_vertex_type(self, vertex: Vertex, vertex_type: str) -> List[Vertex]:
|
||||
"""Returns the children of a vertex based on the vertex type."""
|
||||
children = []
|
||||
vertex_types = [vertex.data["type"]]
|
||||
|
|
@ -796,9 +755,7 @@ class Graph:
|
|||
|
||||
def __repr__(self):
|
||||
vertex_ids = [vertex.id for vertex in self.vertices]
|
||||
edges_repr = "\n".join(
|
||||
[f"{edge.source_id} --> {edge.target_id}" for edge in self.edges]
|
||||
)
|
||||
edges_repr = "\n".join([f"{edge.source_id} --> {edge.target_id}" for edge in self.edges])
|
||||
return f"Graph:\nNodes: {vertex_ids}\nConnections:\n{edges_repr}"
|
||||
|
||||
def sort_up_to_vertex(self, vertex_id: str, is_start: bool = False) -> List[Vertex]:
|
||||
|
|
@ -866,8 +823,7 @@ class Graph:
|
|||
vertex.id
|
||||
for vertex in vertices
|
||||
# if filter_graphs then only vertex.is_input will be considered
|
||||
if self.in_degree_map[vertex.id] == 0
|
||||
and (not filter_graphs or vertex.is_input)
|
||||
if self.in_degree_map[vertex.id] == 0 and (not filter_graphs or vertex.is_input)
|
||||
)
|
||||
layers: List[List[str]] = []
|
||||
visited = set(queue)
|
||||
|
|
@ -941,9 +897,7 @@ class Graph:
|
|||
|
||||
return refined_layers
|
||||
|
||||
def sort_chat_inputs_first(
|
||||
self, vertices_layers: List[List[str]]
|
||||
) -> List[List[str]]:
|
||||
def sort_chat_inputs_first(self, vertices_layers: List[List[str]]) -> List[List[str]]:
|
||||
chat_inputs_first = []
|
||||
for layer in vertices_layers:
|
||||
for vertex_id in layer:
|
||||
|
|
@ -984,9 +938,7 @@ class Graph:
|
|||
first_layer = vertices_layers[0]
|
||||
# save the only the rest
|
||||
self.vertices_layers = vertices_layers[1:]
|
||||
self.vertices_to_run = {
|
||||
vertex_id for vertex_id in chain.from_iterable(vertices_layers)
|
||||
}
|
||||
self.vertices_to_run = {vertex_id for vertex_id in chain.from_iterable(vertices_layers)}
|
||||
# Return just the first layer
|
||||
return first_layer
|
||||
|
||||
|
|
@ -997,15 +949,11 @@ class Graph:
|
|||
self.vertices_to_run.remove(vertex_id)
|
||||
return should_run
|
||||
|
||||
def sort_interface_components_first(
|
||||
self, vertices_layers: List[List[str]]
|
||||
) -> List[List[str]]:
|
||||
def sort_interface_components_first(self, vertices_layers: List[List[str]]) -> List[List[str]]:
|
||||
"""Sorts the vertices in the graph so that vertices containing ChatInput or ChatOutput come first."""
|
||||
|
||||
def contains_interface_component(vertex):
|
||||
return any(
|
||||
component.value in vertex for component in InterfaceComponentTypes
|
||||
)
|
||||
return any(component.value in vertex for component in InterfaceComponentTypes)
|
||||
|
||||
# Sort each inner list so that vertices containing ChatInput or ChatOutput come first
|
||||
sorted_vertices = [
|
||||
|
|
@ -1017,22 +965,16 @@ class Graph:
|
|||
]
|
||||
return sorted_vertices
|
||||
|
||||
def sort_by_avg_build_time(
|
||||
self, vertices_layers: List[List[str]]
|
||||
) -> List[List[str]]:
|
||||
def sort_by_avg_build_time(self, vertices_layers: List[List[str]]) -> List[List[str]]:
|
||||
"""Sorts the vertices in the graph so that vertices with the lowest average build time come first."""
|
||||
|
||||
def sort_layer_by_avg_build_time(vertices_ids: List[str]) -> List[str]:
|
||||
"""Sorts the vertices in the graph so that vertices with the lowest average build time come first."""
|
||||
if len(vertices_ids) == 1:
|
||||
return vertices_ids
|
||||
vertices_ids.sort(
|
||||
key=lambda vertex_id: self.get_vertex(vertex_id).avg_build_time
|
||||
)
|
||||
vertices_ids.sort(key=lambda vertex_id: self.get_vertex(vertex_id).avg_build_time)
|
||||
|
||||
return vertices_ids
|
||||
|
||||
sorted_vertices = [
|
||||
sort_layer_by_avg_build_time(layer) for layer in vertices_layers
|
||||
]
|
||||
sorted_vertices = [sort_layer_by_avg_build_time(layer) for layer in vertices_layers]
|
||||
return sorted_vertices
|
||||
|
|
|
|||
|
|
@ -46,10 +46,7 @@ class VertexTypesDict(LazyLoadDictBase):
|
|||
**{t: types.DocumentLoaderVertex for t in documentloader_creator.to_list()},
|
||||
**{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()},
|
||||
**{t: types.OutputParserVertex for t in output_parser_creator.to_list()},
|
||||
**{
|
||||
t: types.CustomComponentVertex
|
||||
for t in custom_component_creator.to_list()
|
||||
},
|
||||
**{t: types.CustomComponentVertex for t in custom_component_creator.to_list()},
|
||||
**{t: types.RetrieverVertex for t in retriever_creator.to_list()},
|
||||
**{t: types.ChatVertex for t in CHAT_COMPONENTS},
|
||||
**{t: types.RoutingVertex for t in ROUTING_COMPONENTS},
|
||||
|
|
|
|||
|
|
@ -60,13 +60,8 @@ class Vertex:
|
|||
self.updated_raw_params = False
|
||||
self.id: str = data["id"]
|
||||
self.is_state = False
|
||||
self.is_input = any(
|
||||
input_component_name in self.id for input_component_name in INPUT_COMPONENTS
|
||||
)
|
||||
self.is_output = any(
|
||||
output_component_name in self.id
|
||||
for output_component_name in OUTPUT_COMPONENTS
|
||||
)
|
||||
self.is_input = any(input_component_name in self.id for input_component_name in INPUT_COMPONENTS)
|
||||
self.is_output = any(output_component_name in self.id for output_component_name in OUTPUT_COMPONENTS)
|
||||
self.has_session_id = None
|
||||
self._custom_component = None
|
||||
self.has_external_input = False
|
||||
|
|
@ -106,17 +101,11 @@ class Vertex:
|
|||
|
||||
def set_state(self, state: str):
|
||||
self.state = VertexStates[state]
|
||||
if (
|
||||
self.state == VertexStates.INACTIVE
|
||||
and self.graph.in_degree_map[self.id] < 2
|
||||
):
|
||||
if self.state == VertexStates.INACTIVE and self.graph.in_degree_map[self.id] < 2:
|
||||
# If the vertex is inactive and has only one in degree
|
||||
# it means that it is not a merge point in the graph
|
||||
self.graph.inactivated_vertices.add(self.id)
|
||||
elif (
|
||||
self.state == VertexStates.ACTIVE
|
||||
and self.id in self.graph.inactivated_vertices
|
||||
):
|
||||
elif self.state == VertexStates.ACTIVE and self.id in self.graph.inactivated_vertices:
|
||||
self.graph.inactivated_vertices.remove(self.id)
|
||||
|
||||
@property
|
||||
|
|
@ -133,9 +122,7 @@ class Vertex:
|
|||
# If the Vertex.type is a power component
|
||||
# then we need to return the built object
|
||||
# instead of the result dict
|
||||
if self.is_interface_component and not isinstance(
|
||||
self._built_object, UnbuiltObject
|
||||
):
|
||||
if self.is_interface_component and not isinstance(self._built_object, UnbuiltObject):
|
||||
result = self._built_object
|
||||
# if it is not a dict or a string and hasattr model_dump then
|
||||
# return the model_dump
|
||||
|
|
@ -147,11 +134,7 @@ class Vertex:
|
|||
|
||||
if isinstance(self._built_result, UnbuiltResult):
|
||||
return {}
|
||||
return (
|
||||
self._built_result
|
||||
if isinstance(self._built_result, dict)
|
||||
else {"result": self._built_result}
|
||||
)
|
||||
return self._built_result if isinstance(self._built_result, dict) else {"result": self._built_result}
|
||||
|
||||
def set_artifacts(self) -> None:
|
||||
pass
|
||||
|
|
@ -221,31 +204,19 @@ class Vertex:
|
|||
self.selected_output_type = self.data["node"].get("selected_output_type")
|
||||
self.is_input = self.data["node"].get("is_input") or self.is_input
|
||||
self.is_output = self.data["node"].get("is_output") or self.is_output
|
||||
template_dicts = {
|
||||
key: value
|
||||
for key, value in self.data["node"]["template"].items()
|
||||
if isinstance(value, dict)
|
||||
}
|
||||
template_dicts = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
|
||||
|
||||
self.has_session_id = "session_id" in template_dicts
|
||||
|
||||
self.required_inputs = [
|
||||
template_dicts[key]["type"]
|
||||
for key, value in template_dicts.items()
|
||||
if value["required"]
|
||||
template_dicts[key]["type"] for key, value in template_dicts.items() if value["required"]
|
||||
]
|
||||
self.optional_inputs = [
|
||||
template_dicts[key]["type"]
|
||||
for key, value in template_dicts.items()
|
||||
if not value["required"]
|
||||
template_dicts[key]["type"] for key, value in template_dicts.items() if not value["required"]
|
||||
]
|
||||
# Add the template_dicts[key]["input_types"] to the optional_inputs
|
||||
self.optional_inputs.extend(
|
||||
[
|
||||
input_type
|
||||
for value in template_dicts.values()
|
||||
for input_type in value.get("input_types", [])
|
||||
]
|
||||
[input_type for value in template_dicts.values() for input_type in value.get("input_types", [])]
|
||||
)
|
||||
|
||||
template_dict = self.data["node"]["template"]
|
||||
|
|
@ -292,11 +263,7 @@ class Vertex:
|
|||
self.updated_raw_params = False
|
||||
return
|
||||
|
||||
template_dict = {
|
||||
key: value
|
||||
for key, value in self.data["node"]["template"].items()
|
||||
if isinstance(value, dict)
|
||||
}
|
||||
template_dict = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
|
||||
params = {}
|
||||
|
||||
for edge in self.edges:
|
||||
|
|
@ -317,10 +284,7 @@ class Vertex:
|
|||
# we don't know the key of the dict but we need to set the value
|
||||
# to the vertex that is the source of the edge
|
||||
param_dict = template_dict[param_key]["value"]
|
||||
params[param_key] = {
|
||||
key: self.graph.get_vertex(edge.source_id)
|
||||
for key in param_dict.keys()
|
||||
}
|
||||
params[param_key] = {key: self.graph.get_vertex(edge.source_id) for key in param_dict.keys()}
|
||||
else:
|
||||
params[param_key] = self.graph.get_vertex(edge.source_id)
|
||||
|
||||
|
|
@ -356,11 +320,7 @@ class Vertex:
|
|||
# list of dicts, so we need to convert it to a dict
|
||||
# before passing it to the build method
|
||||
if isinstance(val, list):
|
||||
params[key] = {
|
||||
k: v
|
||||
for item in value.get("value", [])
|
||||
for k, v in item.items()
|
||||
}
|
||||
params[key] = {k: v for item in value.get("value", []) for k, v in item.items()}
|
||||
elif isinstance(val, dict):
|
||||
params[key] = val
|
||||
elif value.get("type") == "int" and val is not None:
|
||||
|
|
@ -485,9 +445,7 @@ class Vertex:
|
|||
if isinstance(self._built_object, str):
|
||||
self._built_result = self._built_object
|
||||
|
||||
result = await generate_result(
|
||||
self._built_object, inputs, self.has_external_output, session_id
|
||||
)
|
||||
result = await generate_result(self._built_object, inputs, self.has_external_output, session_id)
|
||||
self._built_result = result
|
||||
|
||||
async def _build_each_node_in_params_dict(self, user_id=None):
|
||||
|
|
@ -507,9 +465,7 @@ class Vertex:
|
|||
elif key not in self.params or self.updated_raw_params:
|
||||
self.params[key] = value
|
||||
|
||||
async def _build_dict_and_update_params(
|
||||
self, key, nodes_dict: Dict[str, "Vertex"], user_id=None
|
||||
):
|
||||
async def _build_dict_and_update_params(self, key, nodes_dict: Dict[str, "Vertex"], user_id=None):
|
||||
"""
|
||||
Iterates over a dictionary of nodes, builds each and updates the params dictionary.
|
||||
"""
|
||||
|
|
@ -532,9 +488,7 @@ class Vertex:
|
|||
"""
|
||||
return all(self._is_node(node) for node in value)
|
||||
|
||||
async def get_result(
|
||||
self, requester: Optional["Vertex"] = None, user_id=None, timeout=None
|
||||
) -> Any:
|
||||
async def get_result(self, requester: Optional["Vertex"] = None, user_id=None, timeout=None) -> Any:
|
||||
# PLEASE REVIEW THIS IF STATEMENT
|
||||
# Check if the Vertex was built already
|
||||
if self._built:
|
||||
|
|
@ -568,9 +522,7 @@ class Vertex:
|
|||
self._extend_params_list_with_result(key, result)
|
||||
self.params[key] = result
|
||||
|
||||
async def _build_list_of_nodes_and_update_params(
|
||||
self, key, nodes: List["Vertex"], user_id=None
|
||||
):
|
||||
async def _build_list_of_nodes_and_update_params(self, key, nodes: List["Vertex"], user_id=None):
|
||||
"""
|
||||
Iterates over a list of nodes, builds each and updates the params dictionary.
|
||||
"""
|
||||
|
|
@ -637,9 +589,7 @@ class Vertex:
|
|||
except Exception as exc:
|
||||
logger.exception(exc)
|
||||
|
||||
raise ValueError(
|
||||
f"Error building node {self.display_name}: {str(exc)}"
|
||||
) from exc
|
||||
raise ValueError(f"Error building node {self.display_name}: {str(exc)}") from exc
|
||||
|
||||
def _update_built_object_and_artifacts(self, result):
|
||||
"""
|
||||
|
|
@ -667,9 +617,7 @@ class Vertex:
|
|||
logger.warning(message)
|
||||
elif isinstance(self._built_object, (Iterator, AsyncIterator)):
|
||||
if self.display_name in ["Text Output"]:
|
||||
raise ValueError(
|
||||
f"You are trying to stream to a {self.display_name}. Try using a Chat Output instead."
|
||||
)
|
||||
raise ValueError(f"You are trying to stream to a {self.display_name}. Try using a Chat Output instead.")
|
||||
|
||||
def _reset(self, params_update: Optional[Dict[str, Any]] = None):
|
||||
self._built = False
|
||||
|
|
@ -732,24 +680,16 @@ class Vertex:
|
|||
return self._built_object
|
||||
|
||||
# Get the requester edge
|
||||
requester_edge = next(
|
||||
(edge for edge in self.edges if edge.target_id == requester.id), None
|
||||
)
|
||||
requester_edge = next((edge for edge in self.edges if edge.target_id == requester.id), None)
|
||||
# Return the result of the requester edge
|
||||
return (
|
||||
None
|
||||
if requester_edge is None
|
||||
else await requester_edge.get_result(source=self, target=requester)
|
||||
)
|
||||
return None if requester_edge is None else await requester_edge.get_result(source=self, target=requester)
|
||||
|
||||
def add_edge(self, edge: "ContractEdge") -> None:
|
||||
if edge not in self.edges:
|
||||
self.edges.append(edge)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"Vertex(display_name={self.display_name}, id={self.id}, data={self.data})"
|
||||
)
|
||||
return f"Vertex(display_name={self.display_name}, id={self.id}, data={self.data})"
|
||||
|
||||
def __eq__(self, __o: object) -> bool:
|
||||
try:
|
||||
|
|
@ -770,8 +710,4 @@ class Vertex:
|
|||
|
||||
def _built_object_repr(self):
|
||||
# Add a message with an emoji, stars for sucess,
|
||||
return (
|
||||
"Built sucessfully ✨"
|
||||
if self._built_object is not None
|
||||
else "Failed to build 😵💫"
|
||||
)
|
||||
return "Built sucessfully ✨" if self._built_object is not None else "Failed to build 😵💫"
|
||||
|
|
|
|||
|
|
@ -123,11 +123,9 @@ class DocumentLoaderVertex(Vertex):
|
|||
# show how many documents are in the list?
|
||||
|
||||
if not isinstance(self._built_object, UnbuiltObject):
|
||||
avg_length = sum(
|
||||
len(record.text)
|
||||
for record in self._built_object
|
||||
if hasattr(record, "text")
|
||||
) / len(self._built_object)
|
||||
avg_length = sum(len(record.text) for record in self._built_object if hasattr(record, "text")) / len(
|
||||
self._built_object
|
||||
)
|
||||
return f"""{self.display_name}({len(self._built_object)} records)
|
||||
\nAvg. Record Length (characters): {int(avg_length)}
|
||||
Records: {self._built_object[:3]}..."""
|
||||
|
|
@ -200,9 +198,7 @@ class TextSplitterVertex(Vertex):
|
|||
# show how many documents are in the list?
|
||||
|
||||
if not isinstance(self._built_object, UnbuiltObject):
|
||||
avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(
|
||||
self._built_object
|
||||
)
|
||||
avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(self._built_object)
|
||||
return f"""{self.vertex_type}({len(self._built_object)} documents)
|
||||
\nAvg. Document Length (characters): {int(avg_length)}
|
||||
\nDocuments: {self._built_object[:3]}..."""
|
||||
|
|
@ -249,27 +245,18 @@ class PromptVertex(Vertex):
|
|||
user_id = kwargs.get("user_id", None)
|
||||
tools = kwargs.get("tools", [])
|
||||
if not self._built or force:
|
||||
if (
|
||||
"input_variables" not in self.params
|
||||
or self.params["input_variables"] is None
|
||||
):
|
||||
if "input_variables" not in self.params or self.params["input_variables"] is None:
|
||||
self.params["input_variables"] = []
|
||||
# Check if it is a ZeroShotPrompt and needs a tool
|
||||
if "ShotPrompt" in self.vertex_type:
|
||||
tools = (
|
||||
[tool_node.build(user_id=user_id) for tool_node in tools]
|
||||
if tools is not None
|
||||
else []
|
||||
)
|
||||
tools = [tool_node.build(user_id=user_id) for tool_node in tools] if tools is not None else []
|
||||
# flatten the list of tools if it is a list of lists
|
||||
# first check if it is a list
|
||||
if tools and isinstance(tools, list) and isinstance(tools[0], list):
|
||||
tools = flatten_list(tools)
|
||||
self.params["tools"] = tools
|
||||
prompt_params = [
|
||||
key
|
||||
for key, value in self.params.items()
|
||||
if isinstance(value, str) and key != "format_instructions"
|
||||
key for key, value in self.params.items() if isinstance(value, str) and key != "format_instructions"
|
||||
]
|
||||
else:
|
||||
prompt_params = ["template"]
|
||||
|
|
@ -279,20 +266,14 @@ class PromptVertex(Vertex):
|
|||
prompt_text = self.params[param]
|
||||
variables = extract_input_variables_from_prompt(prompt_text)
|
||||
self.params["input_variables"].extend(variables)
|
||||
self.params["input_variables"] = list(
|
||||
set(self.params["input_variables"])
|
||||
)
|
||||
self.params["input_variables"] = list(set(self.params["input_variables"]))
|
||||
elif isinstance(self.params, dict):
|
||||
self.params.pop("input_variables", None)
|
||||
|
||||
await self._build(user_id=user_id)
|
||||
|
||||
def _built_object_repr(self):
|
||||
if (
|
||||
not self.artifacts
|
||||
or self._built_object is None
|
||||
or not hasattr(self._built_object, "format")
|
||||
):
|
||||
if not self.artifacts or self._built_object is None or not hasattr(self._built_object, "format"):
|
||||
return super()._built_object_repr()
|
||||
elif isinstance(self._built_object, UnbuiltObject):
|
||||
return super()._built_object_repr()
|
||||
|
|
@ -304,9 +285,7 @@ class PromptVertex(Vertex):
|
|||
# so the prompt format doesn't break
|
||||
artifacts.pop("handle_keys", None)
|
||||
try:
|
||||
if not hasattr(self._built_object, "template") and hasattr(
|
||||
self._built_object, "prompt"
|
||||
):
|
||||
if not hasattr(self._built_object, "template") and hasattr(self._built_object, "prompt"):
|
||||
template = self._built_object.prompt.template
|
||||
else:
|
||||
template = self._built_object.template
|
||||
|
|
@ -314,11 +293,7 @@ class PromptVertex(Vertex):
|
|||
if value:
|
||||
replace_key = "{" + key + "}"
|
||||
template = template.replace(replace_key, value)
|
||||
return (
|
||||
template
|
||||
if isinstance(template, str)
|
||||
else f"{self.vertex_type}({template})"
|
||||
)
|
||||
return template if isinstance(template, str) else f"{self.vertex_type}({template})"
|
||||
except KeyError:
|
||||
return str(self._built_object)
|
||||
|
||||
|
|
|
|||
|
|
@ -18,10 +18,7 @@ STARTER_FOLDER_NAME = "Starter Projects"
|
|||
# can use them as a starting point for their own projects.
|
||||
|
||||
|
||||
def update_projects_components_with_latest_component_versions(
|
||||
project_data, all_types_dict
|
||||
):
|
||||
|
||||
def update_projects_components_with_latest_component_versions(project_data, all_types_dict):
|
||||
# project data has a nodes key, which is a list of nodes
|
||||
# we want to run through each node and see if it exists in the all_types_dict
|
||||
# if so, we go into the template key and also get the template from all_types_dict
|
||||
|
|
@ -148,13 +145,9 @@ def create_or_update_starter_projects():
|
|||
project_icon,
|
||||
project_icon_bg_color,
|
||||
) = get_project_data(project)
|
||||
project_data = update_projects_components_with_latest_component_versions(
|
||||
project_data, all_types_dict
|
||||
)
|
||||
project_data = update_projects_components_with_latest_component_versions(project_data, all_types_dict)
|
||||
if project_name and project_data:
|
||||
for existing_project in get_all_flows_similar_to_project(
|
||||
session, project_name
|
||||
):
|
||||
for existing_project in get_all_flows_similar_to_project(session, project_name):
|
||||
session.delete(existing_project)
|
||||
|
||||
create_new_project(
|
||||
|
|
|
|||
|
|
@ -99,9 +99,7 @@ class CodeParser:
|
|||
elif isinstance(node, ast.ImportFrom):
|
||||
for alias in node.names:
|
||||
if alias.asname:
|
||||
self.data["imports"].append(
|
||||
(node.module, f"{alias.name} as {alias.asname}")
|
||||
)
|
||||
self.data["imports"].append((node.module, f"{alias.name} as {alias.asname}"))
|
||||
else:
|
||||
self.data["imports"].append((node.module, alias.name))
|
||||
|
||||
|
|
@ -150,9 +148,7 @@ class CodeParser:
|
|||
return_type = None
|
||||
if node.returns:
|
||||
return_type_str = ast.unparse(node.returns)
|
||||
eval_env = self.construct_eval_env(
|
||||
return_type_str, tuple(self.data["imports"])
|
||||
)
|
||||
eval_env = self.construct_eval_env(return_type_str, tuple(self.data["imports"]))
|
||||
|
||||
try:
|
||||
return_type = eval(return_type_str, eval_env)
|
||||
|
|
@ -194,22 +190,14 @@ class CodeParser:
|
|||
num_defaults = len(node.args.defaults)
|
||||
num_missing_defaults = num_args - num_defaults
|
||||
missing_defaults = [MissingDefault()] * num_missing_defaults
|
||||
default_values = [
|
||||
ast.unparse(default).strip("'") if default else None
|
||||
for default in node.args.defaults
|
||||
]
|
||||
default_values = [ast.unparse(default).strip("'") if default else None for default in node.args.defaults]
|
||||
# Now check all default values to see if there
|
||||
# are any "None" values in the middle
|
||||
default_values = [
|
||||
None if value == "None" else value for value in default_values
|
||||
]
|
||||
default_values = [None if value == "None" else value for value in default_values]
|
||||
|
||||
defaults = missing_defaults + default_values
|
||||
|
||||
args = [
|
||||
self.parse_arg(arg, default)
|
||||
for arg, default in zip(node.args.args, defaults)
|
||||
]
|
||||
args = [self.parse_arg(arg, default) for arg, default in zip(node.args.args, defaults)]
|
||||
return args
|
||||
|
||||
def parse_varargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
|
||||
|
|
@ -227,17 +215,11 @@ class CodeParser:
|
|||
"""
|
||||
Parses the keyword-only arguments of a function or method node.
|
||||
"""
|
||||
kw_defaults = [None] * (
|
||||
len(node.args.kwonlyargs) - len(node.args.kw_defaults)
|
||||
) + [
|
||||
ast.unparse(default) if default else None
|
||||
for default in node.args.kw_defaults
|
||||
kw_defaults = [None] * (len(node.args.kwonlyargs) - len(node.args.kw_defaults)) + [
|
||||
ast.unparse(default) if default else None for default in node.args.kw_defaults
|
||||
]
|
||||
|
||||
args = [
|
||||
self.parse_arg(arg, default)
|
||||
for arg, default in zip(node.args.kwonlyargs, kw_defaults)
|
||||
]
|
||||
args = [self.parse_arg(arg, default) for arg, default in zip(node.args.kwonlyargs, kw_defaults)]
|
||||
return args
|
||||
|
||||
def parse_kwargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
|
||||
|
|
@ -341,9 +323,7 @@ class CodeParser:
|
|||
Extracts global variables from the code.
|
||||
"""
|
||||
global_var = {
|
||||
"targets": [
|
||||
t.id if hasattr(t, "id") else ast.dump(t) for t in node.targets
|
||||
],
|
||||
"targets": [t.id if hasattr(t, "id") else ast.dump(t) for t in node.targets],
|
||||
"value": ast.unparse(node.value),
|
||||
}
|
||||
self.data["global_vars"].append(global_var)
|
||||
|
|
|
|||
|
|
@ -80,9 +80,7 @@ class CustomComponent(Component):
|
|||
if not self.vertex:
|
||||
raise ValueError("Vertex is not set")
|
||||
try:
|
||||
self.vertex.graph.update_state(
|
||||
name=name, record=value, caller=self.vertex.id
|
||||
)
|
||||
self.vertex.graph.update_state(name=name, record=value, caller=self.vertex.id)
|
||||
except Exception as e:
|
||||
raise ValueError(f"Error updating state: {e}")
|
||||
|
||||
|
|
@ -90,9 +88,7 @@ class CustomComponent(Component):
|
|||
if not self.vertex:
|
||||
raise ValueError("Vertex is not set")
|
||||
try:
|
||||
self.vertex.graph.append_state(
|
||||
name=name, record=value, caller=self.vertex.id
|
||||
)
|
||||
self.vertex.graph.append_state(name=name, record=value, caller=self.vertex.id)
|
||||
except Exception as e:
|
||||
raise ValueError(f"Error appending state: {e}")
|
||||
|
||||
|
|
@ -158,9 +154,7 @@ class CustomComponent(Component):
|
|||
def tree(self):
|
||||
return self.get_code_tree(self.code or "")
|
||||
|
||||
def to_records(
|
||||
self, data: Any, keys: Optional[List[str]] = None, silent_errors: bool = False
|
||||
) -> List[Record]:
|
||||
def to_records(self, data: Any, keys: Optional[List[str]] = None, silent_errors: bool = False) -> List[Record]:
|
||||
"""
|
||||
Converts input data into a list of Record objects.
|
||||
|
||||
|
|
@ -211,9 +205,7 @@ class CustomComponent(Component):
|
|||
|
||||
return records
|
||||
|
||||
def create_references_from_records(
|
||||
self, records: List[Record], include_data: bool = False
|
||||
) -> str:
|
||||
def create_references_from_records(self, records: List[Record], include_data: bool = False) -> str:
|
||||
"""
|
||||
Create references from a list of records.
|
||||
|
||||
|
|
@ -252,20 +244,14 @@ class CustomComponent(Component):
|
|||
if not self.code:
|
||||
return {}
|
||||
|
||||
component_classes = [
|
||||
cls
|
||||
for cls in self.tree["classes"]
|
||||
if self.code_class_base_inheritance in cls["bases"]
|
||||
]
|
||||
component_classes = [cls for cls in self.tree["classes"] if self.code_class_base_inheritance in cls["bases"]]
|
||||
if not component_classes:
|
||||
return {}
|
||||
|
||||
# Assume the first Component class is the one we're interested in
|
||||
component_class = component_classes[0]
|
||||
build_methods = [
|
||||
method
|
||||
for method in component_class["methods"]
|
||||
if method["name"] == self.function_entrypoint_name
|
||||
method for method in component_class["methods"] if method["name"] == self.function_entrypoint_name
|
||||
]
|
||||
|
||||
return build_methods[0] if build_methods else {}
|
||||
|
|
@ -322,9 +308,7 @@ class CustomComponent(Component):
|
|||
# Retrieve and decrypt the credential by name for the current user
|
||||
db_service = get_db_service()
|
||||
with session_getter(db_service) as session:
|
||||
return credential_service.get_credential(
|
||||
user_id=self._user_id or "", name=name, session=session
|
||||
)
|
||||
return credential_service.get_credential(user_id=self._user_id or "", name=name, session=session)
|
||||
|
||||
return get_credential
|
||||
|
||||
|
|
@ -334,9 +318,7 @@ class CustomComponent(Component):
|
|||
credential_service = get_credential_service()
|
||||
db_service = get_db_service()
|
||||
with session_getter(db_service) as session:
|
||||
return credential_service.list_credentials(
|
||||
user_id=self._user_id, session=session
|
||||
)
|
||||
return credential_service.list_credentials(user_id=self._user_id, session=session)
|
||||
|
||||
def index(self, value: int = 0):
|
||||
"""Returns a function that returns the value at the given index in the iterable."""
|
||||
|
|
@ -375,11 +357,7 @@ class CustomComponent(Component):
|
|||
if not self._flows_records:
|
||||
self.list_flows()
|
||||
if not flow_id and self._flows_records:
|
||||
flow_ids = [
|
||||
flow.data["id"]
|
||||
for flow in self._flows_records
|
||||
if flow.data["name"] == flow_name
|
||||
]
|
||||
flow_ids = [flow.data["id"] for flow in self._flows_records if flow.data["name"] == flow_name]
|
||||
if not flow_ids:
|
||||
raise ValueError(f"Flow {flow_name} not found")
|
||||
elif len(flow_ids) > 1:
|
||||
|
|
@ -401,9 +379,7 @@ class CustomComponent(Component):
|
|||
db_service = get_db_service()
|
||||
with get_session(db_service) as session:
|
||||
flows = session.exec(
|
||||
select(Flow)
|
||||
.where(Flow.user_id == self._user_id)
|
||||
.where(Flow.is_component == False) # noqa
|
||||
select(Flow).where(Flow.user_id == self._user_id).where(Flow.is_component == False) # noqa
|
||||
).all()
|
||||
|
||||
flows_records = [flow.to_record() for flow in flows]
|
||||
|
|
|
|||
|
|
@ -34,18 +34,14 @@ class UpdateBuildConfigError(Exception):
|
|||
pass
|
||||
|
||||
|
||||
def add_output_types(
|
||||
frontend_node: CustomComponentFrontendNode, return_types: List[str]
|
||||
):
|
||||
def add_output_types(frontend_node: CustomComponentFrontendNode, return_types: List[str]):
|
||||
"""Add output types to the frontend node"""
|
||||
for return_type in return_types:
|
||||
if return_type is None:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail={
|
||||
"error": (
|
||||
"Invalid return type. Please check your code and try again."
|
||||
),
|
||||
"error": ("Invalid return type. Please check your code and try again."),
|
||||
"traceback": traceback.format_exc(),
|
||||
},
|
||||
)
|
||||
|
|
@ -77,18 +73,14 @@ def reorder_fields(frontend_node: CustomComponentFrontendNode, field_order: List
|
|||
frontend_node.field_order = field_order
|
||||
|
||||
|
||||
def add_base_classes(
|
||||
frontend_node: CustomComponentFrontendNode, return_types: List[str]
|
||||
):
|
||||
def add_base_classes(frontend_node: CustomComponentFrontendNode, return_types: List[str]):
|
||||
"""Add base classes to the frontend node"""
|
||||
for return_type_instance in return_types:
|
||||
if return_type_instance is None:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail={
|
||||
"error": (
|
||||
"Invalid return type. Please check your code and try again."
|
||||
),
|
||||
"error": ("Invalid return type. Please check your code and try again."),
|
||||
"traceback": traceback.format_exc(),
|
||||
},
|
||||
)
|
||||
|
|
@ -123,9 +115,7 @@ def get_field_properties(extra_field):
|
|||
# a required field is a field that does not contain
|
||||
# optional in field_type
|
||||
# and a field that does not have a default value
|
||||
field_required = "optional" not in field_type.lower() and isinstance(
|
||||
field_value, MissingDefault
|
||||
)
|
||||
field_required = "optional" not in field_type.lower() and isinstance(field_value, MissingDefault)
|
||||
field_value = field_value if not isinstance(field_value, MissingDefault) else None
|
||||
|
||||
if not field_required:
|
||||
|
|
@ -171,14 +161,10 @@ def add_new_custom_field(
|
|||
# If options is a list, then it's a dropdown
|
||||
# If options is None, then it's a list of strings
|
||||
is_list = isinstance(field_config.get("options"), list)
|
||||
field_config["is_list"] = (
|
||||
is_list or field_config.get("list", False) or field_contains_list
|
||||
)
|
||||
field_config["is_list"] = is_list or field_config.get("list", False) or field_contains_list
|
||||
|
||||
if "name" in field_config:
|
||||
warnings.warn(
|
||||
"The 'name' key in field_config is used to build the object and can't be changed."
|
||||
)
|
||||
warnings.warn("The 'name' key in field_config is used to build the object and can't be changed.")
|
||||
required = field_config.pop("required", field_required)
|
||||
placeholder = field_config.pop("placeholder", "")
|
||||
|
||||
|
|
@ -217,9 +203,7 @@ def add_extra_fields(frontend_node, field_config, function_args):
|
|||
]:
|
||||
continue
|
||||
|
||||
field_name, field_type, field_value, field_required = get_field_properties(
|
||||
extra_field
|
||||
)
|
||||
field_name, field_type, field_value, field_required = get_field_properties(extra_field)
|
||||
config = _field_config.pop(field_name, {})
|
||||
frontend_node = add_new_custom_field(
|
||||
frontend_node,
|
||||
|
|
@ -229,17 +213,13 @@ def add_extra_fields(frontend_node, field_config, function_args):
|
|||
field_required,
|
||||
config,
|
||||
)
|
||||
if "kwargs" in function_args_names and not all(
|
||||
key in function_args_names for key in field_config.keys()
|
||||
):
|
||||
if "kwargs" in function_args_names and not all(key in function_args_names for key in field_config.keys()):
|
||||
for field_name, field_config in _field_config.copy().items():
|
||||
if "name" not in field_config or field_name == "code":
|
||||
continue
|
||||
config = _field_config.get(field_name, {})
|
||||
config = config.model_dump() if isinstance(config, BaseModel) else config
|
||||
field_name, field_type, field_value, field_required = get_field_properties(
|
||||
extra_field=config
|
||||
)
|
||||
field_name, field_type, field_value, field_required = get_field_properties(extra_field=config)
|
||||
frontend_node = add_new_custom_field(
|
||||
frontend_node,
|
||||
field_name,
|
||||
|
|
@ -277,9 +257,7 @@ def run_build_config(
|
|||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail={
|
||||
"error": (
|
||||
"Invalid type convertion. Please check your code and try again."
|
||||
),
|
||||
"error": ("Invalid type convertion. Please check your code and try again."),
|
||||
"traceback": traceback.format_exc(),
|
||||
},
|
||||
) from exc
|
||||
|
|
@ -329,7 +307,6 @@ def run_build_config(
|
|||
return build_config, custom_instance
|
||||
|
||||
except Exception as exc:
|
||||
|
||||
logger.error(f"Error while building field config: {str(exc)}")
|
||||
if hasattr(exc, "detail") and "traceback" in exc.detail:
|
||||
logger.error(exc.detail["traceback"])
|
||||
|
|
@ -358,7 +335,6 @@ def build_frontend_node(template_config):
|
|||
|
||||
|
||||
def add_code_field(frontend_node: CustomComponentFrontendNode, raw_code, field_config):
|
||||
|
||||
code_field = TemplateField(
|
||||
dynamic=True,
|
||||
required=True,
|
||||
|
|
@ -397,16 +373,10 @@ def build_custom_component_template(
|
|||
|
||||
add_extra_fields(frontend_node, field_config, entrypoint_args)
|
||||
|
||||
frontend_node = add_code_field(
|
||||
frontend_node, custom_component.code, field_config.get("code", {})
|
||||
)
|
||||
frontend_node = add_code_field(frontend_node, custom_component.code, field_config.get("code", {}))
|
||||
|
||||
add_base_classes(
|
||||
frontend_node, custom_component.get_function_entrypoint_return_type
|
||||
)
|
||||
add_output_types(
|
||||
frontend_node, custom_component.get_function_entrypoint_return_type
|
||||
)
|
||||
add_base_classes(frontend_node, custom_component.get_function_entrypoint_return_type)
|
||||
add_output_types(frontend_node, custom_component.get_function_entrypoint_return_type)
|
||||
|
||||
reorder_fields(frontend_node, custom_instance._get_field_order())
|
||||
|
||||
|
|
@ -417,9 +387,7 @@ def build_custom_component_template(
|
|||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail={
|
||||
"error": (
|
||||
f"Something went wrong while building the custom component. Hints: {str(exc)}"
|
||||
),
|
||||
"error": (f"Something went wrong while building the custom component. Hints: {str(exc)}"),
|
||||
"traceback": traceback.format_exc(),
|
||||
},
|
||||
) from exc
|
||||
|
|
@ -455,9 +423,7 @@ def build_custom_components(components_paths: List[str]):
|
|||
custom_component_dict = build_custom_component_list_from_path(path_str)
|
||||
if custom_component_dict:
|
||||
category = next(iter(custom_component_dict))
|
||||
logger.info(
|
||||
f"Loading {len(custom_component_dict[category])} component(s) from category {category}"
|
||||
)
|
||||
logger.info(f"Loading {len(custom_component_dict[category])} component(s) from category {category}")
|
||||
custom_components_from_file = merge_nested_dicts_with_renaming(
|
||||
custom_components_from_file, custom_component_dict
|
||||
)
|
||||
|
|
@ -492,9 +458,7 @@ def update_field_dict(
|
|||
build_config = dd_build_config
|
||||
except Exception as exc:
|
||||
logger.error(f"Error while running update_build_config: {str(exc)}")
|
||||
raise UpdateBuildConfigError(
|
||||
f"Error while running update_build_config: {str(exc)}"
|
||||
) from exc
|
||||
raise UpdateBuildConfigError(f"Error while running update_build_config: {str(exc)}") from exc
|
||||
|
||||
# Let's check if "range_spec" is a RangeSpec object
|
||||
if "rangeSpec" in field_dict and isinstance(field_dict["rangeSpec"], RangeSpec):
|
||||
|
|
|
|||
|
|
@ -145,13 +145,9 @@ async def instantiate_based_on_type(
|
|||
return class_object(**params)
|
||||
|
||||
|
||||
async def instantiate_custom_component(
|
||||
node_type, class_object, params, user_id, vertex
|
||||
):
|
||||
async def instantiate_custom_component(node_type, class_object, params, user_id, vertex):
|
||||
params_copy = params.copy()
|
||||
class_object: Type["CustomComponent"] = eval_custom_component_code(
|
||||
params_copy.pop("code")
|
||||
)
|
||||
class_object: Type["CustomComponent"] = eval_custom_component_code(params_copy.pop("code"))
|
||||
custom_component: "CustomComponent" = class_object(
|
||||
user_id=user_id,
|
||||
parameters=params_copy,
|
||||
|
|
@ -227,9 +223,7 @@ def instantiate_memory(node_type, class_object, params):
|
|||
# I want to catch a specific attribute error that happens
|
||||
# when the object does not have a cursor attribute
|
||||
except Exception as exc:
|
||||
if "object has no attribute 'cursor'" in str(
|
||||
exc
|
||||
) or 'object has no field "conn"' in str(exc):
|
||||
if "object has no attribute 'cursor'" in str(exc) or 'object has no field "conn"' in str(exc):
|
||||
raise AttributeError(
|
||||
(
|
||||
"Failed to build connection to database."
|
||||
|
|
@ -272,9 +266,7 @@ def instantiate_agent(node_type, class_object: Type[agent_module.Agent], params:
|
|||
if class_method := getattr(class_object, method, None):
|
||||
agent = class_method(**params)
|
||||
tools = params.get("tools", [])
|
||||
return AgentExecutor.from_agent_and_tools(
|
||||
agent=agent, tools=tools, handle_parsing_errors=True
|
||||
)
|
||||
return AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, handle_parsing_errors=True)
|
||||
return load_agent_executor(class_object, params)
|
||||
|
||||
|
||||
|
|
@ -330,11 +322,7 @@ def instantiate_embedding(node_type, class_object, params: Dict):
|
|||
try:
|
||||
return class_object(**params)
|
||||
except ValidationError:
|
||||
params = {
|
||||
key: value
|
||||
for key, value in params.items()
|
||||
if key in class_object.model_fields
|
||||
}
|
||||
params = {key: value for key, value in params.items() if key in class_object.model_fields}
|
||||
return class_object(**params)
|
||||
|
||||
|
||||
|
|
@ -346,9 +334,7 @@ def instantiate_vectorstore(class_object: Type[VectorStore], params: Dict):
|
|||
if "texts" in params:
|
||||
params["documents"] = params.pop("texts")
|
||||
if "documents" in params:
|
||||
params["documents"] = [
|
||||
doc for doc in params["documents"] if isinstance(doc, Document)
|
||||
]
|
||||
params["documents"] = [doc for doc in params["documents"] if isinstance(doc, Document)]
|
||||
if initializer := vecstore_initializer.get(class_object.__name__):
|
||||
vecstore = initializer(class_object, params)
|
||||
else:
|
||||
|
|
@ -363,9 +349,7 @@ def instantiate_vectorstore(class_object: Type[VectorStore], params: Dict):
|
|||
return vecstore
|
||||
|
||||
|
||||
def instantiate_documentloader(
|
||||
node_type: str, class_object: Type[BaseLoader], params: Dict
|
||||
):
|
||||
def instantiate_documentloader(node_type: str, class_object: Type[BaseLoader], params: Dict):
|
||||
if "file_filter" in params:
|
||||
# file_filter will be a string but we need a function
|
||||
# that will be used to filter the files using file_filter
|
||||
|
|
@ -374,17 +358,13 @@ def instantiate_documentloader(
|
|||
# in x and if it is, we will return True
|
||||
file_filter = params.pop("file_filter")
|
||||
extensions = file_filter.split(",")
|
||||
params["file_filter"] = lambda x: any(
|
||||
extension.strip() in x for extension in extensions
|
||||
)
|
||||
params["file_filter"] = lambda x: any(extension.strip() in x for extension in extensions)
|
||||
metadata = params.pop("metadata", None)
|
||||
if metadata and isinstance(metadata, str):
|
||||
try:
|
||||
metadata = orjson.loads(metadata)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise ValueError(
|
||||
"The metadata you provided is not a valid JSON string."
|
||||
) from exc
|
||||
raise ValueError("The metadata you provided is not a valid JSON string.") from exc
|
||||
|
||||
if node_type == "WebBaseLoader":
|
||||
if web_path := params.pop("web_path", None):
|
||||
|
|
@ -417,9 +397,7 @@ def instantiate_textsplitter(
|
|||
"Try changing the chunk_size of the Text Splitter."
|
||||
) from exc
|
||||
|
||||
if (
|
||||
"separator_type" in params and params["separator_type"] == "Text"
|
||||
) or "separator_type" not in params:
|
||||
if ("separator_type" in params and params["separator_type"] == "Text") or "separator_type" not in params:
|
||||
params.pop("separator_type", None)
|
||||
# separators might come in as an escaped string like \\n
|
||||
# so we need to convert it to a string
|
||||
|
|
@ -427,9 +405,7 @@ def instantiate_textsplitter(
|
|||
if isinstance(params["separators"], str):
|
||||
params["separators"] = unescape_string(params["separators"])
|
||||
elif isinstance(params["separators"], list):
|
||||
params["separators"] = [
|
||||
unescape_string(separator) for separator in params["separators"]
|
||||
]
|
||||
params["separators"] = [unescape_string(separator) for separator in params["separators"]]
|
||||
text_splitter = class_object(**params)
|
||||
else:
|
||||
from langchain.text_splitter import Language
|
||||
|
|
@ -456,8 +432,7 @@ def replace_zero_shot_prompt_with_prompt_template(nodes):
|
|||
tools = [
|
||||
tool
|
||||
for tool in nodes
|
||||
if tool["type"] != "chatOutputNode"
|
||||
and "Tool" in tool["data"]["node"]["base_classes"]
|
||||
if tool["type"] != "chatOutputNode" and "Tool" in tool["data"]["node"]["base_classes"]
|
||||
]
|
||||
node["data"] = build_prompt_template(prompt=node["data"], tools=tools)
|
||||
break
|
||||
|
|
@ -471,9 +446,7 @@ def load_agent_executor(agent_class: type[agent_module.Agent], params, **kwargs)
|
|||
# agent has hidden args for memory. might need to be support
|
||||
# memory = params["memory"]
|
||||
# if allowed_tools is not a list or set, make it a list
|
||||
if not isinstance(allowed_tools, (list, set)) and isinstance(
|
||||
allowed_tools, BaseTool
|
||||
):
|
||||
if not isinstance(allowed_tools, (list, set)) and isinstance(allowed_tools, BaseTool):
|
||||
allowed_tools = [allowed_tools]
|
||||
tool_names = [tool.name for tool in allowed_tools]
|
||||
# Agent class requires an output_parser but Agent classes
|
||||
|
|
@ -501,10 +474,7 @@ def build_prompt_template(prompt, tools):
|
|||
format_instructions = prompt["node"]["template"]["format_instructions"]["value"]
|
||||
|
||||
tool_strings = "\n".join(
|
||||
[
|
||||
f"{tool['data']['node']['name']}: {tool['data']['node']['description']}"
|
||||
for tool in tools
|
||||
]
|
||||
[f"{tool['data']['node']['name']}: {tool['data']['node']['description']}" for tool in tools]
|
||||
)
|
||||
tool_names = ", ".join([tool["data"]["node"]["name"] for tool in tools])
|
||||
format_instructions = format_instructions.format(tool_names=tool_names)
|
||||
|
|
|
|||
|
|
@ -66,12 +66,8 @@ def build_langchain_types_dict(): # sourcery skip: dict-assign-update-to-union
|
|||
def get_all_types_dict(components_paths):
|
||||
"""Get all types dictionary combining native and custom components."""
|
||||
native_components = build_langchain_types_dict()
|
||||
custom_components_from_file = build_custom_components(
|
||||
components_paths=components_paths
|
||||
)
|
||||
return merge_nested_dicts_with_renaming(
|
||||
native_components, custom_components_from_file
|
||||
)
|
||||
custom_components_from_file = build_custom_components(components_paths=components_paths)
|
||||
return merge_nested_dicts_with_renaming(native_components, custom_components_from_file)
|
||||
|
||||
|
||||
def get_all_components(components_paths, as_dict=False):
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@ from langflow.graph import Graph
|
|||
from langflow.processing.process import process_tweaks
|
||||
|
||||
|
||||
def load_flow_from_json(
|
||||
flow: Union[Path, str, dict], tweaks: Optional[dict] = None
|
||||
) -> Graph:
|
||||
def load_flow_from_json(flow: Union[Path, str, dict], tweaks: Optional[dict] = None) -> Graph:
|
||||
"""
|
||||
Load flow from a JSON file or a JSON object.
|
||||
|
||||
|
|
@ -25,9 +23,7 @@ def load_flow_from_json(
|
|||
elif isinstance(flow, dict):
|
||||
flow_graph = flow
|
||||
else:
|
||||
raise TypeError(
|
||||
"Input must be either a file path (str) or a JSON object (dict)"
|
||||
)
|
||||
raise TypeError("Input must be either a file path (str) or a JSON object (dict)")
|
||||
|
||||
graph_data = flow_graph["data"]
|
||||
if tweaks is not None:
|
||||
|
|
|
|||
|
|
@ -129,9 +129,7 @@ async def process_runnable(runnable: Runnable, inputs: Union[dict, List[dict]]):
|
|||
elif isinstance(inputs, dict) and hasattr(runnable, "ainvoke"):
|
||||
result = await runnable.ainvoke(inputs)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Runnable {runnable} does not support inputs of type {type(inputs)}"
|
||||
)
|
||||
raise ValueError(f"Runnable {runnable} does not support inputs of type {type(inputs)}")
|
||||
# Check if the result is a list of AIMessages
|
||||
if isinstance(result, list) and all(isinstance(r, AIMessage) for r in result):
|
||||
result = [r.content for r in result]
|
||||
|
|
@ -140,9 +138,7 @@ async def process_runnable(runnable: Runnable, inputs: Union[dict, List[dict]]):
|
|||
return result
|
||||
|
||||
|
||||
async def process_inputs_dict(
|
||||
built_object: Union[Chain, VectorStore, Runnable], inputs: dict
|
||||
):
|
||||
async def process_inputs_dict(built_object: Union[Chain, VectorStore, Runnable], inputs: dict):
|
||||
if isinstance(built_object, Chain):
|
||||
if inputs is None:
|
||||
raise ValueError("Inputs must be provided for a Chain")
|
||||
|
|
@ -177,9 +173,7 @@ async def process_inputs_list(built_object: Runnable, inputs: List[dict]):
|
|||
return await process_runnable(built_object, inputs)
|
||||
|
||||
|
||||
async def generate_result(
|
||||
built_object: Union[Chain, VectorStore, Runnable], inputs: Union[dict, List[dict]]
|
||||
):
|
||||
async def generate_result(built_object: Union[Chain, VectorStore, Runnable], inputs: Union[dict, List[dict]]):
|
||||
if isinstance(inputs, dict):
|
||||
result = await process_inputs_dict(built_object, inputs)
|
||||
elif isinstance(inputs, List) and isinstance(built_object, Runnable):
|
||||
|
|
@ -218,9 +212,7 @@ async def run_graph(
|
|||
else:
|
||||
graph_data = graph._graph_data
|
||||
if session_id is None and session_service is not None:
|
||||
session_id = session_service.generate_key(
|
||||
session_id=flow_id, data_graph=graph_data
|
||||
)
|
||||
session_id = session_service.generate_key(session_id=flow_id, data_graph=graph_data)
|
||||
if inputs is None:
|
||||
inputs = [{}]
|
||||
|
||||
|
|
@ -244,9 +236,7 @@ def validate_input(
|
|||
nodes = graph_data.get("data", {}).get("nodes") or graph_data.get("nodes")
|
||||
|
||||
if not isinstance(nodes, list):
|
||||
raise ValueError(
|
||||
"graph_data should contain a list of nodes under 'data' key or directly under 'nodes' key"
|
||||
)
|
||||
raise ValueError("graph_data should contain a list of nodes under 'data' key or directly under 'nodes' key")
|
||||
|
||||
return nodes
|
||||
|
||||
|
|
@ -255,9 +245,7 @@ def apply_tweaks(node: Dict[str, Any], node_tweaks: Dict[str, Any]) -> None:
|
|||
template_data = node.get("data", {}).get("node", {}).get("template")
|
||||
|
||||
if not isinstance(template_data, dict):
|
||||
logger.warning(
|
||||
f"Template data for node {node.get('id')} should be a dictionary"
|
||||
)
|
||||
logger.warning(f"Template data for node {node.get('id')} should be a dictionary")
|
||||
return
|
||||
|
||||
for tweak_name, tweak_value in node_tweaks.items():
|
||||
|
|
@ -272,9 +260,7 @@ def apply_tweaks_on_vertex(vertex: Vertex, node_tweaks: Dict[str, Any]) -> None:
|
|||
vertex.params[tweak_name] = tweak_value
|
||||
|
||||
|
||||
def process_tweaks(
|
||||
graph_data: Dict[str, Any], tweaks: Union["Tweaks", Dict[str, Dict[str, Any]]]
|
||||
) -> Dict[str, Any]:
|
||||
def process_tweaks(graph_data: Dict[str, Any], tweaks: Union["Tweaks", Dict[str, Dict[str, Any]]]) -> Dict[str, Any]:
|
||||
"""
|
||||
This function is used to tweak the graph data using the node id and the tweaks dict.
|
||||
|
||||
|
|
@ -310,8 +296,6 @@ def process_tweaks_on_graph(graph: Graph, tweaks: Dict[str, Dict[str, Any]]):
|
|||
if node_tweaks := tweaks.get(node_id):
|
||||
apply_tweaks_on_vertex(vertex, node_tweaks)
|
||||
else:
|
||||
logger.warning(
|
||||
"Each node should be a Vertex with an 'id' attribute of type str"
|
||||
)
|
||||
logger.warning("Each node should be a Vertex with an 'id' attribute of type str")
|
||||
|
||||
return graph
|
||||
|
|
|
|||
|
|
@ -63,9 +63,7 @@ class Record(BaseModel):
|
|||
return self.data.get(key, self._default_value)
|
||||
except KeyError:
|
||||
# Fallback to default behavior to raise AttributeError for undefined attributes
|
||||
raise AttributeError(
|
||||
f"'{type(self).__name__}' object has no attribute '{key}'"
|
||||
)
|
||||
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{key}'")
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -22,9 +22,7 @@ class FlowBase(SQLModel):
|
|||
icon_bg_color: Optional[str] = Field(default=None, nullable=True)
|
||||
data: Optional[Dict] = Field(default=None, nullable=True)
|
||||
is_component: Optional[bool] = Field(default=False, nullable=True)
|
||||
updated_at: Optional[datetime] = Field(
|
||||
default_factory=datetime.utcnow, nullable=True
|
||||
)
|
||||
updated_at: Optional[datetime] = Field(default_factory=datetime.utcnow, nullable=True)
|
||||
folder: Optional[str] = Field(default=None, nullable=True)
|
||||
|
||||
@field_validator("icon_bg_color")
|
||||
|
|
|
|||
|
|
@ -10,9 +10,7 @@ if TYPE_CHECKING:
|
|||
|
||||
class TransactionModel(BaseModel):
|
||||
id: Optional[int] = Field(default=None, alias="id")
|
||||
timestamp: Optional[datetime] = Field(
|
||||
default_factory=datetime.now, alias="timestamp"
|
||||
)
|
||||
timestamp: Optional[datetime] = Field(default_factory=datetime.now, alias="timestamp")
|
||||
source: str
|
||||
target: str
|
||||
target_args: dict
|
||||
|
|
@ -53,12 +51,8 @@ class MessageModel(BaseModel):
|
|||
@classmethod
|
||||
def from_record(cls, record: "Record"):
|
||||
# first check if the record has all the required fields
|
||||
if not record.data or (
|
||||
"sender" not in record.data and "sender_name" not in record.data
|
||||
):
|
||||
raise ValueError(
|
||||
"The record does not have the required fields 'sender' and 'sender_name' in the data."
|
||||
)
|
||||
if not record.data or ("sender" not in record.data and "sender_name" not in record.data):
|
||||
raise ValueError("The record does not have the required fields 'sender' and 'sender_name' in the data.")
|
||||
return cls(
|
||||
sender=record.sender,
|
||||
sender_name=record.sender_name,
|
||||
|
|
|
|||
|
|
@ -44,9 +44,7 @@ class MonitorService(Service):
|
|||
|
||||
def ensure_tables_exist(self):
|
||||
for table_name, model in self.table_map.items():
|
||||
drop_and_create_table_if_schema_mismatch(
|
||||
str(self.db_path), table_name, model
|
||||
)
|
||||
drop_and_create_table_if_schema_mismatch(str(self.db_path), table_name, model)
|
||||
|
||||
def add_row(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -74,9 +74,7 @@ class TaskService(Service):
|
|||
result = await result
|
||||
return task.id, result
|
||||
|
||||
async def launch_task(
|
||||
self, task_func: Callable[..., Any], *args: Any, **kwargs: Any
|
||||
) -> Any:
|
||||
async def launch_task(self, task_func: Callable[..., Any], *args: Any, **kwargs: Any) -> Any:
|
||||
logger.debug(f"Launching task {task_func} with args {args} and kwargs {kwargs}")
|
||||
logger.debug(f"Using backend {self.backend}")
|
||||
task = self.backend.launch_task(task_func, *args, **kwargs)
|
||||
|
|
|
|||
|
|
@ -73,9 +73,7 @@ class TemplateField(BaseModel):
|
|||
refresh_button_text: Optional[str] = None
|
||||
"""Specifies the text for the refresh button. Defaults to None."""
|
||||
|
||||
range_spec: Optional[RangeSpec] = Field(
|
||||
default=None, serialization_alias="rangeSpec"
|
||||
)
|
||||
range_spec: Optional[RangeSpec] = Field(default=None, serialization_alias="rangeSpec")
|
||||
"""Range specification for the field. Defaults to None."""
|
||||
|
||||
title_case: bool = False
|
||||
|
|
@ -124,10 +122,6 @@ class TemplateField(BaseModel):
|
|||
if not isinstance(value, list):
|
||||
raise ValueError("file_types must be a list")
|
||||
return [
|
||||
(
|
||||
f".{file_type}"
|
||||
if isinstance(file_type, str) and not file_type.startswith(".")
|
||||
else file_type
|
||||
)
|
||||
(f".{file_type}" if isinstance(file_type, str) and not file_type.startswith(".") else file_type)
|
||||
for file_type in value
|
||||
]
|
||||
|
|
|
|||
|
|
@ -20,12 +20,8 @@ def remove_ansi_escape_codes(text):
|
|||
return re.sub(r"\x1b\[[0-9;]*[a-zA-Z]", "", text)
|
||||
|
||||
|
||||
def build_template_from_function(
|
||||
name: str, type_to_loader_dict: Dict, add_function: bool = False
|
||||
):
|
||||
classes = [
|
||||
item.__annotations__["return"].__name__ for item in type_to_loader_dict.values()
|
||||
]
|
||||
def build_template_from_function(name: str, type_to_loader_dict: Dict, add_function: bool = False):
|
||||
classes = [item.__annotations__["return"].__name__ for item in type_to_loader_dict.values()]
|
||||
|
||||
# Raise error if name is not in chains
|
||||
if name not in classes:
|
||||
|
|
@ -46,10 +42,8 @@ def build_template_from_function(
|
|||
for name_, value_ in value.__repr_args__():
|
||||
if name_ == "default_factory":
|
||||
try:
|
||||
variables[class_field_items]["default"] = (
|
||||
get_default_factory(
|
||||
module=_class.__base__.__module__, function=value_
|
||||
)
|
||||
variables[class_field_items]["default"] = get_default_factory(
|
||||
module=_class.__base__.__module__, function=value_
|
||||
)
|
||||
except Exception:
|
||||
variables[class_field_items]["default"] = None
|
||||
|
|
@ -57,9 +51,7 @@ def build_template_from_function(
|
|||
variables[class_field_items][name_] = value_
|
||||
|
||||
variables[class_field_items]["placeholder"] = (
|
||||
docs.params[class_field_items]
|
||||
if class_field_items in docs.params
|
||||
else ""
|
||||
docs.params[class_field_items] if class_field_items in docs.params else ""
|
||||
)
|
||||
# Adding function to base classes to allow
|
||||
# the output to be a function
|
||||
|
|
@ -74,9 +66,7 @@ def build_template_from_function(
|
|||
}
|
||||
|
||||
|
||||
def build_template_from_class(
|
||||
name: str, type_to_cls_dict: Dict, add_function: bool = False
|
||||
):
|
||||
def build_template_from_class(name: str, type_to_cls_dict: Dict, add_function: bool = False):
|
||||
classes = [item.__name__ for item in type_to_cls_dict.values()]
|
||||
|
||||
# Raise error if name is not in chains
|
||||
|
|
@ -100,11 +90,9 @@ def build_template_from_class(
|
|||
for name_, value_ in value.__repr_args__():
|
||||
if name_ == "default_factory":
|
||||
try:
|
||||
variables[class_field_items]["default"] = (
|
||||
get_default_factory(
|
||||
module=_class.__base__.__module__,
|
||||
function=value_,
|
||||
)
|
||||
variables[class_field_items]["default"] = get_default_factory(
|
||||
module=_class.__base__.__module__,
|
||||
function=value_,
|
||||
)
|
||||
except Exception:
|
||||
variables[class_field_items]["default"] = None
|
||||
|
|
@ -112,9 +100,7 @@ def build_template_from_class(
|
|||
variables[class_field_items][name_] = value_
|
||||
|
||||
variables[class_field_items]["placeholder"] = (
|
||||
docs.params[class_field_items]
|
||||
if class_field_items in docs.params
|
||||
else ""
|
||||
docs.params[class_field_items] if class_field_items in docs.params else ""
|
||||
)
|
||||
base_classes = get_base_classes(_class)
|
||||
# Adding function to base classes to allow
|
||||
|
|
@ -146,9 +132,7 @@ def build_template_from_method(
|
|||
|
||||
# Check if the method exists in this class
|
||||
if not hasattr(_class, method_name):
|
||||
raise ValueError(
|
||||
f"Method {method_name} not found in class {class_name}"
|
||||
)
|
||||
raise ValueError(f"Method {method_name} not found in class {class_name}")
|
||||
|
||||
# Get the method
|
||||
method = getattr(_class, method_name)
|
||||
|
|
@ -167,14 +151,8 @@ def build_template_from_method(
|
|||
"_type": _type,
|
||||
**{
|
||||
name: {
|
||||
"default": (
|
||||
param.default if param.default != param.empty else None
|
||||
),
|
||||
"type": (
|
||||
param.annotation
|
||||
if param.annotation != param.empty
|
||||
else None
|
||||
),
|
||||
"default": (param.default if param.default != param.empty else None),
|
||||
"type": (param.annotation if param.annotation != param.empty else None),
|
||||
"required": param.default == param.empty,
|
||||
}
|
||||
for name, param in params.items()
|
||||
|
|
@ -261,9 +239,7 @@ def sync_to_async(func):
|
|||
return async_wrapper
|
||||
|
||||
|
||||
def format_dict(
|
||||
dictionary: Dict[str, Any], class_name: Optional[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
def format_dict(dictionary: Dict[str, Any], class_name: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Formats a dictionary by removing certain keys and modifying the
|
||||
values of other keys.
|
||||
|
|
@ -349,9 +325,7 @@ def check_list_type(_type: str, value: Dict[str, Any]) -> str:
|
|||
The modified type string.
|
||||
"""
|
||||
if any(list_type in _type for list_type in ["List", "Sequence", "Set"]):
|
||||
_type = (
|
||||
_type.replace("List[", "").replace("Sequence[", "").replace("Set[", "")[:-1]
|
||||
)
|
||||
_type = _type.replace("List[", "").replace("Sequence[", "").replace("Set[", "")[:-1]
|
||||
value["list"] = True
|
||||
else:
|
||||
value["list"] = False
|
||||
|
|
@ -454,9 +428,7 @@ def set_headers_value(value: Dict[str, Any]) -> None:
|
|||
value["value"] = """{"Authorization": "Bearer <token>"}"""
|
||||
|
||||
|
||||
def add_options_to_field(
|
||||
value: Dict[str, Any], class_name: Optional[str], key: str
|
||||
) -> None:
|
||||
def add_options_to_field(value: Dict[str, Any], class_name: Optional[str], key: str) -> None:
|
||||
"""
|
||||
Adds options to the field based on the class name and key.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -173,8 +173,9 @@ export default function GenericNode({
|
|||
const iconColor = nodeColors[types[data.type]];
|
||||
const iconName =
|
||||
iconElement || (data.node?.flow ? "group_components" : name);
|
||||
const iconClassName = `generic-node-icon ${!showNode ? " absolute inset-x-6 h-12 w-12 " : ""
|
||||
}`;
|
||||
const iconClassName = `generic-node-icon ${
|
||||
!showNode ? " absolute inset-x-6 h-12 w-12 " : ""
|
||||
}`;
|
||||
if (iconElement && isEmoji) {
|
||||
return nodeIconFragment(iconElement);
|
||||
} else {
|
||||
|
|
@ -303,7 +304,7 @@ export default function GenericNode({
|
|||
numberOfHandles={handles}
|
||||
showNode={showNode}
|
||||
openAdvancedModal={false}
|
||||
onCloseAdvancedModal={() => { }}
|
||||
onCloseAdvancedModal={() => {}}
|
||||
selected={selected}
|
||||
></NodeToolbarComponent>
|
||||
</NodeToolbar>
|
||||
|
|
@ -426,33 +427,33 @@ export default function GenericNode({
|
|||
data={data}
|
||||
color={
|
||||
data.node?.template[templateField].input_types &&
|
||||
data.node?.template[templateField].input_types!
|
||||
.length > 0
|
||||
data.node?.template[templateField].input_types!
|
||||
.length > 0
|
||||
? nodeColors[
|
||||
data.node?.template[templateField]
|
||||
.input_types![
|
||||
data.node?.template[templateField]
|
||||
.input_types!.length - 1
|
||||
]
|
||||
] ??
|
||||
nodeColors[
|
||||
types[
|
||||
data.node?.template[templateField]
|
||||
.input_types![
|
||||
data.node?.template[templateField]
|
||||
.input_types!.length - 1
|
||||
]
|
||||
]
|
||||
]
|
||||
data.node?.template[templateField]
|
||||
.input_types![
|
||||
data.node?.template[templateField]
|
||||
.input_types!.length - 1
|
||||
]
|
||||
] ??
|
||||
nodeColors[
|
||||
types[
|
||||
data.node?.template[templateField]
|
||||
.input_types![
|
||||
data.node?.template[templateField]
|
||||
.input_types!.length - 1
|
||||
]
|
||||
]
|
||||
]
|
||||
: nodeColors[
|
||||
data.node?.template[templateField].type!
|
||||
] ??
|
||||
nodeColors[
|
||||
types[
|
||||
data.node?.template[templateField].type!
|
||||
]
|
||||
] ??
|
||||
nodeColors.unknown
|
||||
data.node?.template[templateField].type!
|
||||
] ??
|
||||
nodeColors[
|
||||
types[
|
||||
data.node?.template[templateField].type!
|
||||
]
|
||||
] ??
|
||||
nodeColors.unknown
|
||||
}
|
||||
title={getFieldTitle(
|
||||
data.node?.template!,
|
||||
|
|
@ -496,7 +497,7 @@ export default function GenericNode({
|
|||
color={nodeColors[types[data.type]] ?? nodeColors.unknown}
|
||||
title={
|
||||
data.node?.output_types &&
|
||||
data.node.output_types.length > 0
|
||||
data.node.output_types.length > 0
|
||||
? data.node.output_types.join(" | ")
|
||||
: data.type
|
||||
}
|
||||
|
|
@ -515,59 +516,62 @@ export default function GenericNode({
|
|||
</div>
|
||||
{showNode && (
|
||||
<ShadTooltip
|
||||
content={
|
||||
buildStatus === BuildStatus.BUILDING ? (
|
||||
<span> {STATUS_BUILDING} </span>
|
||||
) : !validationStatus ? (
|
||||
<span className="flex">{STATUS_BUILD}</span>
|
||||
) : (
|
||||
<div className="max-h-100 p-2">
|
||||
<div>
|
||||
{lastRunTime && (
|
||||
<div className="justify-left flex text-muted-foreground font-normal">
|
||||
<div>{RUN_TIMESTAMP_PREFIX}</div>
|
||||
<div className="ml-1 text-status-blue">
|
||||
{lastRunTime}
|
||||
content={
|
||||
buildStatus === BuildStatus.BUILDING ? (
|
||||
<span> {STATUS_BUILDING} </span>
|
||||
) : !validationStatus ? (
|
||||
<span className="flex">{STATUS_BUILD}</span>
|
||||
) : (
|
||||
<div className="max-h-100 p-2">
|
||||
<div>
|
||||
{lastRunTime && (
|
||||
<div className="justify-left flex font-normal text-muted-foreground">
|
||||
<div>{RUN_TIMESTAMP_PREFIX}</div>
|
||||
<div className="ml-1 text-status-blue">
|
||||
{lastRunTime}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="justify-left flex font-normal text-muted-foreground">
|
||||
<div>Duration:</div>
|
||||
<div className="mb-3 ml-1 text-status-blue">
|
||||
{validationStatus?.data.duration}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="justify-left flex text-muted-foreground font-normal">
|
||||
<div>Duration:</div>
|
||||
<div className="ml-1 text-status-blue mb-3">
|
||||
{validationStatus?.data.duration}
|
||||
</div>
|
||||
<hr />
|
||||
<span className="mb-2 mt-2 flex justify-center font-semibold text-muted-foreground">
|
||||
Output
|
||||
</span>
|
||||
<div className="max-h-96 overflow-auto font-normal custom-scroll">
|
||||
{validationString.split("\n").map((line, index) => (
|
||||
<div className="font-normal" key={index}>
|
||||
{line}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<span className="flex justify-center text-muted-foreground mt-2 mb-2 font-semibold">
|
||||
Output
|
||||
</span>
|
||||
<div className="max-h-96 overflow-auto custom-scroll font-normal">
|
||||
{validationString.split("\n").map((line, index) => (
|
||||
<div className="font-normal" key={index}>{line}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
side="bottom"
|
||||
>
|
||||
<Button onClick={() => {
|
||||
if (buildStatus === BuildStatus.BUILDING || isBuilding)
|
||||
return;
|
||||
setValidationStatus(null);
|
||||
buildFlow({ stopNodeId: data.id });
|
||||
}} variant="secondary" className={"group h-9 px-1.5"}>
|
||||
<div>
|
||||
|
||||
<div
|
||||
className="generic-node-status-position flex items-center justify-center"
|
||||
>
|
||||
)
|
||||
}
|
||||
side="bottom"
|
||||
>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (buildStatus === BuildStatus.BUILDING || isBuilding)
|
||||
return;
|
||||
setValidationStatus(null);
|
||||
buildFlow({ stopNodeId: data.id });
|
||||
}}
|
||||
variant="secondary"
|
||||
className={"group h-9 px-1.5"}
|
||||
>
|
||||
<div>
|
||||
<div className="generic-node-status-position flex items-center justify-center">
|
||||
{renderIconStatus(buildStatus, validationStatus)}
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
</ShadTooltip>
|
||||
</div>
|
||||
</Button>
|
||||
</ShadTooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -642,7 +646,7 @@ export default function GenericNode({
|
|||
}}
|
||||
>
|
||||
{(data.node?.description === "" || !data.node?.description) &&
|
||||
nameEditable
|
||||
nameEditable
|
||||
? "Double Click to Edit Description"
|
||||
: data.node?.description}
|
||||
</div>
|
||||
|
|
@ -655,7 +659,7 @@ export default function GenericNode({
|
|||
.map((templateField: string, idx) => (
|
||||
<div key={idx}>
|
||||
{data.node!.template[templateField].show &&
|
||||
!data.node!.template[templateField].advanced ? (
|
||||
!data.node!.template[templateField].advanced ? (
|
||||
<ParameterComponent
|
||||
index={idx.toString()}
|
||||
key={scapedJSONStringfy({
|
||||
|
|
@ -669,30 +673,30 @@ export default function GenericNode({
|
|||
data={data}
|
||||
color={
|
||||
data.node?.template[templateField].input_types &&
|
||||
data.node?.template[templateField].input_types!
|
||||
.length > 0
|
||||
data.node?.template[templateField].input_types!
|
||||
.length > 0
|
||||
? nodeColors[
|
||||
data.node?.template[templateField].input_types![
|
||||
data.node?.template[templateField]
|
||||
.input_types!.length - 1
|
||||
]
|
||||
] ??
|
||||
nodeColors[
|
||||
types[
|
||||
data.node?.template[templateField]
|
||||
.input_types![
|
||||
data.node?.template[templateField]
|
||||
.input_types!.length - 1
|
||||
]
|
||||
]
|
||||
]
|
||||
data.node?.template[templateField].input_types![
|
||||
data.node?.template[templateField]
|
||||
.input_types!.length - 1
|
||||
]
|
||||
] ??
|
||||
nodeColors[
|
||||
types[
|
||||
data.node?.template[templateField]
|
||||
.input_types![
|
||||
data.node?.template[templateField]
|
||||
.input_types!.length - 1
|
||||
]
|
||||
]
|
||||
]
|
||||
: nodeColors[
|
||||
data.node?.template[templateField].type!
|
||||
] ??
|
||||
nodeColors[
|
||||
types[data.node?.template[templateField].type!]
|
||||
] ??
|
||||
nodeColors.unknown
|
||||
data.node?.template[templateField].type!
|
||||
] ??
|
||||
nodeColors[
|
||||
types[data.node?.template[templateField].type!]
|
||||
] ??
|
||||
nodeColors.unknown
|
||||
}
|
||||
title={getFieldTitle(
|
||||
data.node?.template!,
|
||||
|
|
@ -744,9 +748,9 @@ export default function GenericNode({
|
|||
data={data}
|
||||
color={
|
||||
(data.node?.output_types &&
|
||||
data.node.output_types.length > 0
|
||||
data.node.output_types.length > 0
|
||||
? nodeColors[data.node.output_types[0]] ??
|
||||
nodeColors[types[data.node.output_types[0]]]
|
||||
nodeColors[types[data.node.output_types[0]]]
|
||||
: nodeColors[types[data.type]]) ?? nodeColors.unknown
|
||||
}
|
||||
title={
|
||||
|
|
|
|||
|
|
@ -20,7 +20,12 @@ import { Badge } from "../ui/badge";
|
|||
import { Button } from "../ui/button";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
||||
|
||||
export default function IOView({ children, open, setOpen, disable }: {
|
||||
export default function IOView({
|
||||
children,
|
||||
open,
|
||||
setOpen,
|
||||
disable,
|
||||
}: {
|
||||
children: JSX.Element;
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
|
|
|
|||
|
|
@ -1,26 +1,25 @@
|
|||
import { Card, CardContent, CardDescription, CardTitle } from "../ui/card";
|
||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import IconComponent from "../genericIconComponent";
|
||||
import { cn } from "../../utils/utils";
|
||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||
import { Card, CardContent, CardDescription, CardTitle } from "../ui/card";
|
||||
|
||||
export default function NewFlowCardComponent() {
|
||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<Card onClick={() => {
|
||||
addFlow(true).then((id) => {
|
||||
navigate("/flow/" + id);
|
||||
});
|
||||
}} className="pt-4 w-80 h-64 cursor-pointer bg-background">
|
||||
<CardContent className="w-full h-full">
|
||||
<div className="bg-dotted-spacing-6 bg-dotted-muted-foreground bg-dotted-radius-px rounded-md bg-muted w-full h-full flex flex-col items-center align-middle justify-center">
|
||||
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardDescription className="px-6 pb-4">
|
||||
<CardTitle className="text-primary text-lg">Blank Flow</CardTitle>
|
||||
</CardDescription>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<Card
|
||||
onClick={() => {
|
||||
addFlow(true).then((id) => {
|
||||
navigate("/flow/" + id);
|
||||
});
|
||||
}}
|
||||
className="h-64 w-80 cursor-pointer bg-background pt-4"
|
||||
>
|
||||
<CardContent className="h-full w-full">
|
||||
<div className="flex h-full w-full flex-col items-center justify-center rounded-md bg-muted align-middle bg-dotted-spacing-6 bg-dotted-muted-foreground bg-dotted-radius-px"></div>
|
||||
</CardContent>
|
||||
<CardDescription className="px-6 pb-4">
|
||||
<CardTitle className="text-lg text-primary">Blank Flow</CardTitle>
|
||||
</CardDescription>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import useFlowStore from "../../stores/flowStore";
|
||||
import { ChatType } from "../../types/chat";
|
||||
import IOView from "../IOview";
|
||||
import ChatTrigger from "../ViewTriggers/chat";
|
||||
import { Transition } from "@headlessui/react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import ApiModal from "../../modals/ApiModal";
|
||||
import ShareModal from "../../modals/shareModal";
|
||||
import useFlowStore from "../../stores/flowStore";
|
||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||
import { useStoreStore } from "../../stores/storeStore";
|
||||
import { ChatType } from "../../types/chat";
|
||||
import { classNames } from "../../utils/utils";
|
||||
import IOView from "../IOview";
|
||||
import ForwardedIconComponent from "../genericIconComponent";
|
||||
import { Separator } from "../ui/separator";
|
||||
import ShareModal from "../../modals/shareModal";
|
||||
import { useStoreStore } from "../../stores/storeStore";
|
||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||
import { classNames } from "../../utils/utils";
|
||||
import ApiModal from "../../modals/ApiModal";
|
||||
|
||||
export default function FlowToolbar({ flow }: ChatType): JSX.Element {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
|
@ -51,7 +50,7 @@ export default function FlowToolbar({ flow }: ChatType): JSX.Element {
|
|||
<button
|
||||
disabled={!hasApiKey || !validApiKey || !hasStore}
|
||||
className={classNames(
|
||||
"relative inline-flex w-full h-full items-center justify-center hover:bg-hover bg-muted hover:bg-background px-5 py-3 text-foreground transition-all duration-500 ease-in-out gap-[4px] text-sm font-semibold ",
|
||||
"relative inline-flex h-full w-full items-center justify-center gap-[4px] bg-muted px-5 py-3 text-sm font-semibold text-foreground transition-all duration-500 ease-in-out hover:bg-background hover:bg-hover ",
|
||||
!hasApiKey || !validApiKey || !hasStore
|
||||
? " button-disable text-muted-foreground "
|
||||
: ""
|
||||
|
|
@ -76,67 +75,83 @@ export default function FlowToolbar({ flow }: ChatType): JSX.Element {
|
|||
return (
|
||||
<>
|
||||
<Transition
|
||||
show={true}
|
||||
appear={true}
|
||||
enter="transition ease-out duration-300"
|
||||
enterFrom="translate-y-96"
|
||||
enterTo="translate-y-0"
|
||||
leave="transition ease-in duration-300"
|
||||
leaveFrom="translate-y-0"
|
||||
leaveTo="translate-y-96"
|
||||
show={true}
|
||||
appear={true}
|
||||
enter="transition ease-out duration-300"
|
||||
enterFrom="translate-y-96"
|
||||
enterTo="translate-y-0"
|
||||
leave="transition ease-in duration-300"
|
||||
leaveFrom="translate-y-0"
|
||||
leaveTo="translate-y-96"
|
||||
>
|
||||
<div
|
||||
className={
|
||||
"shadow-round-btn-shadow hover:shadow-round-btn-shadow message-button-position flex items-center justify-center rounded-sm bg-muted shadow-md transition-all gap-7 border"
|
||||
}
|
||||
>
|
||||
<div className="flex">
|
||||
<div className="flex gap-1 text-medium-indigo rounded-sm transition-all w-full h-full">
|
||||
{hasIO ? (
|
||||
<IOView open={open} setOpen={setOpen} disable={!hasIO}>
|
||||
<div className="relative inline-flex w-full items-center justify-center hover:bg-hover transition-all duration-500 ease-in-out px-5 py-3 text-medium-indigo ease-in-out gap-1 text-sm font-semibold transition-all">
|
||||
<ForwardedIconComponent
|
||||
name="Zap"
|
||||
className={"message-button-icon h-5 w-5 transition-all"}
|
||||
/>
|
||||
Run
|
||||
<div
|
||||
className={
|
||||
"shadow-round-btn-shadow hover:shadow-round-btn-shadow message-button-position flex items-center justify-center gap-7 rounded-sm border bg-muted shadow-md transition-all"
|
||||
}
|
||||
>
|
||||
<div className="flex">
|
||||
<div className="flex h-full w-full gap-1 rounded-sm text-medium-indigo transition-all">
|
||||
{hasIO ? (
|
||||
<IOView open={open} setOpen={setOpen} disable={!hasIO}>
|
||||
<div className="relative inline-flex w-full items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-medium-indigo transition-all transition-all duration-500 ease-in-out ease-in-out hover:bg-hover">
|
||||
<ForwardedIconComponent
|
||||
name="Zap"
|
||||
className={"message-button-icon h-5 w-5 transition-all"}
|
||||
/>
|
||||
Run
|
||||
</div>
|
||||
</IOView>
|
||||
) : (
|
||||
<div
|
||||
className={`relative inline-flex w-full cursor-not-allowed items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-muted-foreground transition-all duration-500 ease-in-out ease-in-out`}
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name="Zap"
|
||||
className={
|
||||
"message-button-icon h-5 w-5 fill-muted-foreground stroke-muted-foreground transition-all"
|
||||
}
|
||||
/>
|
||||
Run
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</IOView>
|
||||
) : (
|
||||
<div className={`relative inline-flex w-full items-center justify-center transition-all duration-500 ease-in-out px-5 py-3 text-muted-foreground ease-in-out gap-1 text-sm font-semibold cursor-not-allowed`}>
|
||||
<ForwardedIconComponent
|
||||
name="Zap"
|
||||
className={"message-button-icon h-5 w-5 transition-all fill-muted-foreground stroke-muted-foreground"}
|
||||
/>
|
||||
Run
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<Separator orientation="vertical" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2 cursor-pointer">
|
||||
{currentFlow && currentFlow.data && (
|
||||
<ApiModal flow={currentFlow}>
|
||||
<div className={classNames("relative inline-flex w-full items-center justify-center hover:bg-hover px-5 py-3 text-foreground transition-all duration-500 ease-in-out gap-1 text-sm font-semibold")}>
|
||||
<div>
|
||||
<Separator orientation="vertical" />
|
||||
</div>
|
||||
<div className="flex cursor-pointer items-center gap-2">
|
||||
{currentFlow && currentFlow.data && (
|
||||
<ApiModal flow={currentFlow}>
|
||||
<div
|
||||
className={classNames(
|
||||
"relative inline-flex w-full items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-foreground transition-all duration-500 ease-in-out hover:bg-hover"
|
||||
)}
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name="Code2"
|
||||
className={" h-5 w-5"}
|
||||
/>
|
||||
API
|
||||
</div>
|
||||
</ApiModal>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<Separator orientation="vertical" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`side-bar-button ${!hasApiKey || !validApiKey || !hasStore ? " cursor-not-allowed" : " cursor-pointer"}`}>{ModalMemo}</div>
|
||||
</ApiModal>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<Separator orientation="vertical" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className={`side-bar-button ${
|
||||
!hasApiKey || !validApiKey || !hasStore
|
||||
? " cursor-not-allowed"
|
||||
: " cursor-pointer"
|
||||
}`}
|
||||
>
|
||||
{ModalMemo}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Transition>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,18 +9,18 @@ import {
|
|||
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Node } from "reactflow";
|
||||
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
|
||||
import { SAVED_HOVER } from "../../../../constants/constants";
|
||||
import ExportModal from "../../../../modals/exportModal";
|
||||
import FlowSettingsModal from "../../../../modals/flowSettingsModal";
|
||||
import useAlertStore from "../../../../stores/alertStore";
|
||||
import useFlowStore from "../../../../stores/flowStore";
|
||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||
import { useStoreStore } from "../../../../stores/storeStore";
|
||||
import { cn } from "../../../../utils/utils";
|
||||
import ShadTooltip from "../../../ShadTooltipComponent";
|
||||
import IconComponent from "../../../genericIconComponent";
|
||||
import { Button } from "../../../ui/button";
|
||||
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
|
||||
import ExportModal from "../../../../modals/exportModal";
|
||||
import { useStoreStore } from "../../../../stores/storeStore";
|
||||
|
||||
export const MenuBar = ({
|
||||
removeFunction,
|
||||
|
|
@ -121,12 +121,15 @@ export const MenuBar = ({
|
|||
<IconComponent name="FileUp" className="header-menu-options " />
|
||||
Import
|
||||
</DropdownMenuItem>
|
||||
<ExportModal>
|
||||
<div className="header-menubar-item">
|
||||
<IconComponent name="FileDown" className="header-menu-options" />
|
||||
Export
|
||||
</div>
|
||||
</ExportModal>
|
||||
<ExportModal>
|
||||
<div className="header-menubar-item">
|
||||
<IconComponent
|
||||
name="FileDown"
|
||||
className="header-menu-options"
|
||||
/>
|
||||
Export
|
||||
</div>
|
||||
</ExportModal>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
undo();
|
||||
|
|
@ -141,7 +144,9 @@ export const MenuBar = ({
|
|||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||
/>
|
||||
) : (
|
||||
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">Ctrl + </span>
|
||||
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">
|
||||
Ctrl +{" "}
|
||||
</span>
|
||||
)}
|
||||
<span className="absolute right-2 top-[0.4em]">Z</span>
|
||||
</DropdownMenuItem>
|
||||
|
|
@ -159,7 +164,9 @@ export const MenuBar = ({
|
|||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||
/>
|
||||
) : (
|
||||
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">Ctrl + </span>
|
||||
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">
|
||||
Ctrl +{" "}
|
||||
</span>
|
||||
)}
|
||||
<span className="absolute right-2 top-[0.4em]">Y</span>
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ export default function KeypairListComponent({
|
|||
|
||||
{isList && index === ref.current.length - 1 ? (
|
||||
<button
|
||||
disabled={disabled}
|
||||
disabled={disabled}
|
||||
onClick={() => {
|
||||
let newInputList = _.cloneDeep(ref.current);
|
||||
newInputList.push({ "": "" });
|
||||
|
|
|
|||
|
|
@ -1,60 +1,107 @@
|
|||
|
||||
import { useNavigate } from "react-router-dom";
|
||||
/// <reference types="vite-plugin-svgr/client" />
|
||||
//@ts-ignore
|
||||
import { ReactComponent as TransferFiles } from "../../assets/undraw_transfer_files_re_a2a9.svg"
|
||||
import { ReactComponent as TransferFiles } from "../../assets/undraw_transfer_files_re_a2a9.svg";
|
||||
//@ts-ignore
|
||||
import { ReactComponent as BasicPrompt } from "../../assets/undraw_design_components_9vy6.svg"
|
||||
import { ReactComponent as BasicPrompt } from "../../assets/undraw_design_components_9vy6.svg";
|
||||
//@ts-ignore
|
||||
import { ReactComponent as ChatWithHistory } from "../../assets/undraw_mobile_messages_re_yx8w.svg"
|
||||
import { ReactComponent as ChatWithHistory } from "../../assets/undraw_mobile_messages_re_yx8w.svg";
|
||||
//@ts-ignore
|
||||
import { ReactComponent as Assistant } from "../../assets/undraw_team_collaboration_re_ow29.svg"
|
||||
import { ReactComponent as Assistant } from "../../assets/undraw_team_collaboration_re_ow29.svg";
|
||||
//@ts-ignore
|
||||
import { ReactComponent as APIRequest } from "../../assets/undraw_real_time_analytics_re_yliv.svg"
|
||||
import { ReactComponent as APIRequest } from "../../assets/undraw_real_time_analytics_re_yliv.svg";
|
||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||
import { FlowType } from "../../types/flow"
|
||||
import { FlowType } from "../../types/flow";
|
||||
import { updateIds } from "../../utils/reactflowUtils";
|
||||
import ShadTooltip from "../ShadTooltipComponent"
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardTitle } from "../ui/card"
|
||||
import { Card, CardContent, CardDescription, CardTitle } from "../ui/card";
|
||||
|
||||
export default function UndrawCardComponent({
|
||||
flow
|
||||
}: { flow: FlowType }) {
|
||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||
const navigate = useNavigate();
|
||||
export default function UndrawCardComponent({ flow }: { flow: FlowType }) {
|
||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||
const navigate = useNavigate();
|
||||
|
||||
function selectImage() {
|
||||
switch (flow.name) {
|
||||
case "Data Ingestion":
|
||||
return <TransferFiles style={{ width: '80%', height: '80%', preserveAspectRatio: 'xMidYMid meet' }} />
|
||||
case "Basic Prompting":
|
||||
return <BasicPrompt style={{ width: '80%', height: '80%', preserveAspectRatio: 'xMidYMid meet' }} />
|
||||
case "Chat with memory":
|
||||
return <ChatWithHistory style={{ width: '70%', height: '70%', preserveAspectRatio: 'xMidYMid meet' }} />
|
||||
case "API requests":
|
||||
return <APIRequest style={{ width: '70%', height: '70%', preserveAspectRatio: 'xMidYMid meet' }} />
|
||||
case "Assistant":
|
||||
return <Assistant style={{ width: '80%', height: '80%', preserveAspectRatio: 'xMidYMid meet' }} />
|
||||
default:
|
||||
return <TransferFiles style={{ width: '80%', height: '80%', preserveAspectRatio: 'xMidYMid meet' }} />
|
||||
}
|
||||
function selectImage() {
|
||||
switch (flow.name) {
|
||||
case "Data Ingestion":
|
||||
return (
|
||||
<TransferFiles
|
||||
style={{
|
||||
width: "80%",
|
||||
height: "80%",
|
||||
preserveAspectRatio: "xMidYMid meet",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case "Basic Prompting":
|
||||
return (
|
||||
<BasicPrompt
|
||||
style={{
|
||||
width: "80%",
|
||||
height: "80%",
|
||||
preserveAspectRatio: "xMidYMid meet",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case "Chat with memory":
|
||||
return (
|
||||
<ChatWithHistory
|
||||
style={{
|
||||
width: "70%",
|
||||
height: "70%",
|
||||
preserveAspectRatio: "xMidYMid meet",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case "API requests":
|
||||
return (
|
||||
<APIRequest
|
||||
style={{
|
||||
width: "70%",
|
||||
height: "70%",
|
||||
preserveAspectRatio: "xMidYMid meet",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
case "Assistant":
|
||||
return (
|
||||
<Assistant
|
||||
style={{
|
||||
width: "80%",
|
||||
height: "80%",
|
||||
preserveAspectRatio: "xMidYMid meet",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<TransferFiles
|
||||
style={{
|
||||
width: "80%",
|
||||
height: "80%",
|
||||
preserveAspectRatio: "xMidYMid meet",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Card onClick={() => {
|
||||
updateIds(flow.data!);
|
||||
addFlow(true, flow).then((id) => {
|
||||
navigate("/flow/" + id);
|
||||
});
|
||||
}} className="pt-4 w-80 h-64 cursor-pointer bg-background">
|
||||
<CardContent className="w-full h-full">
|
||||
<div className="rounded-md p-1 bg-muted w-full h-full flex flex-col items-center align-middle justify-center">
|
||||
{selectImage()}
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardDescription className="px-6 pb-4">
|
||||
<CardTitle className="text-primary text-lg">{flow.name}</CardTitle>
|
||||
</CardDescription>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Card
|
||||
onClick={() => {
|
||||
updateIds(flow.data!);
|
||||
addFlow(true, flow).then((id) => {
|
||||
navigate("/flow/" + id);
|
||||
});
|
||||
}}
|
||||
className="h-64 w-80 cursor-pointer bg-background pt-4"
|
||||
>
|
||||
<CardContent className="h-full w-full">
|
||||
<div className="flex h-full w-full flex-col items-center justify-center rounded-md bg-muted p-1 align-middle">
|
||||
{selectImage()}
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardDescription className="px-6 pb-4">
|
||||
<CardTitle className="text-lg text-primary">{flow.name}</CardTitle>
|
||||
</CardDescription>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -750,14 +750,14 @@ Vector Store
|
|||
Assistant
|
||||
*/
|
||||
|
||||
export const EXAMPLES_MOCK:FlowType[] = [
|
||||
export const EXAMPLES_MOCK: FlowType[] = [
|
||||
{
|
||||
name: "Working with data",
|
||||
id: "Working with data Description",
|
||||
data: {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
viewport: { zoom: 1, x: 1, y: 1 }
|
||||
viewport: { zoom: 1, x: 1, y: 1 },
|
||||
},
|
||||
description: "This flow represents the first process in our application.",
|
||||
folder: STARTER_FOLDER_NAME,
|
||||
|
|
@ -769,7 +769,7 @@ export const EXAMPLES_MOCK:FlowType[] = [
|
|||
data: {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
viewport: { zoom: 1, x: 1, y: 1 }
|
||||
viewport: { zoom: 1, x: 1, y: 1 },
|
||||
},
|
||||
description: "This flow represents the first process in our application.",
|
||||
folder: STARTER_FOLDER_NAME,
|
||||
|
|
@ -781,7 +781,7 @@ export const EXAMPLES_MOCK:FlowType[] = [
|
|||
data: {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
viewport: { zoom: 1, x: 1, y: 1 }
|
||||
viewport: { zoom: 1, x: 1, y: 1 },
|
||||
},
|
||||
description: "This flow represents the first process in our application.",
|
||||
folder: STARTER_FOLDER_NAME,
|
||||
|
|
@ -793,7 +793,7 @@ export const EXAMPLES_MOCK:FlowType[] = [
|
|||
data: {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
viewport: { zoom: 1, x: 1, y: 1 }
|
||||
viewport: { zoom: 1, x: 1, y: 1 },
|
||||
},
|
||||
description: "This flow represents the first process in our application.",
|
||||
folder: STARTER_FOLDER_NAME,
|
||||
|
|
@ -805,7 +805,7 @@ export const EXAMPLES_MOCK:FlowType[] = [
|
|||
data: {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
viewport: { zoom: 1, x: 1, y: 1 }
|
||||
viewport: { zoom: 1, x: 1, y: 1 },
|
||||
},
|
||||
description: "This flow represents the first process in our application.",
|
||||
folder: STARTER_FOLDER_NAME,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import ReactFlow, {
|
|||
updateEdge,
|
||||
} from "reactflow";
|
||||
import GenericNode from "../../../../CustomNodes/GenericNode";
|
||||
import FlowToolbar from "../../../../components/chatComponent";
|
||||
import {
|
||||
INVALID_SELECTION_ERROR_ALERT,
|
||||
UPLOAD_ALERT_LIST,
|
||||
|
|
@ -37,7 +38,6 @@ import { getRandomName, isWrappedWithClass } from "../../../../utils/utils";
|
|||
import ConnectionLineComponent from "../ConnectionLineComponent";
|
||||
import SelectionMenu from "../SelectionMenuComponent";
|
||||
import ExtraSidebar from "../extraSidebarComponent";
|
||||
import FlowToolbar from "../../../../components/chatComponent";
|
||||
|
||||
const nodeTypes = {
|
||||
genericNode: GenericNode,
|
||||
|
|
|
|||
|
|
@ -267,7 +267,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
<>
|
||||
{index === 0 && (
|
||||
<div className="pt-0.5">
|
||||
<div className="p-2 px-4 font-semibold text-sm" key={index}>
|
||||
<div className="p-2 px-4 text-sm font-semibold" key={index}>
|
||||
Native Components
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -283,7 +283,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
{/* BUG ON THIS ICON */}
|
||||
<SparklesIcon
|
||||
strokeWidth={1.5}
|
||||
className="text-primary w-[22px]"
|
||||
className="w-[22px] text-primary"
|
||||
/>
|
||||
|
||||
<span className="components-disclosure-title">
|
||||
|
|
@ -296,7 +296,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div className="p-2 px-4 font-semibold text-sm" key={index}>
|
||||
<div className="p-2 px-4 text-sm font-semibold" key={index}>
|
||||
Legacy Components
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
import ConfirmationModal from "../../../../modals/ConfirmationModal";
|
||||
import EditNodeModal from "../../../../modals/EditNodeModal";
|
||||
import ShareModal from "../../../../modals/shareModal";
|
||||
import useAlertStore from "../../../../stores/alertStore";
|
||||
import { useDarkStore } from "../../../../stores/darkStore";
|
||||
import useFlowStore from "../../../../stores/flowStore";
|
||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||
|
|
@ -27,7 +28,6 @@ import {
|
|||
updateFlowPosition,
|
||||
} from "../../../../utils/reactflowUtils";
|
||||
import { classNames, cn } from "../../../../utils/utils";
|
||||
import useAlertStore from "../../../../stores/alertStore";
|
||||
|
||||
export default function NodeToolbarComponent({
|
||||
data,
|
||||
|
|
@ -97,8 +97,8 @@ export default function NodeToolbarComponent({
|
|||
(state) => state.setLastCopiedSelection
|
||||
);
|
||||
|
||||
const setSuccessData = useAlertStore(state => state.setSuccessData);
|
||||
const setNoticeData = useAlertStore(state => state.setNoticeData);
|
||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||
const setNoticeData = useAlertStore((state) => state.setNoticeData);
|
||||
|
||||
useEffect(() => {
|
||||
setFlowComponent(createFlowComponent(cloneDeep(data), version));
|
||||
|
|
@ -166,10 +166,8 @@ export default function NodeToolbarComponent({
|
|||
{
|
||||
x: 50,
|
||||
y: 10,
|
||||
paneX: nodes.find((node) => node.id === data.id)?.position
|
||||
.x,
|
||||
paneY: nodes.find((node) => node.id === data.id)?.position
|
||||
.y,
|
||||
paneX: nodes.find((node) => node.id === data.id)?.position.x,
|
||||
paneY: nodes.find((node) => node.id === data.id)?.position.y,
|
||||
}
|
||||
);
|
||||
break;
|
||||
|
|
@ -229,7 +227,7 @@ export default function NodeToolbarComponent({
|
|||
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const hasCode = Object.keys(data.node!.template).includes("code");
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
if (
|
||||
|
|
@ -260,18 +258,17 @@ export default function NodeToolbarComponent({
|
|||
event.preventDefault();
|
||||
setShowconfirmShare((state) => !state);
|
||||
}
|
||||
if (
|
||||
selected &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.key === "q"
|
||||
) {
|
||||
if (selected && (event.ctrlKey || event.metaKey) && event.key === "q") {
|
||||
event.preventDefault();
|
||||
if (isMinimal) {
|
||||
setShowState(show => !show)
|
||||
setShowState((show) => !show);
|
||||
setShowNode(data.showNode ?? true ? false : true);
|
||||
return
|
||||
return;
|
||||
}
|
||||
setNoticeData({title: "Minimization are only available for nodes with one handle or fewer."});
|
||||
setNoticeData({
|
||||
title:
|
||||
"Minimization are only available for nodes with one handle or fewer.",
|
||||
});
|
||||
}
|
||||
if (
|
||||
selected &&
|
||||
|
|
@ -281,7 +278,7 @@ export default function NodeToolbarComponent({
|
|||
) {
|
||||
event.preventDefault();
|
||||
if (hasCode) return setOpenModal((state) => !state);
|
||||
setNoticeData({title: `You can not access ${data.id} code`})
|
||||
setNoticeData({ title: `You can not access ${data.id} code` });
|
||||
}
|
||||
if (
|
||||
selected &&
|
||||
|
|
@ -292,11 +289,7 @@ export default function NodeToolbarComponent({
|
|||
event.preventDefault();
|
||||
setShowModalAdvanced((state) => !state);
|
||||
}
|
||||
if (
|
||||
selected &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.key === "s"
|
||||
) {
|
||||
if (selected && (event.ctrlKey || event.metaKey) && event.key === "s") {
|
||||
if (isSaved) {
|
||||
event.preventDefault();
|
||||
return setShowOverrideModal((state) => !state);
|
||||
|
|
@ -304,7 +297,7 @@ export default function NodeToolbarComponent({
|
|||
if (hasCode) {
|
||||
event.preventDefault();
|
||||
saveComponent(cloneDeep(data), false);
|
||||
setSuccessData({title: `${data.id} saved successfully`})
|
||||
setSuccessData({ title: `${data.id} saved successfully` });
|
||||
}
|
||||
}
|
||||
if (
|
||||
|
|
@ -317,7 +310,9 @@ export default function NodeToolbarComponent({
|
|||
if (data.node?.documentation) {
|
||||
return openInNewTab(data.node?.documentation);
|
||||
}
|
||||
setNoticeData({title: `${data.id} docs is not available at the moment.`})
|
||||
setNoticeData({
|
||||
title: `${data.id} docs is not available at the moment.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -349,7 +344,8 @@ export default function NodeToolbarComponent({
|
|||
<ShadTooltip content={"Save"} side="top">
|
||||
<button
|
||||
className={classNames(
|
||||
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10", (hasCode ? " " : " rounded-l-md ")
|
||||
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10",
|
||||
hasCode ? " " : " rounded-l-md "
|
||||
)}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
|
|
@ -359,10 +355,7 @@ export default function NodeToolbarComponent({
|
|||
saveComponent(cloneDeep(data), false);
|
||||
}}
|
||||
>
|
||||
<IconComponent
|
||||
name="SaveAll"
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
<IconComponent name="SaveAll" className="h-4 w-4" />
|
||||
</button>
|
||||
</ShadTooltip>
|
||||
|
||||
|
|
@ -425,28 +418,35 @@ export default function NodeToolbarComponent({
|
|||
<span className="">Edit</span>{" "}
|
||||
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
||||
<IconComponent
|
||||
name="Command"
|
||||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||
></IconComponent>
|
||||
name="Command"
|
||||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||
></IconComponent>
|
||||
) : (
|
||||
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">Ctrl + </span>
|
||||
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">
|
||||
Ctrl +{" "}
|
||||
</span>
|
||||
)}
|
||||
<span className="absolute right-2 top-[0.46em]">E</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
)}
|
||||
)}
|
||||
<SelectItem value={"duplicate"}>
|
||||
<div className="flex" data-testid="save-button-modal">
|
||||
<IconComponent name="Copy" className="relative top-0.5 mr-2 h-4 w-4" />
|
||||
<IconComponent
|
||||
name="Copy"
|
||||
className="relative top-0.5 mr-2 h-4 w-4"
|
||||
/>
|
||||
Duplicate
|
||||
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
||||
<IconComponent
|
||||
<IconComponent
|
||||
name="Command"
|
||||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||
></IconComponent>
|
||||
) : (
|
||||
<span className="absolute right-[1.20rem] top-[0.40em] stroke-2">Ctrl + </span>
|
||||
)}
|
||||
) : (
|
||||
<span className="absolute right-[1.20rem] top-[0.40em] stroke-2">
|
||||
Ctrl +{" "}
|
||||
</span>
|
||||
)}
|
||||
<span className="absolute right-2 top-[0.4em]">D</span>
|
||||
</div>{" "}
|
||||
</SelectItem>
|
||||
|
|
@ -458,13 +458,15 @@ export default function NodeToolbarComponent({
|
|||
/>{" "}
|
||||
<span className="">Copy</span>{" "}
|
||||
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
||||
<IconComponent
|
||||
<IconComponent
|
||||
name="Command"
|
||||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||
></IconComponent>
|
||||
) : (
|
||||
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">Ctrl + </span>
|
||||
)}
|
||||
) : (
|
||||
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">
|
||||
Ctrl +{" "}
|
||||
</span>
|
||||
)}
|
||||
<span className="absolute right-2 top-[0.4em]">C</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
|
|
@ -481,13 +483,14 @@ export default function NodeToolbarComponent({
|
|||
Share{" "}
|
||||
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
||||
<IconComponent
|
||||
name="Command"
|
||||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||
></IconComponent>
|
||||
name="Command"
|
||||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||
></IconComponent>
|
||||
) : (
|
||||
<span className="absolute right-[2.10rem] top-[0.43em] stroke-2">Ctrl</span>
|
||||
<span className="absolute right-[2.10rem] top-[0.43em] stroke-2">
|
||||
Ctrl
|
||||
</span>
|
||||
)}
|
||||
|
||||
<IconComponent
|
||||
name="ArrowBigUp"
|
||||
className="absolute right-[1.09rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||
|
|
@ -518,13 +521,15 @@ export default function NodeToolbarComponent({
|
|||
/>{" "}
|
||||
<span className="">Docs</span>{" "}
|
||||
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
||||
<IconComponent
|
||||
<IconComponent
|
||||
name="Command"
|
||||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||
></IconComponent>
|
||||
) : (
|
||||
<span className="absolute right-[2.10rem] top-[0.43em] stroke-2">Ctrl</span>
|
||||
)}
|
||||
) : (
|
||||
<span className="absolute right-[2.10rem] top-[0.43em] stroke-2">
|
||||
Ctrl
|
||||
</span>
|
||||
)}
|
||||
<IconComponent
|
||||
name="ArrowBigUp"
|
||||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||
|
|
@ -542,11 +547,13 @@ export default function NodeToolbarComponent({
|
|||
{showNode ? "Minimize" : "Expand"}
|
||||
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
||||
<IconComponent
|
||||
name="Command"
|
||||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||
></IconComponent>
|
||||
name="Command"
|
||||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||
></IconComponent>
|
||||
) : (
|
||||
<span className="absolute right-[1.30rem] top-[0.40em] stroke-2">Ctrl + </span>
|
||||
<span className="absolute right-[1.30rem] top-[0.40em] stroke-2">
|
||||
Ctrl +{" "}
|
||||
</span>
|
||||
)}
|
||||
<span className="absolute right-2 top-[0.46em]">Q</span>
|
||||
</div>
|
||||
|
|
@ -562,11 +569,13 @@ export default function NodeToolbarComponent({
|
|||
<span className="">Ungroup</span>{" "}
|
||||
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
||||
<IconComponent
|
||||
name="Command"
|
||||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||
></IconComponent>
|
||||
name="Command"
|
||||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||
></IconComponent>
|
||||
) : (
|
||||
<span className="absolute right-[1.30rem] top-[0.40em] stroke-2">Ctrl + </span>
|
||||
<span className="absolute right-[1.30rem] top-[0.40em] stroke-2">
|
||||
Ctrl +{" "}
|
||||
</span>
|
||||
)}
|
||||
<span className="absolute right-2 top-[0.43em]">U</span>
|
||||
</div>
|
||||
|
|
@ -601,12 +610,12 @@ export default function NodeToolbarComponent({
|
|||
index={6}
|
||||
onConfirm={(index, user) => {
|
||||
saveComponent(cloneDeep(data), true);
|
||||
setSuccessData({title: `${data.id} successfully overridden!`})
|
||||
setSuccessData({ title: `${data.id} successfully overridden!` });
|
||||
}}
|
||||
onClose={setShowOverrideModal}
|
||||
onCancel={() => {
|
||||
saveComponent(cloneDeep(data), false)
|
||||
setSuccessData({title: "New node successfully saved!"})
|
||||
saveComponent(cloneDeep(data), false);
|
||||
setSuccessData({ title: "New node successfully saved!" });
|
||||
}}
|
||||
>
|
||||
<ConfirmationModal.Content>
|
||||
|
|
|
|||
|
|
@ -2,15 +2,14 @@ import { Group, ToyBrick } from "lucide-react";
|
|||
import { useEffect, useState } from "react";
|
||||
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||
import DropdownButton from "../../components/DropdownButtonComponent";
|
||||
import NewFlowCardComponent from "../../components/NewFLowCard2";;
|
||||
import ExampleCardComponent from "../../components/exampleComponent";
|
||||
import NewFlowCardComponent from "../../components/NewFLowCard2";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import PageLayout from "../../components/pageLayout";
|
||||
import SidebarNav from "../../components/sidebarComponent";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import UndrawCardComponent from "../../components/undrawCards";
|
||||
import { CONSOLE_ERROR_MSG } from "../../constants/alerts_constants";
|
||||
import {
|
||||
EXAMPLES_MOCK,
|
||||
MY_COLLECTION_DESC,
|
||||
USER_PROJECTS_HEADER,
|
||||
} from "../../constants/constants";
|
||||
|
|
@ -18,7 +17,6 @@ import BaseModal from "../../modals/baseModal";
|
|||
import useAlertStore from "../../stores/alertStore";
|
||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||
import { downloadFlows } from "../../utils/reactflowUtils";
|
||||
import UndrawCardComponent from "../../components/undrawCards";
|
||||
export default function HomePage(): JSX.Element {
|
||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
|
||||
|
|
@ -120,9 +118,7 @@ export default function HomePage(): JSX.Element {
|
|||
</div>
|
||||
</div>
|
||||
<BaseModal size="three-cards" open={openModal} setOpen={setOpenModal}>
|
||||
<BaseModal.Header
|
||||
description={"Select a template below"}
|
||||
>
|
||||
<BaseModal.Header description={"Select a template below"}>
|
||||
<span className="pr-2" data-testid="modal-title">
|
||||
Get Started
|
||||
</span>
|
||||
|
|
@ -133,7 +129,7 @@ export default function HomePage(): JSX.Element {
|
|||
/> */}
|
||||
</BaseModal.Header>
|
||||
<BaseModal.Content>
|
||||
<div className=" h-full w-full grid grid-cols-3 gap-3 overflow-auto p-4 custom-scroll">
|
||||
<div className=" grid h-full w-full grid-cols-3 gap-3 overflow-auto p-4 custom-scroll">
|
||||
{examples.map((example, idx) => {
|
||||
return <UndrawCardComponent key={idx} flow={example} />;
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ module.exports = {
|
|||
"beta-foreground": "var(--beta-foreground)",
|
||||
"chat-bot-icon": "var(--chat-bot-icon)",
|
||||
"chat-user-icon": "var(--chat-user-icon)",
|
||||
"ice": "var(--ice)",
|
||||
ice: "var(--ice)",
|
||||
hover: "var(--hover)",
|
||||
white: "var(--white)",
|
||||
border: "hsl(var(--border))",
|
||||
|
|
@ -224,6 +224,6 @@ module.exports = {
|
|||
}),
|
||||
require("@tailwindcss/typography"),
|
||||
require("daisyui"),
|
||||
require('tailwindcss-dotted-background'),
|
||||
require("tailwindcss-dotted-background"),
|
||||
],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import pytest
|
||||
|
||||
from langflow.services.database.models.api_key import ApiKeyCreate
|
||||
|
||||
|
||||
|
|
@ -21,7 +22,6 @@ def test_get_api_keys(client, logged_in_headers, api_key):
|
|||
assert any("test-api-key" in api_key["name"] for api_key in data["api_keys"])
|
||||
# assert all api keys in data["api_keys"] are masked
|
||||
assert all("**" in api_key["api_key"] for api_key in data["api_keys"])
|
||||
# Add more assertions as needed based on the expected data structure and content
|
||||
|
||||
|
||||
def test_create_api_key(client, logged_in_headers):
|
||||
|
|
|
|||
|
|
@ -38,9 +38,7 @@ def test_create_or_update_starter_projects(client):
|
|||
num_projects = len(load_starter_projects())
|
||||
|
||||
# Get the number of projects in the database
|
||||
num_db_projects = session.exec(
|
||||
select(func.count(Flow.id)).where(Flow.folder == STARTER_FOLDER_NAME)
|
||||
).one()
|
||||
num_db_projects = session.exec(select(func.count(Flow.id)).where(Flow.folder == STARTER_FOLDER_NAME)).one()
|
||||
|
||||
# Check that the number of projects in the database is the same as the number of projects returned by load_starter_projects
|
||||
assert num_db_projects == num_projects
|
||||
|
|
@ -56,21 +54,16 @@ async def test_starter_project_can_run_successfully(client):
|
|||
num_projects = len(load_starter_projects())
|
||||
|
||||
# Get the number of projects in the database
|
||||
num_db_projects = session.exec(
|
||||
select(func.count(Flow.id)).where(Flow.folder == STARTER_FOLDER_NAME)
|
||||
).one()
|
||||
num_db_projects = session.exec(select(func.count(Flow.id)).where(Flow.folder == STARTER_FOLDER_NAME)).one()
|
||||
|
||||
# Check that the number of projects in the database is the same as the number of projects returned by load_starter_projects
|
||||
assert num_db_projects == num_projects
|
||||
|
||||
# Get all the starter projects
|
||||
projects = session.exec(
|
||||
select(Flow).where(Flow.folder == STARTER_FOLDER_NAME)
|
||||
).all()
|
||||
projects = session.exec(select(Flow).where(Flow.folder == STARTER_FOLDER_NAME)).all()
|
||||
|
||||
graphs: list[Graph] = [
|
||||
(project.name, Graph.from_payload(project.data, flow_id=project.id))
|
||||
for project in projects
|
||||
(project.name, Graph.from_payload(project.data, flow_id=project.id)) for project in projects
|
||||
]
|
||||
assert len(graphs) == len(projects)
|
||||
for name, graph in graphs:
|
||||
|
|
|
|||
|
|
@ -268,13 +268,9 @@ async def test_load_langchain_object_with_cached_session(client, basic_graph_dat
|
|||
# Provide a non-existent session_id
|
||||
session_service = get_session_service()
|
||||
session_id1 = "non-existent-session-id"
|
||||
graph1, artifacts1 = await session_service.load_session(
|
||||
session_id1, basic_graph_data
|
||||
)
|
||||
graph1, artifacts1 = await session_service.load_session(session_id1, basic_graph_data)
|
||||
# Use the new session_id to get the langchain_object again
|
||||
graph2, artifacts2 = await session_service.load_session(
|
||||
session_id1, basic_graph_data
|
||||
)
|
||||
graph2, artifacts2 = await session_service.load_session(session_id1, basic_graph_data)
|
||||
|
||||
assert graph1 == graph2
|
||||
assert artifacts1 == artifacts2
|
||||
|
|
@ -286,15 +282,11 @@ async def test_load_langchain_object_with_no_cached_session(client, basic_graph_
|
|||
session_service = get_session_service()
|
||||
session_id1 = "non-existent-session-id"
|
||||
session_id = session_service.build_key(session_id1, basic_graph_data)
|
||||
graph1, artifacts1 = await session_service.load_session(
|
||||
session_id, basic_graph_data
|
||||
)
|
||||
graph1, artifacts1 = await session_service.load_session(session_id, basic_graph_data)
|
||||
# Clear the cache
|
||||
session_service.clear_session(session_id)
|
||||
# Use the new session_id to get the langchain_object again
|
||||
graph2, artifacts2 = await session_service.load_session(
|
||||
session_id, basic_graph_data
|
||||
)
|
||||
graph2, artifacts2 = await session_service.load_session(session_id, basic_graph_data)
|
||||
|
||||
assert id(graph1) != id(graph2)
|
||||
# Since the cache was cleared, objects should be different
|
||||
|
|
@ -305,12 +297,8 @@ async def test_load_langchain_object_without_session_id(client, basic_graph_data
|
|||
# Provide a non-existent session_id
|
||||
session_service = get_session_service()
|
||||
session_id1 = None
|
||||
graph1, artifacts1 = await session_service.load_session(
|
||||
session_id1, basic_graph_data
|
||||
)
|
||||
graph1, artifacts1 = await session_service.load_session(session_id1, basic_graph_data)
|
||||
# Use the new session_id to get the langchain_object again
|
||||
graph2, artifacts2 = await session_service.load_session(
|
||||
session_id1, basic_graph_data
|
||||
)
|
||||
graph2, artifacts2 = await session_service.load_session(session_id1, basic_graph_data)
|
||||
|
||||
assert graph1 == graph2
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue