Merge branch 'zustand/io/migration' of personal:logspace-ai/langflow into zustand/io/migration
This commit is contained in:
commit
b388873d1b
101 changed files with 1193 additions and 1295 deletions
|
|
@ -86,9 +86,7 @@ def validate_prompt(template: str):
|
||||||
# Check if there are invalid characters in the input_variables
|
# Check if there are invalid characters in the input_variables
|
||||||
input_variables = check_input_variables(input_variables)
|
input_variables = check_input_variables(input_variables)
|
||||||
if any(var in INVALID_NAMES for var in input_variables):
|
if any(var in INVALID_NAMES for var in input_variables):
|
||||||
raise ValueError(
|
raise ValueError(f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. ")
|
||||||
f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. "
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
PromptTemplate(template=template, input_variables=input_variables)
|
PromptTemplate(template=template, input_variables=input_variables)
|
||||||
|
|
@ -123,9 +121,7 @@ def fix_variable(var, invalid_chars, wrong_variables):
|
||||||
# Handle variables starting with a number
|
# Handle variables starting with a number
|
||||||
if var[0].isdigit():
|
if var[0].isdigit():
|
||||||
invalid_chars.append(var[0])
|
invalid_chars.append(var[0])
|
||||||
new_var, invalid_chars, wrong_variables = fix_variable(
|
new_var, invalid_chars, wrong_variables = fix_variable(var[1:], invalid_chars, wrong_variables)
|
||||||
var[1:], invalid_chars, wrong_variables
|
|
||||||
)
|
|
||||||
|
|
||||||
# Temporarily replace {{ and }} to avoid treating them as invalid
|
# Temporarily replace {{ and }} to avoid treating them as invalid
|
||||||
new_var = new_var.replace("{{", "ᴛᴇᴍᴘᴏᴘᴇɴ").replace("}}", "ᴛᴇᴍᴘᴄʟᴏsᴇ")
|
new_var = new_var.replace("{{", "ᴛᴇᴍᴘᴏᴘᴇɴ").replace("}}", "ᴛᴇᴍᴘᴄʟᴏsᴇ")
|
||||||
|
|
@ -152,9 +148,7 @@ def check_variable(var, invalid_chars, wrong_variables, empty_variables):
|
||||||
return wrong_variables, empty_variables
|
return wrong_variables, empty_variables
|
||||||
|
|
||||||
|
|
||||||
def check_for_errors(
|
def check_for_errors(input_variables, fixed_variables, wrong_variables, empty_variables):
|
||||||
input_variables, fixed_variables, wrong_variables, empty_variables
|
|
||||||
):
|
|
||||||
if any(var for var in input_variables if var not in fixed_variables):
|
if any(var for var in input_variables if var not in fixed_variables):
|
||||||
error_message = (
|
error_message = (
|
||||||
f"Error: Input variables contain invalid characters or formats. \n"
|
f"Error: Input variables contain invalid characters or formats. \n"
|
||||||
|
|
@ -179,17 +173,11 @@ def check_input_variables(input_variables):
|
||||||
if is_json_like(var):
|
if is_json_like(var):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
new_var, wrong_variables, empty_variables = fix_variable(
|
new_var, wrong_variables, empty_variables = fix_variable(var, invalid_chars, wrong_variables)
|
||||||
var, invalid_chars, wrong_variables
|
wrong_variables, empty_variables = check_variable(var, INVALID_CHARACTERS, wrong_variables, empty_variables)
|
||||||
)
|
|
||||||
wrong_variables, empty_variables = check_variable(
|
|
||||||
var, INVALID_CHARACTERS, wrong_variables, empty_variables
|
|
||||||
)
|
|
||||||
fixed_variables.append(new_var)
|
fixed_variables.append(new_var)
|
||||||
variables_to_check.append(var)
|
variables_to_check.append(var)
|
||||||
|
|
||||||
check_for_errors(
|
check_for_errors(variables_to_check, fixed_variables, wrong_variables, empty_variables)
|
||||||
variables_to_check, fixed_variables, wrong_variables, empty_variables
|
|
||||||
)
|
|
||||||
|
|
||||||
return fixed_variables
|
return fixed_variables
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,7 @@ class AsyncStreamingLLMCallbackHandleSIO(AsyncCallbackHandler):
|
||||||
resp = ChatResponse(message=token, type="stream", intermediate_steps="")
|
resp = ChatResponse(message=token, type="stream", intermediate_steps="")
|
||||||
await self.socketio_service.emit_token(to=self.sid, data=resp.model_dump())
|
await self.socketio_service.emit_token(to=self.sid, data=resp.model_dump())
|
||||||
|
|
||||||
async def on_tool_start(
|
async def on_tool_start(self, serialized: Dict[str, Any], input_str: str, **kwargs: Any) -> Any:
|
||||||
self, serialized: Dict[str, Any], input_str: str, **kwargs: Any
|
|
||||||
) -> Any:
|
|
||||||
"""Run when tool starts running."""
|
"""Run when tool starts running."""
|
||||||
resp = ChatResponse(
|
resp = ChatResponse(
|
||||||
message="",
|
message="",
|
||||||
|
|
@ -73,9 +71,7 @@ class AsyncStreamingLLMCallbackHandleSIO(AsyncCallbackHandler):
|
||||||
try:
|
try:
|
||||||
# This is to emulate the stream of tokens
|
# This is to emulate the stream of tokens
|
||||||
for resp in resps:
|
for resp in resps:
|
||||||
await self.socketio_service.emit_token(
|
await self.socketio_service.emit_token(to=self.sid, data=resp.model_dump())
|
||||||
to=self.sid, data=resp.model_dump()
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(f"Error sending response: {exc}")
|
logger.error(f"Error sending response: {exc}")
|
||||||
|
|
||||||
|
|
@ -101,9 +97,7 @@ class AsyncStreamingLLMCallbackHandleSIO(AsyncCallbackHandler):
|
||||||
resp = PromptResponse(
|
resp = PromptResponse(
|
||||||
prompt=text,
|
prompt=text,
|
||||||
)
|
)
|
||||||
await self.socketio_service.emit_message(
|
await self.socketio_service.emit_message(to=self.sid, data=resp.model_dump())
|
||||||
to=self.sid, data=resp.model_dump()
|
|
||||||
)
|
|
||||||
|
|
||||||
async def on_agent_action(self, action: AgentAction, **kwargs: Any):
|
async def on_agent_action(self, action: AgentAction, **kwargs: Any):
|
||||||
log = f"Thought: {action.log}"
|
log = f"Thought: {action.log}"
|
||||||
|
|
@ -113,9 +107,7 @@ class AsyncStreamingLLMCallbackHandleSIO(AsyncCallbackHandler):
|
||||||
logs = log.split("\n")
|
logs = log.split("\n")
|
||||||
for log in logs:
|
for log in logs:
|
||||||
resp = ChatResponse(message="", type="stream", intermediate_steps=log)
|
resp = ChatResponse(message="", type="stream", intermediate_steps=log)
|
||||||
await self.socketio_service.emit_token(
|
await self.socketio_service.emit_token(to=self.sid, data=resp.model_dump())
|
||||||
to=self.sid, data=resp.model_dump()
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
resp = ChatResponse(message="", type="stream", intermediate_steps=log)
|
resp = ChatResponse(message="", type="stream", intermediate_steps=log)
|
||||||
await self.socketio_service.emit_token(to=self.sid, data=resp.model_dump())
|
await self.socketio_service.emit_token(to=self.sid, data=resp.model_dump())
|
||||||
|
|
|
||||||
|
|
@ -101,12 +101,8 @@ async def build_vertex(
|
||||||
cache = chat_service.get_cache(flow_id)
|
cache = chat_service.get_cache(flow_id)
|
||||||
if not cache:
|
if not cache:
|
||||||
# If there's no cache
|
# If there's no cache
|
||||||
logger.warning(
|
logger.warning(f"No cache found for {flow_id}. Building graph starting at {vertex_id}")
|
||||||
f"No cache found for {flow_id}. Building graph starting at {vertex_id}"
|
graph = build_and_cache_graph(flow_id=flow_id, session=next(get_session()), chat_service=chat_service)
|
||||||
)
|
|
||||||
graph = build_and_cache_graph(
|
|
||||||
flow_id=flow_id, session=next(get_session()), chat_service=chat_service
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
graph = cache.get("result")
|
graph = cache.get("result")
|
||||||
result_data_response = ResultDataResponse(results={})
|
result_data_response = ResultDataResponse(results={})
|
||||||
|
|
@ -126,9 +122,7 @@ async def build_vertex(
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"No result found for vertex {vertex_id}")
|
raise ValueError(f"No result found for vertex {vertex_id}")
|
||||||
next_vertices_ids = vertex.successors_ids
|
next_vertices_ids = vertex.successors_ids
|
||||||
next_vertices_ids = [
|
next_vertices_ids = [v for v in next_vertices_ids if graph.should_run_vertex(v)]
|
||||||
v for v in next_vertices_ids if graph.should_run_vertex(v)
|
|
||||||
]
|
|
||||||
|
|
||||||
result_data_response = ResultDataResponse(**result_dict.model_dump())
|
result_data_response = ResultDataResponse(**result_dict.model_dump())
|
||||||
|
|
||||||
|
|
@ -211,9 +205,7 @@ async def build_vertex_stream(
|
||||||
else:
|
else:
|
||||||
graph = cache.get("result")
|
graph = cache.get("result")
|
||||||
else:
|
else:
|
||||||
session_data = await session_service.load_session(
|
session_data = await session_service.load_session(session_id, flow_id=flow_id)
|
||||||
session_id, flow_id=flow_id
|
|
||||||
)
|
|
||||||
graph, artifacts = session_data if session_data else (None, None)
|
graph, artifacts = session_data if session_data else (None, None)
|
||||||
if not graph:
|
if not graph:
|
||||||
raise ValueError(f"No graph found for {flow_id}.")
|
raise ValueError(f"No graph found for {flow_id}.")
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,7 @@ def get_all(
|
||||||
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post("/run/{flow_id}", response_model=RunResponse, response_model_exclude_none=True)
|
||||||
"/run/{flow_id}", response_model=RunResponse, response_model_exclude_none=True
|
|
||||||
)
|
|
||||||
async def run_flow_with_caching(
|
async def run_flow_with_caching(
|
||||||
session: Annotated[Session, Depends(get_session)],
|
session: Annotated[Session, Depends(get_session)],
|
||||||
flow_id: str,
|
flow_id: str,
|
||||||
|
|
@ -113,9 +111,7 @@ async def run_flow_with_caching(
|
||||||
outputs = []
|
outputs = []
|
||||||
|
|
||||||
if session_id:
|
if session_id:
|
||||||
session_data = await session_service.load_session(
|
session_data = await session_service.load_session(session_id, flow_id=flow_id)
|
||||||
session_id, flow_id=flow_id
|
|
||||||
)
|
|
||||||
graph, artifacts = session_data if session_data else (None, None)
|
graph, artifacts = session_data if session_data else (None, None)
|
||||||
task_result: Any = None
|
task_result: Any = None
|
||||||
if not graph:
|
if not graph:
|
||||||
|
|
@ -134,11 +130,7 @@ async def run_flow_with_caching(
|
||||||
else:
|
else:
|
||||||
# Get the flow that matches the flow_id and belongs to the user
|
# Get the flow that matches the flow_id and belongs to the user
|
||||||
# flow = session.query(Flow).filter(Flow.id == flow_id).filter(Flow.user_id == api_key_user.id).first()
|
# flow = session.query(Flow).filter(Flow.id == flow_id).filter(Flow.user_id == api_key_user.id).first()
|
||||||
flow = session.exec(
|
flow = session.exec(select(Flow).where(Flow.id == flow_id).where(Flow.user_id == api_key_user.id)).first()
|
||||||
select(Flow)
|
|
||||||
.where(Flow.id == flow_id)
|
|
||||||
.where(Flow.user_id == api_key_user.id)
|
|
||||||
).first()
|
|
||||||
if flow is None:
|
if flow is None:
|
||||||
raise ValueError(f"Flow {flow_id} not found")
|
raise ValueError(f"Flow {flow_id} not found")
|
||||||
|
|
||||||
|
|
@ -162,18 +154,12 @@ async def run_flow_with_caching(
|
||||||
# StatementError('(builtins.ValueError) badly formed hexadecimal UUID string')
|
# StatementError('(builtins.ValueError) badly formed hexadecimal UUID string')
|
||||||
if "badly formed hexadecimal UUID string" in str(exc):
|
if "badly formed hexadecimal UUID string" in str(exc):
|
||||||
# This means the Flow ID is not a valid UUID which means it can't find the flow
|
# This means the Flow ID is not a valid UUID which means it can't find the flow
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
|
||||||
) from exc
|
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
if f"Flow {flow_id} not found" in str(exc):
|
if f"Flow {flow_id} not found" in str(exc):
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
|
||||||
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
|
|
||||||
) from exc
|
|
||||||
else:
|
else:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)) from exc
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
|
|
@ -202,8 +188,7 @@ async def process(
|
||||||
"""
|
"""
|
||||||
# Raise a depreciation warning
|
# Raise a depreciation warning
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"The /process endpoint is deprecated and will be removed in a future version. "
|
"The /process endpoint is deprecated and will be removed in a future version. " "Please use /run instead."
|
||||||
"Please use /run instead."
|
|
||||||
)
|
)
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
|
@ -275,16 +260,12 @@ async def custom_component(
|
||||||
|
|
||||||
built_frontend_node = build_custom_component_template(component, user_id=user.id)
|
built_frontend_node = build_custom_component_template(component, user_id=user.id)
|
||||||
|
|
||||||
built_frontend_node = update_frontend_node_with_template_values(
|
built_frontend_node = update_frontend_node_with_template_values(built_frontend_node, raw_code.frontend_node)
|
||||||
built_frontend_node, raw_code.frontend_node
|
|
||||||
)
|
|
||||||
return built_frontend_node
|
return built_frontend_node
|
||||||
|
|
||||||
|
|
||||||
@router.post("/custom_component/reload", status_code=HTTPStatus.OK)
|
@router.post("/custom_component/reload", status_code=HTTPStatus.OK)
|
||||||
async def reload_custom_component(
|
async def reload_custom_component(path: str, user: User = Depends(get_current_active_user)):
|
||||||
path: str, user: User = Depends(get_current_active_user)
|
|
||||||
):
|
|
||||||
from langflow.interface.custom.utils import build_custom_component_template
|
from langflow.interface.custom.utils import build_custom_component_template
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -57,20 +57,22 @@ def read_flows(
|
||||||
try:
|
try:
|
||||||
auth_settings = settings_service.auth_settings
|
auth_settings = settings_service.auth_settings
|
||||||
if auth_settings.AUTO_LOGIN:
|
if auth_settings.AUTO_LOGIN:
|
||||||
flows = session.exec(select(Flow).where(Flow.user_id == None)).all()
|
flows = session.exec(select(Flow).where(Flow.user_id == None | Flow.user_id == current_user.id)).all() # noqa
|
||||||
else:
|
else:
|
||||||
flows = current_user.flows
|
flows = current_user.flows
|
||||||
|
|
||||||
flows = validate_is_component(flows)
|
flows = validate_is_component(flows)
|
||||||
|
flow_ids = [flow.id for flow in flows]
|
||||||
# with the session get the flows that DO NOT have a user_id
|
# with the session get the flows that DO NOT have a user_id
|
||||||
try:
|
try:
|
||||||
example_flows = session.exec(
|
example_flows = session.exec(
|
||||||
select(Flow).where(
|
select(Flow).where(
|
||||||
Flow.user_id == None, Flow.folder == STARTER_FOLDER_NAME
|
Flow.user_id == None,
|
||||||
|
Flow.folder == STARTER_FOLDER_NAME, # noqa
|
||||||
)
|
)
|
||||||
).all() # noqa
|
).all() # noqa
|
||||||
for example_flow in example_flows:
|
for example_flow in example_flows:
|
||||||
if example_flow not in flows:
|
if example_flow.id not in flow_ids:
|
||||||
flows.append(example_flow)
|
flows.append(example_flow)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
|
|
@ -89,15 +91,14 @@ def read_flow(
|
||||||
):
|
):
|
||||||
"""Read a flow."""
|
"""Read a flow."""
|
||||||
auth_settings = settings_service.auth_settings
|
auth_settings = settings_service.auth_settings
|
||||||
|
stmt = select(Flow).where(Flow.id == flow_id)
|
||||||
if auth_settings.AUTO_LOGIN:
|
if auth_settings.AUTO_LOGIN:
|
||||||
user_id = None
|
# If auto login is enable user_id can be current_user.id or None
|
||||||
else:
|
# so write an OR
|
||||||
user_id = current_user.id
|
stmt = stmt.where(
|
||||||
if user_flow := (
|
(Flow.user_id == current_user.id) | (Flow.user_id == None) # noqa
|
||||||
session.exec(
|
) # noqa
|
||||||
select(Flow).where(Flow.id == flow_id, Flow.user_id == user_id)
|
if user_flow := session.exec(stmt).first():
|
||||||
).first()
|
|
||||||
):
|
|
||||||
return user_flow
|
return user_flow
|
||||||
else:
|
else:
|
||||||
raise HTTPException(status_code=404, detail="Flow not found")
|
raise HTTPException(status_code=404, detail="Flow not found")
|
||||||
|
|
|
||||||
|
|
@ -158,9 +158,7 @@ class StreamData(BaseModel):
|
||||||
data: dict
|
data: dict
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return (
|
return f"event: {self.event}\ndata: {orjson_dumps(self.data, indent_2=False)}\n\n"
|
||||||
f"event: {self.event}\ndata: {orjson_dumps(self.data, indent_2=False)}\n\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CustomComponentCode(BaseModel):
|
class CustomComponentCode(BaseModel):
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,7 @@ def post_validate_prompt(prompt_request: ValidatePromptRequest):
|
||||||
|
|
||||||
add_new_variables_to_template(input_variables, prompt_request)
|
add_new_variables_to_template(input_variables, prompt_request)
|
||||||
|
|
||||||
remove_old_variables_from_template(
|
remove_old_variables_from_template(old_custom_fields, input_variables, prompt_request)
|
||||||
old_custom_fields, input_variables, prompt_request
|
|
||||||
)
|
|
||||||
|
|
||||||
update_input_variables_field(input_variables, prompt_request)
|
update_input_variables_field(input_variables, prompt_request)
|
||||||
|
|
||||||
|
|
@ -58,19 +56,12 @@ def post_validate_prompt(prompt_request: ValidatePromptRequest):
|
||||||
|
|
||||||
def get_old_custom_fields(prompt_request):
|
def get_old_custom_fields(prompt_request):
|
||||||
try:
|
try:
|
||||||
if (
|
if len(prompt_request.frontend_node.custom_fields) == 1 and prompt_request.name == "":
|
||||||
len(prompt_request.frontend_node.custom_fields) == 1
|
|
||||||
and prompt_request.name == ""
|
|
||||||
):
|
|
||||||
# If there is only one custom field and the name is empty string
|
# If there is only one custom field and the name is empty string
|
||||||
# then we are dealing with the first prompt request after the node was created
|
# then we are dealing with the first prompt request after the node was created
|
||||||
prompt_request.name = list(
|
prompt_request.name = list(prompt_request.frontend_node.custom_fields.keys())[0]
|
||||||
prompt_request.frontend_node.custom_fields.keys()
|
|
||||||
)[0]
|
|
||||||
|
|
||||||
old_custom_fields = prompt_request.frontend_node.custom_fields[
|
old_custom_fields = prompt_request.frontend_node.custom_fields[prompt_request.name]
|
||||||
prompt_request.name
|
|
||||||
]
|
|
||||||
if old_custom_fields is None:
|
if old_custom_fields is None:
|
||||||
old_custom_fields = []
|
old_custom_fields = []
|
||||||
|
|
||||||
|
|
@ -87,40 +78,26 @@ def add_new_variables_to_template(input_variables, prompt_request):
|
||||||
template_field = DefaultPromptField(name=variable, display_name=variable)
|
template_field = DefaultPromptField(name=variable, display_name=variable)
|
||||||
if variable in prompt_request.frontend_node.template:
|
if variable in prompt_request.frontend_node.template:
|
||||||
# Set the new field with the old value
|
# Set the new field with the old value
|
||||||
template_field.value = prompt_request.frontend_node.template[variable][
|
template_field.value = prompt_request.frontend_node.template[variable]["value"]
|
||||||
"value"
|
|
||||||
]
|
|
||||||
|
|
||||||
prompt_request.frontend_node.template[variable] = template_field.to_dict()
|
prompt_request.frontend_node.template[variable] = template_field.to_dict()
|
||||||
|
|
||||||
# Check if variable is not already in the list before appending
|
# Check if variable is not already in the list before appending
|
||||||
if (
|
if variable not in prompt_request.frontend_node.custom_fields[prompt_request.name]:
|
||||||
variable
|
prompt_request.frontend_node.custom_fields[prompt_request.name].append(variable)
|
||||||
not in prompt_request.frontend_node.custom_fields[prompt_request.name]
|
|
||||||
):
|
|
||||||
prompt_request.frontend_node.custom_fields[prompt_request.name].append(
|
|
||||||
variable
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(exc)
|
logger.exception(exc)
|
||||||
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
raise HTTPException(status_code=500, detail=str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
def remove_old_variables_from_template(
|
def remove_old_variables_from_template(old_custom_fields, input_variables, prompt_request):
|
||||||
old_custom_fields, input_variables, prompt_request
|
|
||||||
):
|
|
||||||
for variable in old_custom_fields:
|
for variable in old_custom_fields:
|
||||||
if variable not in input_variables:
|
if variable not in input_variables:
|
||||||
try:
|
try:
|
||||||
# Remove the variable from custom_fields associated with the given name
|
# Remove the variable from custom_fields associated with the given name
|
||||||
if (
|
if variable in prompt_request.frontend_node.custom_fields[prompt_request.name]:
|
||||||
variable
|
prompt_request.frontend_node.custom_fields[prompt_request.name].remove(variable)
|
||||||
in prompt_request.frontend_node.custom_fields[prompt_request.name]
|
|
||||||
):
|
|
||||||
prompt_request.frontend_node.custom_fields[
|
|
||||||
prompt_request.name
|
|
||||||
].remove(variable)
|
|
||||||
|
|
||||||
# Remove the variable from the template
|
# Remove the variable from the template
|
||||||
prompt_request.frontend_node.template.pop(variable, None)
|
prompt_request.frontend_node.template.pop(variable, None)
|
||||||
|
|
@ -132,6 +109,4 @@ def remove_old_variables_from_template(
|
||||||
|
|
||||||
def update_input_variables_field(input_variables, prompt_request):
|
def update_input_variables_field(input_variables, prompt_request):
|
||||||
if "input_variables" in prompt_request.frontend_node.template:
|
if "input_variables" in prompt_request.frontend_node.template:
|
||||||
prompt_request.frontend_node.template["input_variables"][
|
prompt_request.frontend_node.template["input_variables"]["value"] = input_variables
|
||||||
"value"
|
|
||||||
] = input_variables
|
|
||||||
|
|
|
||||||
|
|
@ -35,9 +35,7 @@ def retrieve_file_paths(
|
||||||
|
|
||||||
glob = "**/*" if recursive else "*"
|
glob = "**/*" if recursive else "*"
|
||||||
paths = walk_level(path_obj, depth) if depth else path_obj.glob(glob)
|
paths = walk_level(path_obj, depth) if depth else path_obj.glob(glob)
|
||||||
file_paths = [
|
file_paths = [Text(p) for p in paths if p.is_file() and match_types(p) and is_not_hidden(p)]
|
||||||
Text(p) for p in paths if p.is_file() and match_types(p) and is_not_hidden(p)
|
|
||||||
]
|
|
||||||
|
|
||||||
return file_paths
|
return file_paths
|
||||||
|
|
||||||
|
|
@ -70,16 +68,12 @@ def get_elements(
|
||||||
if use_multithreading:
|
if use_multithreading:
|
||||||
records = parallel_load_records(file_paths, silent_errors, max_concurrency)
|
records = parallel_load_records(file_paths, silent_errors, max_concurrency)
|
||||||
else:
|
else:
|
||||||
records = [
|
records = [parse_file_to_record(file_path, silent_errors) for file_path in file_paths]
|
||||||
parse_file_to_record(file_path, silent_errors) for file_path in file_paths
|
|
||||||
]
|
|
||||||
records = list(filter(None, records))
|
records = list(filter(None, records))
|
||||||
return records
|
return records
|
||||||
|
|
||||||
|
|
||||||
def parallel_load_records(
|
def parallel_load_records(file_paths: List[str], silent_errors: bool, max_concurrency: int) -> List[Optional[Record]]:
|
||||||
file_paths: List[str], silent_errors: bool, max_concurrency: int
|
|
||||||
) -> List[Optional[Record]]:
|
|
||||||
with futures.ThreadPoolExecutor(max_workers=max_concurrency) as executor:
|
with futures.ThreadPoolExecutor(max_workers=max_concurrency) as executor:
|
||||||
loaded_files = executor.map(
|
loaded_files = executor.map(
|
||||||
lambda file_path: parse_file_to_record(file_path, silent_errors),
|
lambda file_path: parse_file_to_record(file_path, silent_errors),
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,7 @@ class ChatComponent(CustomComponent):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
if not session_id or not sender or not sender_name:
|
if not session_id or not sender or not sender_name:
|
||||||
raise ValueError(
|
raise ValueError("All of session_id, sender, and sender_name must be provided.")
|
||||||
"All of session_id, sender, and sender_name must be provided."
|
|
||||||
)
|
|
||||||
if isinstance(message, Record):
|
if isinstance(message, Record):
|
||||||
record = message
|
record = message
|
||||||
record.data.update(
|
record.data.update(
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ class ConversationChainComponent(CustomComponent):
|
||||||
chain = ConversationChain(llm=llm)
|
chain = ConversationChain(llm=llm)
|
||||||
else:
|
else:
|
||||||
chain = ConversationChain(llm=llm, memory=memory)
|
chain = ConversationChain(llm=llm, memory=memory)
|
||||||
result = chain.invoke(inputs)
|
result = chain.invoke({"input": input_value})
|
||||||
if hasattr(result, "content") and isinstance(result.content, str):
|
if hasattr(result, "content") and isinstance(result.content, str):
|
||||||
result = result.content
|
result = result.content
|
||||||
elif isinstance(result, str):
|
elif isinstance(result, str):
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,12 @@ class APIRequest(CustomComponent):
|
||||||
"headers": {
|
"headers": {
|
||||||
"display_name": "Headers",
|
"display_name": "Headers",
|
||||||
"info": "The headers to send with the request.",
|
"info": "The headers to send with the request.",
|
||||||
"input_types": ["dict"]
|
"input_types": ["dict"],
|
||||||
},
|
},
|
||||||
"body": {
|
"body": {
|
||||||
"display_name": "Body",
|
"display_name": "Body",
|
||||||
"info": "The body to send with the request (for POST, PATCH, PUT).",
|
"info": "The body to send with the request (for POST, PATCH, PUT).",
|
||||||
"input_types": ["dict"]
|
"input_types": ["dict"],
|
||||||
},
|
},
|
||||||
"timeout": {
|
"timeout": {
|
||||||
"display_name": "Timeout",
|
"display_name": "Timeout",
|
||||||
|
|
@ -58,9 +58,7 @@ class APIRequest(CustomComponent):
|
||||||
data = body if body else None
|
data = body if body else None
|
||||||
data = json.dumps(data)
|
data = json.dumps(data)
|
||||||
try:
|
try:
|
||||||
response = await client.request(
|
response = await client.request(method, url, headers=headers, content=data, timeout=timeout)
|
||||||
method, url, headers=headers, content=data, timeout=timeout
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
response_json = response.json()
|
response_json = response.json()
|
||||||
result = orjson_dumps(response_json, indent_2=False)
|
result = orjson_dumps(response_json, indent_2=False)
|
||||||
|
|
@ -96,16 +94,9 @@ class APIRequest(CustomComponent):
|
||||||
if headers is None:
|
if headers is None:
|
||||||
headers = {}
|
headers = {}
|
||||||
urls = url if isinstance(url, list) else [url]
|
urls = url if isinstance(url, list) else [url]
|
||||||
bodies = (
|
bodies = body if isinstance(body, list) else [body] if body else [None] * len(urls)
|
||||||
body
|
|
||||||
if isinstance(body, list)
|
|
||||||
else [body] if body else [None] * len(urls)
|
|
||||||
)
|
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
results = await asyncio.gather(
|
results = await asyncio.gather(
|
||||||
*[
|
*[self.make_request(client, method, u, headers, rec, timeout) for u, rec in zip(urls, bodies)]
|
||||||
self.make_request(client, method, u, headers, rec, timeout)
|
|
||||||
for u, rec in zip(urls, bodies)
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
return results
|
return results
|
||||||
|
|
|
||||||
|
|
@ -57,20 +57,13 @@ class DirectoryComponent(CustomComponent):
|
||||||
if types is None:
|
if types is None:
|
||||||
types = []
|
types = []
|
||||||
resolved_path = self.resolve_path(path)
|
resolved_path = self.resolve_path(path)
|
||||||
file_paths = retrieve_file_paths(
|
file_paths = retrieve_file_paths(resolved_path, types, load_hidden, recursive, depth)
|
||||||
resolved_path, types, load_hidden, recursive, depth
|
|
||||||
)
|
|
||||||
loaded_records = []
|
loaded_records = []
|
||||||
|
|
||||||
if use_multithreading:
|
if use_multithreading:
|
||||||
loaded_records = parallel_load_records(
|
loaded_records = parallel_load_records(file_paths, silent_errors, max_concurrency)
|
||||||
file_paths, silent_errors, max_concurrency
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
loaded_records = [
|
loaded_records = [parse_file_to_record(file_path, silent_errors) for file_path in file_paths]
|
||||||
parse_file_to_record(file_path, silent_errors)
|
|
||||||
for file_path in file_paths
|
|
||||||
]
|
|
||||||
loaded_records = list(filter(None, loaded_records))
|
loaded_records = list(filter(None, loaded_records))
|
||||||
self.status = loaded_records
|
self.status = loaded_records
|
||||||
return loaded_records
|
return loaded_records
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,7 @@ class FileLoaderComponent(CustomComponent):
|
||||||
beta = True
|
beta = True
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
loader_options = ["Automatic"] + [
|
loader_options = ["Automatic"] + [loader_info["name"] for loader_info in LOADERS_INFO]
|
||||||
loader_info["name"] for loader_info in LOADERS_INFO
|
|
||||||
]
|
|
||||||
|
|
||||||
file_types = []
|
file_types = []
|
||||||
suffixes = []
|
suffixes = []
|
||||||
|
|
@ -105,9 +103,7 @@ class FileLoaderComponent(CustomComponent):
|
||||||
if isinstance(selected_loader_info, dict):
|
if isinstance(selected_loader_info, dict):
|
||||||
loader_import: str = selected_loader_info["import"]
|
loader_import: str = selected_loader_info["import"]
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(f"Loader info for {loader} is not a dict\nLoader info:\n{selected_loader_info}")
|
||||||
f"Loader info for {loader} is not a dict\nLoader info:\n{selected_loader_info}"
|
|
||||||
)
|
|
||||||
module_name, class_name = loader_import.rsplit(".", 1)
|
module_name, class_name = loader_import.rsplit(".", 1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -115,9 +111,7 @@ class FileLoaderComponent(CustomComponent):
|
||||||
loader_module = __import__(module_name, fromlist=[class_name])
|
loader_module = __import__(module_name, fromlist=[class_name])
|
||||||
loader_instance = getattr(loader_module, class_name)
|
loader_instance = getattr(loader_module, class_name)
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
raise ValueError(
|
raise ValueError(f"Loader {loader} could not be imported\nLoader info:\n{selected_loader_info}") from e
|
||||||
f"Loader {loader} could not be imported\nLoader info:\n{selected_loader_info}"
|
|
||||||
) from e
|
|
||||||
|
|
||||||
result = loader_instance(file_path=file_path)
|
result = loader_instance(file_path=file_path)
|
||||||
docs = result.load()
|
docs = result.load()
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ class URLComponent(CustomComponent):
|
||||||
self,
|
self,
|
||||||
urls: list[str],
|
urls: list[str],
|
||||||
) -> Record:
|
) -> Record:
|
||||||
|
|
||||||
loader = WebBaseLoader(web_paths=urls)
|
loader = WebBaseLoader(web_paths=urls)
|
||||||
docs = loader.load()
|
docs = loader.load()
|
||||||
records = self.to_records(docs)
|
records = self.to_records(docs)
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,7 @@ class NotifyComponent(CustomComponent):
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(
|
def build(self, name: str, record: Optional[Record] = None, append: bool = False) -> Record:
|
||||||
self, name: str, record: Optional[Record] = None, append: bool = False
|
|
||||||
) -> Record:
|
|
||||||
if record and not isinstance(record, Record):
|
if record and not isinstance(record, Record):
|
||||||
if isinstance(record, str):
|
if isinstance(record, str):
|
||||||
record = Record(text=record)
|
record = Record(text=record)
|
||||||
|
|
@ -39,10 +39,7 @@ class RunFlowComponent(CustomComponent):
|
||||||
records.append(record)
|
records.append(record)
|
||||||
return records
|
return records
|
||||||
|
|
||||||
async def build(
|
async def build(self, input_value: Text, flow_name: str, tweaks: NestedDict) -> Record:
|
||||||
self, input_value: Text, flow_name: str, tweaks: NestedDict
|
|
||||||
) -> Record:
|
|
||||||
|
|
||||||
results: List[Optional[ResultData]] = await self.run_flow(
|
results: List[Optional[ResultData]] = await self.run_flow(
|
||||||
input_value=input_value, flow_name=flow_name, tweaks=tweaks
|
input_value=input_value, flow_name=flow_name, tweaks=tweaks
|
||||||
)
|
)
|
||||||
|
|
@ -11,7 +11,10 @@ class SQLExecutorComponent(CustomComponent):
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
"database": {"display_name": "Database"},
|
"database_url": {
|
||||||
|
"display_name": "Database URL",
|
||||||
|
"info": "The URL of the database.",
|
||||||
|
},
|
||||||
"include_columns": {
|
"include_columns": {
|
||||||
"display_name": "Include Columns",
|
"display_name": "Include Columns",
|
||||||
"info": "Include columns in the result.",
|
"info": "Include columns in the result.",
|
||||||
|
|
@ -26,15 +29,24 @@ class SQLExecutorComponent(CustomComponent):
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def clean_up_uri(self, uri: str) -> str:
|
||||||
|
if uri.startswith("postgresql://"):
|
||||||
|
uri = uri.replace("postgresql://", "postgres://")
|
||||||
|
return uri.strip()
|
||||||
|
|
||||||
def build(
|
def build(
|
||||||
self,
|
self,
|
||||||
query: str,
|
query: str,
|
||||||
database: SQLDatabase,
|
database_url: str,
|
||||||
include_columns: bool = False,
|
include_columns: bool = False,
|
||||||
passthrough: bool = False,
|
passthrough: bool = False,
|
||||||
add_error: bool = False,
|
add_error: bool = False,
|
||||||
) -> Text:
|
) -> Text:
|
||||||
error = None
|
error = None
|
||||||
|
try:
|
||||||
|
database = SQLDatabase.from_uri(database_url)
|
||||||
|
except Exception as e:
|
||||||
|
raise ValueError(f"An error occurred while connecting to the database: {e}")
|
||||||
try:
|
try:
|
||||||
tool = QuerySQLDataBaseTool(db=database)
|
tool = QuerySQLDataBaseTool(db=database)
|
||||||
result = tool.run(query, include_columns=include_columns)
|
result = tool.run(query, include_columns=include_columns)
|
||||||
0
src/backend/langflow/components/experimental/__init__.py
Normal file
0
src/backend/langflow/components/experimental/__init__.py
Normal file
|
|
@ -11,7 +11,6 @@ class ExtractKeyFromRecordComponent(CustomComponent):
|
||||||
}
|
}
|
||||||
|
|
||||||
def build(self, record: Record, key: str, silent_error: bool = True) -> dict:
|
def build(self, record: Record, key: str, silent_error: bool = True) -> dict:
|
||||||
|
|
||||||
data = getattr(record, key)
|
data = getattr(record, key)
|
||||||
self.status = data
|
self.status = data
|
||||||
return data
|
return data
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,7 @@ class UUIDGeneratorComponent(CustomComponent):
|
||||||
display_name = "Unique ID Generator"
|
display_name = "Unique ID Generator"
|
||||||
description = "Generates a unique ID."
|
description = "Generates a unique ID."
|
||||||
|
|
||||||
def update_build_config(
|
def update_build_config(self, build_config: dict, field_name: Text, field_value: Any):
|
||||||
self, build_config: dict, field_name: Text, field_value: Any
|
|
||||||
):
|
|
||||||
if field_name == "unique_id":
|
if field_name == "unique_id":
|
||||||
build_config[field_name]["value"] = str(uuid.uuid4())
|
build_config[field_name]["value"] = str(uuid.uuid4())
|
||||||
return build_config
|
return build_config
|
||||||
|
|
|
||||||
47
src/backend/langflow/components/helpers/RecordComponent1.py
Normal file
47
src/backend/langflow/components/helpers/RecordComponent1.py
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from langflow import CustomComponent
|
||||||
|
from langflow.schema import Record
|
||||||
|
from langflow.template.field.base import TemplateField
|
||||||
|
|
||||||
|
|
||||||
|
class RecordComponent(CustomComponent):
|
||||||
|
display_name = "Record Numbers"
|
||||||
|
description = "A component to create a record from key-value pairs."
|
||||||
|
field_order = ["n_keys"]
|
||||||
|
|
||||||
|
def update_build_config(self, build_config: dict, field_name: str, field_value: Any):
|
||||||
|
if field_value is None:
|
||||||
|
return
|
||||||
|
elif int(field_value) == 0:
|
||||||
|
keep = ["n_keys", "code"]
|
||||||
|
for key in build_config.copy():
|
||||||
|
if key in keep:
|
||||||
|
continue
|
||||||
|
del build_config[key]
|
||||||
|
build_config[field_name]["value"] = int(field_value)
|
||||||
|
|
||||||
|
# Add new fields depending on the field value
|
||||||
|
for i in range(int(field_value)):
|
||||||
|
field = TemplateField(
|
||||||
|
name=f"Key and Value {i}",
|
||||||
|
field_type="dict",
|
||||||
|
display_name="",
|
||||||
|
info="The key for the record.",
|
||||||
|
input_types=["Text"],
|
||||||
|
)
|
||||||
|
build_config[field.name] = field.to_dict()
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
return {
|
||||||
|
"n_keys": {
|
||||||
|
"display_name": "Number of Fields",
|
||||||
|
"refresh": True,
|
||||||
|
"info": "The number of keys to create in the record.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(self, n_keys: int, **kwargs) -> Record:
|
||||||
|
data = {k: v for d in kwargs.values() for k, v in d.items()}
|
||||||
|
record = Record(data=data)
|
||||||
|
return record
|
||||||
51
src/backend/langflow/components/helpers/RecordComponent2.py
Normal file
51
src/backend/langflow/components/helpers/RecordComponent2.py
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
from typing import Any, List
|
||||||
|
|
||||||
|
from langflow import CustomComponent
|
||||||
|
from langflow.schema import Record
|
||||||
|
from langflow.template.field.base import TemplateField
|
||||||
|
|
||||||
|
|
||||||
|
class RecordComponent2(CustomComponent):
|
||||||
|
display_name = "Record Text"
|
||||||
|
description = "A component to create a record from key-value pairs."
|
||||||
|
field_order = ["keys"]
|
||||||
|
|
||||||
|
def update_build_config(self, build_config: dict, field_name: str, field_value: Any):
|
||||||
|
if field_value is None:
|
||||||
|
field_value = []
|
||||||
|
if field_name is None:
|
||||||
|
return build_config
|
||||||
|
elif len(field_value) == 0:
|
||||||
|
keep = ["keys", "code"]
|
||||||
|
for key in build_config.copy():
|
||||||
|
if key in keep:
|
||||||
|
continue
|
||||||
|
del build_config[key]
|
||||||
|
build_config[field_name]["value"] = field_value
|
||||||
|
|
||||||
|
# Add new fields depending on the field value
|
||||||
|
for val in field_value:
|
||||||
|
if not isinstance(val, str) or val == "":
|
||||||
|
continue
|
||||||
|
field = TemplateField(
|
||||||
|
name=val,
|
||||||
|
field_type="str",
|
||||||
|
display_name="",
|
||||||
|
info="The key for the record.",
|
||||||
|
)
|
||||||
|
build_config[field.name] = field.to_dict()
|
||||||
|
|
||||||
|
def build_config(self):
|
||||||
|
return {
|
||||||
|
"keys": {
|
||||||
|
"display_name": "Keys",
|
||||||
|
"refresh": True,
|
||||||
|
"info": "The number of keys to create in the record.",
|
||||||
|
"input_types": [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(self, keys: List[str], **kwargs) -> Record:
|
||||||
|
record = Record(data=kwargs)
|
||||||
|
self.status = record
|
||||||
|
return record
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from langflow import CustomComponent
|
|
||||||
from langflow.schema import Record
|
|
||||||
|
|
||||||
|
|
||||||
class SharedState(CustomComponent):
|
|
||||||
display_name = "Shared State"
|
|
||||||
description = "A component to share state between components."
|
|
||||||
|
|
||||||
def build_config(self):
|
|
||||||
return {
|
|
||||||
"name": {"display_name": "Name", "info": "The name of the state."},
|
|
||||||
"record": {"display_name": "Record", "info": "The record to store."},
|
|
||||||
"append": {
|
|
||||||
"display_name": "Append",
|
|
||||||
"info": "If True, the record will be appended to the state.",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def build(
|
|
||||||
self, name: str, record: Optional[Record] = None, append: bool = False
|
|
||||||
) -> Record:
|
|
||||||
if record:
|
|
||||||
if append:
|
|
||||||
self.append_state(name, record)
|
|
||||||
else:
|
|
||||||
self.update_state(name, record)
|
|
||||||
|
|
||||||
state = self.get_state(name)
|
|
||||||
if state and not isinstance(state, Record):
|
|
||||||
if isinstance(state, str):
|
|
||||||
state = Record(text=state)
|
|
||||||
elif isinstance(state, dict):
|
|
||||||
state = Record(data=state)
|
|
||||||
else:
|
|
||||||
state = Record(text=str(state))
|
|
||||||
elif not state:
|
|
||||||
state = Record(text="")
|
|
||||||
self.status = state
|
|
||||||
return state
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
# Implement ShouldRunNext component
|
|
||||||
from typing import Text
|
|
||||||
from langchain_core.prompts import PromptTemplate
|
|
||||||
|
|
||||||
from langflow import CustomComponent
|
|
||||||
from langflow.field_typing import BaseLanguageModel, Prompt
|
|
||||||
|
|
||||||
|
|
||||||
class ShouldRunNext(CustomComponent):
|
|
||||||
display_name = "Should Run Next"
|
|
||||||
description = "Decides whether to run the next component."
|
|
||||||
|
|
||||||
def build_config(self):
|
|
||||||
return {
|
|
||||||
"prompt": {
|
|
||||||
"display_name": "Prompt",
|
|
||||||
"info": "The prompt to use for the decision. It should generate a boolean response (True or False).",
|
|
||||||
},
|
|
||||||
"llm": {
|
|
||||||
"display_name": "LLM",
|
|
||||||
"info": "The language model to use for the decision.",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def build(self, template: Prompt, llm: BaseLanguageModel, **kwargs) -> dict:
|
|
||||||
# This is a simple component that always returns True
|
|
||||||
prompt_template = PromptTemplate.from_template(Text(template))
|
|
||||||
|
|
||||||
attributes_to_check = ["text", "page_content"]
|
|
||||||
for key, value in kwargs.items():
|
|
||||||
for attribute in attributes_to_check:
|
|
||||||
if hasattr(value, attribute):
|
|
||||||
kwargs[key] = getattr(value, attribute)
|
|
||||||
|
|
||||||
chain = prompt_template | llm
|
|
||||||
result = chain.invoke(kwargs)
|
|
||||||
if hasattr(result, "content") and isinstance(result.content, str):
|
|
||||||
result = result.content
|
|
||||||
elif isinstance(result, str):
|
|
||||||
result = result
|
|
||||||
else:
|
|
||||||
result = result.get("response")
|
|
||||||
|
|
||||||
if result.lower() not in ["true", "false"]:
|
|
||||||
raise ValueError("The prompt should generate a boolean response (True or False).")
|
|
||||||
# The string should be the words true or false
|
|
||||||
# if not raise an error
|
|
||||||
bool_result = result.lower() == "true"
|
|
||||||
return {"condition": bool_result, "result": kwargs}
|
|
||||||
|
|
@ -9,9 +9,7 @@ from langflow.schema.schema import Record
|
||||||
class LanguageRecursiveTextSplitterComponent(CustomComponent):
|
class LanguageRecursiveTextSplitterComponent(CustomComponent):
|
||||||
display_name: str = "Language Recursive Text Splitter"
|
display_name: str = "Language Recursive Text Splitter"
|
||||||
description: str = "Split text into chunks of a specified length based on language."
|
description: str = "Split text into chunks of a specified length based on language."
|
||||||
documentation: str = (
|
documentation: str = "https://docs.langflow.org/components/text-splitters#languagerecursivetextsplitter"
|
||||||
"https://docs.langflow.org/components/text-splitters#languagerecursivetextsplitter"
|
|
||||||
)
|
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
options = [x.value for x in Language]
|
options = [x.value for x in Language]
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,7 @@ from langflow.utils.util import build_loader_repr_from_documents
|
||||||
class RecursiveCharacterTextSplitterComponent(CustomComponent):
|
class RecursiveCharacterTextSplitterComponent(CustomComponent):
|
||||||
display_name: str = "Recursive Character Text Splitter"
|
display_name: str = "Recursive Character Text Splitter"
|
||||||
description: str = "Split text into chunks of a specified length."
|
description: str = "Split text into chunks of a specified length."
|
||||||
documentation: str = (
|
documentation: str = "https://docs.langflow.org/components/text-splitters#recursivecharactertextsplitter"
|
||||||
"https://docs.langflow.org/components/text-splitters#recursivecharactertextsplitter"
|
|
||||||
)
|
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -85,8 +85,7 @@ class ChromaComponent(CustomComponent):
|
||||||
|
|
||||||
if chroma_server_host is not None:
|
if chroma_server_host is not None:
|
||||||
chroma_settings = chromadb.config.Settings(
|
chroma_settings = chromadb.config.Settings(
|
||||||
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins
|
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins or None,
|
||||||
or None,
|
|
||||||
chroma_server_host=chroma_server_host,
|
chroma_server_host=chroma_server_host,
|
||||||
chroma_server_port=chroma_server_port or None,
|
chroma_server_port=chroma_server_port or None,
|
||||||
chroma_server_grpc_port=chroma_server_grpc_port or None,
|
chroma_server_grpc_port=chroma_server_grpc_port or None,
|
||||||
|
|
@ -107,9 +106,7 @@ class ChromaComponent(CustomComponent):
|
||||||
documents.append(_input)
|
documents.append(_input)
|
||||||
if documents is not None and embedding is not None:
|
if documents is not None and embedding is not None:
|
||||||
if len(documents) == 0:
|
if len(documents) == 0:
|
||||||
raise ValueError(
|
raise ValueError("If documents are provided, there must be at least one document.")
|
||||||
"If documents are provided, there must be at least one document."
|
|
||||||
)
|
|
||||||
chroma = Chroma.from_documents(
|
chroma = Chroma.from_documents(
|
||||||
documents=documents, # type: ignore
|
documents=documents, # type: ignore
|
||||||
persist_directory=index_directory,
|
persist_directory=index_directory,
|
||||||
|
|
|
||||||
|
|
@ -92,8 +92,7 @@ class ChromaSearchComponent(LCVectorStoreComponent):
|
||||||
|
|
||||||
if chroma_server_host is not None:
|
if chroma_server_host is not None:
|
||||||
chroma_settings = chromadb.config.Settings(
|
chroma_settings = chromadb.config.Settings(
|
||||||
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins
|
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins or None,
|
||||||
or None,
|
|
||||||
chroma_server_host=chroma_server_host,
|
chroma_server_host=chroma_server_host,
|
||||||
chroma_server_port=chroma_server_port or None,
|
chroma_server_port=chroma_server_port or None,
|
||||||
chroma_server_grpc_port=chroma_server_grpc_port or None,
|
chroma_server_grpc_port=chroma_server_grpc_port or None,
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,7 @@ class FAISSSearchComponent(LCVectorStoreComponent):
|
||||||
if not folder_path:
|
if not folder_path:
|
||||||
raise ValueError("Folder path is required to save the FAISS index.")
|
raise ValueError("Folder path is required to save the FAISS index.")
|
||||||
path = self.resolve_path(folder_path)
|
path = self.resolve_path(folder_path)
|
||||||
vector_store = FAISS.load_local(
|
vector_store = FAISS.load_local(folder_path=Text(path), embeddings=embedding, index_name=index_name)
|
||||||
folder_path=Text(path), embeddings=embedding, index_name=index_name
|
|
||||||
)
|
|
||||||
if not vector_store:
|
if not vector_store:
|
||||||
raise ValueError("Failed to load the FAISS index.")
|
raise ValueError("Failed to load the FAISS index.")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,7 @@ from langflow.schema.schema import Record
|
||||||
|
|
||||||
class MongoDBAtlasComponent(CustomComponent):
|
class MongoDBAtlasComponent(CustomComponent):
|
||||||
display_name = "MongoDB Atlas"
|
display_name = "MongoDB Atlas"
|
||||||
description = (
|
description = "Construct a `MongoDB Atlas Vector Search` vector store from raw documents."
|
||||||
"Construct a `MongoDB Atlas Vector Search` vector store from raw documents."
|
|
||||||
)
|
|
||||||
icon = "MongoDB"
|
icon = "MongoDB"
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
|
|
@ -39,9 +37,7 @@ class MongoDBAtlasComponent(CustomComponent):
|
||||||
try:
|
try:
|
||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise ImportError(
|
raise ImportError("Please install pymongo to use MongoDB Atlas Vector Store")
|
||||||
"Please install pymongo to use MongoDB Atlas Vector Store"
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
mongo_client: MongoClient = MongoClient(mongodb_atlas_cluster_uri)
|
mongo_client: MongoClient = MongoClient(mongodb_atlas_cluster_uri)
|
||||||
collection = mongo_client[db_name][collection_name]
|
collection = mongo_client[db_name][collection_name]
|
||||||
|
|
|
||||||
|
|
@ -67,9 +67,7 @@ class RedisComponent(CustomComponent):
|
||||||
documents.append(_input)
|
documents.append(_input)
|
||||||
if not documents:
|
if not documents:
|
||||||
if schema is None:
|
if schema is None:
|
||||||
raise ValueError(
|
raise ValueError("If no documents are provided, a schema must be provided.")
|
||||||
"If no documents are provided, a schema must be provided."
|
|
||||||
)
|
|
||||||
redis_vs = Redis.from_existing_index(
|
redis_vs = Redis.from_existing_index(
|
||||||
embedding=embedding,
|
embedding=embedding,
|
||||||
index_name=redis_index_name,
|
index_name=redis_index_name,
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ class RedisSearchComponent(RedisComponent, LCVectorStoreComponent):
|
||||||
"input_value": {"display_name": "Input"},
|
"input_value": {"display_name": "Input"},
|
||||||
"index_name": {"display_name": "Index Name", "value": "your_index"},
|
"index_name": {"display_name": "Index Name", "value": "your_index"},
|
||||||
"code": {"show": False, "display_name": "Code"},
|
"code": {"show": False, "display_name": "Code"},
|
||||||
|
|
||||||
"embedding": {"display_name": "Embedding"},
|
"embedding": {"display_name": "Embedding"},
|
||||||
"schema": {"display_name": "Schema", "file_types": [".yaml"]},
|
"schema": {"display_name": "Schema", "file_types": [".yaml"]},
|
||||||
"redis_server_url": {
|
"redis_server_url": {
|
||||||
|
|
|
||||||
|
|
@ -35,9 +35,7 @@ class SupabaseComponent(CustomComponent):
|
||||||
supabase_url: str = "",
|
supabase_url: str = "",
|
||||||
table_name: str = "",
|
table_name: str = "",
|
||||||
) -> Union[VectorStore, SupabaseVectorStore, BaseRetriever]:
|
) -> Union[VectorStore, SupabaseVectorStore, BaseRetriever]:
|
||||||
supabase: Client = create_client(
|
supabase: Client = create_client(supabase_url, supabase_key=supabase_service_key)
|
||||||
supabase_url, supabase_key=supabase_service_key
|
|
||||||
)
|
|
||||||
documents = []
|
documents = []
|
||||||
for _input in inputs:
|
for _input in inputs:
|
||||||
if isinstance(_input, Record):
|
if isinstance(_input, Record):
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,7 @@ class SupabaseSearchComponent(LCVectorStoreComponent):
|
||||||
supabase_url: str = "",
|
supabase_url: str = "",
|
||||||
table_name: str = "",
|
table_name: str = "",
|
||||||
) -> List[Record]:
|
) -> List[Record]:
|
||||||
supabase: Client = create_client(
|
supabase: Client = create_client(supabase_url, supabase_key=supabase_service_key)
|
||||||
supabase_url, supabase_key=supabase_service_key
|
|
||||||
)
|
|
||||||
vector_store = SupabaseVectorStore(
|
vector_store = SupabaseVectorStore(
|
||||||
client=supabase,
|
client=supabase,
|
||||||
embedding=embedding,
|
embedding=embedding,
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,7 @@ from langflow.schema.schema import Record
|
||||||
class VectaraComponent(CustomComponent):
|
class VectaraComponent(CustomComponent):
|
||||||
display_name: str = "Vectara"
|
display_name: str = "Vectara"
|
||||||
description: str = "Implementation of Vector Store using Vectara"
|
description: str = "Implementation of Vector Store using Vectara"
|
||||||
documentation = (
|
documentation = "https://python.langchain.com/docs/integrations/vectorstores/vectara"
|
||||||
"https://python.langchain.com/docs/integrations/vectorstores/vectara"
|
|
||||||
)
|
|
||||||
beta = True
|
beta = True
|
||||||
icon = "Vectara"
|
icon = "Vectara"
|
||||||
field_config = {
|
field_config = {
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,7 @@ from langflow.schema import Record
|
||||||
class VectaraSearchComponent(VectaraComponent, LCVectorStoreComponent):
|
class VectaraSearchComponent(VectaraComponent, LCVectorStoreComponent):
|
||||||
display_name: str = "Vectara Search"
|
display_name: str = "Vectara Search"
|
||||||
description: str = "Search a Vectara Vector Store for similar documents."
|
description: str = "Search a Vectara Vector Store for similar documents."
|
||||||
documentation = (
|
documentation = "https://python.langchain.com/docs/integrations/vectorstores/vectara"
|
||||||
"https://python.langchain.com/docs/integrations/vectorstores/vectara"
|
|
||||||
)
|
|
||||||
beta = True
|
beta = True
|
||||||
icon = "Vectara"
|
icon = "Vectara"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,7 @@ from langflow.schema.schema import Record
|
||||||
class WeaviateVectorStoreComponent(CustomComponent):
|
class WeaviateVectorStoreComponent(CustomComponent):
|
||||||
display_name: str = "Weaviate"
|
display_name: str = "Weaviate"
|
||||||
description: str = "Implementation of Vector Store using Weaviate"
|
description: str = "Implementation of Vector Store using Weaviate"
|
||||||
documentation = (
|
documentation = "https://python.langchain.com/docs/integrations/vectorstores/weaviate"
|
||||||
"https://python.langchain.com/docs/integrations/vectorstores/weaviate"
|
|
||||||
)
|
|
||||||
beta = True
|
beta = True
|
||||||
field_config = {
|
field_config = {
|
||||||
"url": {"display_name": "Weaviate URL", "value": "http://localhost:8080"},
|
"url": {"display_name": "Weaviate URL", "value": "http://localhost:8080"},
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,7 @@ from langflow.schema import Record
|
||||||
class WeaviateSearchVectorStore(WeaviateVectorStoreComponent, LCVectorStoreComponent):
|
class WeaviateSearchVectorStore(WeaviateVectorStoreComponent, LCVectorStoreComponent):
|
||||||
display_name: str = "Weaviate Search"
|
display_name: str = "Weaviate Search"
|
||||||
description: str = "Search a Weaviate Vector Store for similar documents."
|
description: str = "Search a Weaviate Vector Store for similar documents."
|
||||||
documentation = (
|
documentation = "https://python.langchain.com/docs/integrations/vectorstores/weaviate"
|
||||||
"https://python.langchain.com/docs/integrations/vectorstores/weaviate"
|
|
||||||
)
|
|
||||||
beta = True
|
beta = True
|
||||||
icon = "Weaviate"
|
icon = "Weaviate"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,14 +37,8 @@ class LCVectorStoreComponent(CustomComponent):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
docs: List[Document] = []
|
docs: List[Document] = []
|
||||||
if (
|
if input_value and isinstance(input_value, str) and hasattr(vector_store, "search"):
|
||||||
input_value
|
docs = vector_store.search(query=input_value, search_type=search_type.lower())
|
||||||
and isinstance(input_value, str)
|
|
||||||
and hasattr(vector_store, "search")
|
|
||||||
):
|
|
||||||
docs = vector_store.search(
|
|
||||||
query=input_value, search_type=search_type.lower()
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid inputs provided.")
|
raise ValueError("Invalid inputs provided.")
|
||||||
return docs_to_records(docs)
|
return docs_to_records(docs)
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,7 @@ class PGVectorComponent(CustomComponent):
|
||||||
|
|
||||||
display_name: str = "PGVector"
|
display_name: str = "PGVector"
|
||||||
description: str = "Implementation of Vector Store using PostgreSQL"
|
description: str = "Implementation of Vector Store using PostgreSQL"
|
||||||
documentation = (
|
documentation = "https://python.langchain.com/docs/integrations/vectorstores/pgvector"
|
||||||
"https://python.langchain.com/docs/integrations/vectorstores/pgvector"
|
|
||||||
)
|
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,7 @@ class PGVectorSearchComponent(PGVectorComponent, LCVectorStoreComponent):
|
||||||
|
|
||||||
display_name: str = "PGVector Search"
|
display_name: str = "PGVector Search"
|
||||||
description: str = "Search a PGVector Store for similar documents."
|
description: str = "Search a PGVector Store for similar documents."
|
||||||
documentation = (
|
documentation = "https://python.langchain.com/docs/integrations/vectorstores/pgvector"
|
||||||
"https://python.langchain.com/docs/integrations/vectorstores/pgvector"
|
|
||||||
)
|
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class SourceHandle(BaseModel):
|
class SourceHandle(BaseModel):
|
||||||
baseClasses: List[str] = Field(
|
baseClasses: List[str] = Field(..., description="List of base classes for the source handle.")
|
||||||
..., description="List of base classes for the source handle."
|
|
||||||
)
|
|
||||||
dataType: str = Field(..., description="Data type for the source handle.")
|
dataType: str = Field(..., description="Data type for the source handle.")
|
||||||
id: str = Field(..., description="Unique identifier for the source handle.")
|
id: str = Field(..., description="Unique identifier for the source handle.")
|
||||||
|
|
||||||
|
|
@ -22,9 +20,7 @@ class SourceHandle(BaseModel):
|
||||||
class TargetHandle(BaseModel):
|
class TargetHandle(BaseModel):
|
||||||
fieldName: str = Field(..., description="Field name for the target handle.")
|
fieldName: str = Field(..., description="Field name for the target handle.")
|
||||||
id: str = Field(..., description="Unique identifier for the target handle.")
|
id: str = Field(..., description="Unique identifier for the target handle.")
|
||||||
inputTypes: Optional[List[str]] = Field(
|
inputTypes: Optional[List[str]] = Field(None, description="List of input types for the target handle.")
|
||||||
None, description="List of input types for the target handle."
|
|
||||||
)
|
|
||||||
type: str = Field(..., description="Type of the target handle.")
|
type: str = Field(..., description="Type of the target handle.")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -53,24 +49,16 @@ class Edge:
|
||||||
|
|
||||||
def validate_handles(self, source, target) -> None:
|
def validate_handles(self, source, target) -> None:
|
||||||
if self.target_handle.inputTypes is None:
|
if self.target_handle.inputTypes is None:
|
||||||
self.valid_handles = (
|
self.valid_handles = self.target_handle.type in self.source_handle.baseClasses
|
||||||
self.target_handle.type in self.source_handle.baseClasses
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.valid_handles = (
|
self.valid_handles = (
|
||||||
any(
|
any(baseClass in self.target_handle.inputTypes for baseClass in self.source_handle.baseClasses)
|
||||||
baseClass in self.target_handle.inputTypes
|
|
||||||
for baseClass in self.source_handle.baseClasses
|
|
||||||
)
|
|
||||||
or self.target_handle.type in self.source_handle.baseClasses
|
or self.target_handle.type in self.source_handle.baseClasses
|
||||||
)
|
)
|
||||||
if not self.valid_handles:
|
if not self.valid_handles:
|
||||||
logger.debug(self.source_handle)
|
logger.debug(self.source_handle)
|
||||||
logger.debug(self.target_handle)
|
logger.debug(self.target_handle)
|
||||||
raise ValueError(
|
raise ValueError(f"Edge between {source.vertex_type} and {target.vertex_type} " f"has invalid handles")
|
||||||
f"Edge between {source.vertex_type} and {target.vertex_type} "
|
|
||||||
f"has invalid handles"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __setstate__(self, state):
|
def __setstate__(self, state):
|
||||||
self.source_id = state["source_id"]
|
self.source_id = state["source_id"]
|
||||||
|
|
@ -87,11 +75,7 @@ class Edge:
|
||||||
# Both lists contain strings and sometimes a string contains the value we are
|
# Both lists contain strings and sometimes a string contains the value we are
|
||||||
# looking for e.g. comgin_out=["Chain"] and target_reqs=["LLMChain"]
|
# looking for e.g. comgin_out=["Chain"] and target_reqs=["LLMChain"]
|
||||||
# so we need to check if any of the strings in source_types is in target_reqs
|
# so we need to check if any of the strings in source_types is in target_reqs
|
||||||
self.valid = any(
|
self.valid = any(output in target_req for output in self.source_types for target_req in self.target_reqs)
|
||||||
output in target_req
|
|
||||||
for output in self.source_types
|
|
||||||
for target_req in self.target_reqs
|
|
||||||
)
|
|
||||||
# Get what type of input the target node is expecting
|
# Get what type of input the target node is expecting
|
||||||
|
|
||||||
self.matched_type = next(
|
self.matched_type = next(
|
||||||
|
|
@ -102,10 +86,7 @@ class Edge:
|
||||||
if no_matched_type:
|
if no_matched_type:
|
||||||
logger.debug(self.source_types)
|
logger.debug(self.source_types)
|
||||||
logger.debug(self.target_reqs)
|
logger.debug(self.target_reqs)
|
||||||
raise ValueError(
|
raise ValueError(f"Edge between {source.vertex_type} and {target.vertex_type} " f"has no matched type")
|
||||||
f"Edge between {source.vertex_type} and {target.vertex_type} "
|
|
||||||
f"has no matched type"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return (
|
||||||
|
|
@ -118,10 +99,7 @@ class Edge:
|
||||||
|
|
||||||
def __eq__(self, __o: object) -> bool:
|
def __eq__(self, __o: object) -> bool:
|
||||||
# Create a better way to compare edges
|
# Create a better way to compare edges
|
||||||
return (
|
return self._source_handle == __o._source_handle and self._target_handle == __o._target_handle
|
||||||
self._source_handle == __o._source_handle
|
|
||||||
and self._target_handle == __o._target_handle
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ContractEdge(Edge):
|
class ContractEdge(Edge):
|
||||||
|
|
@ -178,9 +156,7 @@ class ContractEdge(Edge):
|
||||||
return f"{self.source_id} -[{self.target_param}]-> {self.target_id}"
|
return f"{self.source_id} -[{self.target_param}]-> {self.target_id}"
|
||||||
|
|
||||||
|
|
||||||
def log_transaction(
|
def log_transaction(edge: ContractEdge, source: "Vertex", target: "Vertex", status, error=None):
|
||||||
edge: ContractEdge, source: "Vertex", target: "Vertex", status, error=None
|
|
||||||
):
|
|
||||||
try:
|
try:
|
||||||
monitor_service = get_monitor_service()
|
monitor_service = get_monitor_service()
|
||||||
clean_params = build_clean_params(target)
|
clean_params = build_clean_params(target)
|
||||||
|
|
|
||||||
|
|
@ -76,9 +76,7 @@ class Graph:
|
||||||
"""Returns the state of the graph."""
|
"""Returns the state of the graph."""
|
||||||
return self.state_manager.get_state(name, run_id=self._run_id)
|
return self.state_manager.get_state(name, run_id=self._run_id)
|
||||||
|
|
||||||
def update_state(
|
def update_state(self, name: str, record: Union[str, Record], caller: Optional[str] = None) -> None:
|
||||||
self, name: str, record: Union[str, Record], caller: Optional[str] = None
|
|
||||||
) -> None:
|
|
||||||
"""Updates the state of the graph."""
|
"""Updates the state of the graph."""
|
||||||
if caller:
|
if caller:
|
||||||
# If there is a caller which is a vertex_id, I want to activate
|
# If there is a caller which is a vertex_id, I want to activate
|
||||||
|
|
@ -110,12 +108,9 @@ class Graph:
|
||||||
def reset_activated_vertices(self):
|
def reset_activated_vertices(self):
|
||||||
self.activated_vertices = []
|
self.activated_vertices = []
|
||||||
|
|
||||||
def append_state(
|
def append_state(self, name: str, record: Union[str, Record], caller: Optional[str] = None) -> None:
|
||||||
self, name: str, record: Union[str, Record], caller: Optional[str] = None
|
|
||||||
) -> None:
|
|
||||||
"""Appends the state of the graph."""
|
"""Appends the state of the graph."""
|
||||||
if caller:
|
if caller:
|
||||||
|
|
||||||
self.activate_state_vertices(name, caller)
|
self.activate_state_vertices(name, caller)
|
||||||
|
|
||||||
self.state_manager.append_state(name, record, run_id=self._run_id)
|
self.state_manager.append_state(name, record, run_id=self._run_id)
|
||||||
|
|
@ -161,10 +156,7 @@ class Graph:
|
||||||
"""Runs the graph with the given inputs."""
|
"""Runs the graph with the given inputs."""
|
||||||
for vertex_id in self._is_input_vertices:
|
for vertex_id in self._is_input_vertices:
|
||||||
vertex = self.get_vertex(vertex_id)
|
vertex = self.get_vertex(vertex_id)
|
||||||
if input_components and (
|
if input_components and (vertex_id not in input_components or vertex.display_name not in input_components):
|
||||||
vertex_id not in input_components
|
|
||||||
or vertex.display_name not in input_components
|
|
||||||
):
|
|
||||||
continue
|
continue
|
||||||
if vertex is None:
|
if vertex is None:
|
||||||
raise ValueError(f"Vertex {vertex_id} not found")
|
raise ValueError(f"Vertex {vertex_id} not found")
|
||||||
|
|
@ -187,11 +179,7 @@ class Graph:
|
||||||
if vertex is None:
|
if vertex is None:
|
||||||
raise ValueError(f"Vertex {vertex_id} not found")
|
raise ValueError(f"Vertex {vertex_id} not found")
|
||||||
|
|
||||||
if (
|
if not vertex.result and not stream and hasattr(vertex, "consume_async_generator"):
|
||||||
not vertex.result
|
|
||||||
and not stream
|
|
||||||
and hasattr(vertex, "consume_async_generator")
|
|
||||||
):
|
|
||||||
await vertex.consume_async_generator()
|
await vertex.consume_async_generator()
|
||||||
if vertex.display_name in outputs or vertex.id in outputs:
|
if vertex.display_name in outputs or vertex.id in outputs:
|
||||||
vertex_outputs.append(vertex.result)
|
vertex_outputs.append(vertex.result)
|
||||||
|
|
@ -269,9 +257,7 @@ class Graph:
|
||||||
def build_parent_child_map(self):
|
def build_parent_child_map(self):
|
||||||
parent_child_map = defaultdict(list)
|
parent_child_map = defaultdict(list)
|
||||||
for vertex in self.vertices:
|
for vertex in self.vertices:
|
||||||
parent_child_map[vertex.id] = [
|
parent_child_map[vertex.id] = [child.id for child in self.get_successors(vertex)]
|
||||||
child.id for child in self.get_successors(vertex)
|
|
||||||
]
|
|
||||||
return parent_child_map
|
return parent_child_map
|
||||||
|
|
||||||
def increment_run_count(self):
|
def increment_run_count(self):
|
||||||
|
|
@ -456,11 +442,7 @@ class Graph:
|
||||||
"""Updates the edges of a vertex."""
|
"""Updates the edges of a vertex."""
|
||||||
# Vertex has edges, so we need to update the edges
|
# Vertex has edges, so we need to update the edges
|
||||||
for edge in vertex.edges:
|
for edge in vertex.edges:
|
||||||
if (
|
if edge not in self.edges and edge.source_id in self.vertex_map and edge.target_id in self.vertex_map:
|
||||||
edge not in self.edges
|
|
||||||
and edge.source_id in self.vertex_map
|
|
||||||
and edge.target_id in self.vertex_map
|
|
||||||
):
|
|
||||||
self.edges.append(edge)
|
self.edges.append(edge)
|
||||||
|
|
||||||
def _build_graph(self) -> None:
|
def _build_graph(self) -> None:
|
||||||
|
|
@ -485,11 +467,7 @@ class Graph:
|
||||||
return
|
return
|
||||||
self.vertices.remove(vertex)
|
self.vertices.remove(vertex)
|
||||||
self.vertex_map.pop(vertex_id)
|
self.vertex_map.pop(vertex_id)
|
||||||
self.edges = [
|
self.edges = [edge for edge in self.edges if edge.source_id != vertex_id and edge.target_id != vertex_id]
|
||||||
edge
|
|
||||||
for edge in self.edges
|
|
||||||
if edge.source_id != vertex_id and edge.target_id != vertex_id
|
|
||||||
]
|
|
||||||
|
|
||||||
def _build_vertex_params(self) -> None:
|
def _build_vertex_params(self) -> None:
|
||||||
"""Identifies and handles the LLM vertex within the graph."""
|
"""Identifies and handles the LLM vertex within the graph."""
|
||||||
|
|
@ -510,9 +488,7 @@ class Graph:
|
||||||
return
|
return
|
||||||
for vertex in self.vertices:
|
for vertex in self.vertices:
|
||||||
if not self._validate_vertex(vertex):
|
if not self._validate_vertex(vertex):
|
||||||
raise ValueError(
|
raise ValueError(f"{vertex.display_name} is not connected to any other components")
|
||||||
f"{vertex.display_name} is not connected to any other components"
|
|
||||||
)
|
|
||||||
|
|
||||||
def _validate_vertex(self, vertex: Vertex) -> bool:
|
def _validate_vertex(self, vertex: Vertex) -> bool:
|
||||||
"""Validates a vertex."""
|
"""Validates a vertex."""
|
||||||
|
|
@ -574,9 +550,7 @@ class Graph:
|
||||||
name=f"{vertex.display_name} Run {vertex_task_run_count.get(vertex_id, 0)}",
|
name=f"{vertex.display_name} Run {vertex_task_run_count.get(vertex_id, 0)}",
|
||||||
)
|
)
|
||||||
tasks.append(task)
|
tasks.append(task)
|
||||||
vertex_task_run_count[vertex_id] = (
|
vertex_task_run_count[vertex_id] = vertex_task_run_count.get(vertex_id, 0) + 1
|
||||||
vertex_task_run_count.get(vertex_id, 0) + 1
|
|
||||||
)
|
|
||||||
logger.debug(f"Running layer {layer_index} with {len(tasks)} tasks")
|
logger.debug(f"Running layer {layer_index} with {len(tasks)} tasks")
|
||||||
await self._execute_tasks(tasks)
|
await self._execute_tasks(tasks)
|
||||||
logger.debug("Graph processing complete")
|
logger.debug("Graph processing complete")
|
||||||
|
|
@ -618,9 +592,7 @@ class Graph:
|
||||||
def dfs(vertex):
|
def dfs(vertex):
|
||||||
if state[vertex] == 1:
|
if state[vertex] == 1:
|
||||||
# We have a cycle
|
# We have a cycle
|
||||||
raise ValueError(
|
raise ValueError("Graph contains a cycle, cannot perform topological sort")
|
||||||
"Graph contains a cycle, cannot perform topological sort"
|
|
||||||
)
|
|
||||||
if state[vertex] == 0:
|
if state[vertex] == 0:
|
||||||
state[vertex] = 1
|
state[vertex] = 1
|
||||||
for edge in vertex.edges:
|
for edge in vertex.edges:
|
||||||
|
|
@ -644,10 +616,7 @@ class Graph:
|
||||||
|
|
||||||
def get_predecessors(self, vertex):
|
def get_predecessors(self, vertex):
|
||||||
"""Returns the predecessors of a vertex."""
|
"""Returns the predecessors of a vertex."""
|
||||||
return [
|
return [self.get_vertex(source_id) for source_id in self.predecessor_map.get(vertex.id, [])]
|
||||||
self.get_vertex(source_id)
|
|
||||||
for source_id in self.predecessor_map.get(vertex.id, [])
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_all_successors(self, vertex, recursive=True, flat=True):
|
def get_all_successors(self, vertex, recursive=True, flat=True):
|
||||||
# Recursively get the successors of the current vertex
|
# Recursively get the successors of the current vertex
|
||||||
|
|
@ -688,10 +657,7 @@ class Graph:
|
||||||
|
|
||||||
def get_successors(self, vertex):
|
def get_successors(self, vertex):
|
||||||
"""Returns the successors of a vertex."""
|
"""Returns the successors of a vertex."""
|
||||||
return [
|
return [self.get_vertex(target_id) for target_id in self.successor_map.get(vertex.id, [])]
|
||||||
self.get_vertex(target_id)
|
|
||||||
for target_id in self.successor_map.get(vertex.id, [])
|
|
||||||
]
|
|
||||||
|
|
||||||
def get_vertex_neighbors(self, vertex: Vertex) -> Dict[Vertex, int]:
|
def get_vertex_neighbors(self, vertex: Vertex) -> Dict[Vertex, int]:
|
||||||
"""Returns the neighbors of a vertex."""
|
"""Returns the neighbors of a vertex."""
|
||||||
|
|
@ -737,9 +703,7 @@ class Graph:
|
||||||
edges_added.add((source.id, target.id))
|
edges_added.add((source.id, target.id))
|
||||||
return edges
|
return edges
|
||||||
|
|
||||||
def _get_vertex_class(
|
def _get_vertex_class(self, node_type: str, node_base_type: str, node_id: str) -> Type[Vertex]:
|
||||||
self, node_type: str, node_base_type: str, node_id: str
|
|
||||||
) -> Type[Vertex]:
|
|
||||||
"""Returns the node class based on the node type."""
|
"""Returns the node class based on the node type."""
|
||||||
# First we check for the node_base_type
|
# First we check for the node_base_type
|
||||||
node_name = node_id.split("-")[0]
|
node_name = node_id.split("-")[0]
|
||||||
|
|
@ -772,18 +736,14 @@ class Graph:
|
||||||
vertex_type: str = vertex_data["type"] # type: ignore
|
vertex_type: str = vertex_data["type"] # type: ignore
|
||||||
vertex_base_type: str = vertex_data["node"]["template"]["_type"] # type: ignore
|
vertex_base_type: str = vertex_data["node"]["template"]["_type"] # type: ignore
|
||||||
|
|
||||||
VertexClass = self._get_vertex_class(
|
VertexClass = self._get_vertex_class(vertex_type, vertex_base_type, vertex_data["id"])
|
||||||
vertex_type, vertex_base_type, vertex_data["id"]
|
|
||||||
)
|
|
||||||
vertex_instance = VertexClass(vertex, graph=self)
|
vertex_instance = VertexClass(vertex, graph=self)
|
||||||
vertex_instance.set_top_level(self.top_level_vertices)
|
vertex_instance.set_top_level(self.top_level_vertices)
|
||||||
vertices.append(vertex_instance)
|
vertices.append(vertex_instance)
|
||||||
|
|
||||||
return vertices
|
return vertices
|
||||||
|
|
||||||
def get_children_by_vertex_type(
|
def get_children_by_vertex_type(self, vertex: Vertex, vertex_type: str) -> List[Vertex]:
|
||||||
self, vertex: Vertex, vertex_type: str
|
|
||||||
) -> List[Vertex]:
|
|
||||||
"""Returns the children of a vertex based on the vertex type."""
|
"""Returns the children of a vertex based on the vertex type."""
|
||||||
children = []
|
children = []
|
||||||
vertex_types = [vertex.data["type"]]
|
vertex_types = [vertex.data["type"]]
|
||||||
|
|
@ -795,9 +755,7 @@ class Graph:
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
vertex_ids = [vertex.id for vertex in self.vertices]
|
vertex_ids = [vertex.id for vertex in self.vertices]
|
||||||
edges_repr = "\n".join(
|
edges_repr = "\n".join([f"{edge.source_id} --> {edge.target_id}" for edge in self.edges])
|
||||||
[f"{edge.source_id} --> {edge.target_id}" for edge in self.edges]
|
|
||||||
)
|
|
||||||
return f"Graph:\nNodes: {vertex_ids}\nConnections:\n{edges_repr}"
|
return f"Graph:\nNodes: {vertex_ids}\nConnections:\n{edges_repr}"
|
||||||
|
|
||||||
def sort_up_to_vertex(self, vertex_id: str, is_start: bool = False) -> List[Vertex]:
|
def sort_up_to_vertex(self, vertex_id: str, is_start: bool = False) -> List[Vertex]:
|
||||||
|
|
@ -865,8 +823,7 @@ class Graph:
|
||||||
vertex.id
|
vertex.id
|
||||||
for vertex in vertices
|
for vertex in vertices
|
||||||
# if filter_graphs then only vertex.is_input will be considered
|
# if filter_graphs then only vertex.is_input will be considered
|
||||||
if self.in_degree_map[vertex.id] == 0
|
if self.in_degree_map[vertex.id] == 0 and (not filter_graphs or vertex.is_input)
|
||||||
and (not filter_graphs or vertex.is_input)
|
|
||||||
)
|
)
|
||||||
layers: List[List[str]] = []
|
layers: List[List[str]] = []
|
||||||
visited = set(queue)
|
visited = set(queue)
|
||||||
|
|
@ -940,9 +897,7 @@ class Graph:
|
||||||
|
|
||||||
return refined_layers
|
return refined_layers
|
||||||
|
|
||||||
def sort_chat_inputs_first(
|
def sort_chat_inputs_first(self, vertices_layers: List[List[str]]) -> List[List[str]]:
|
||||||
self, vertices_layers: List[List[str]]
|
|
||||||
) -> List[List[str]]:
|
|
||||||
chat_inputs_first = []
|
chat_inputs_first = []
|
||||||
for layer in vertices_layers:
|
for layer in vertices_layers:
|
||||||
for vertex_id in layer:
|
for vertex_id in layer:
|
||||||
|
|
@ -983,9 +938,7 @@ class Graph:
|
||||||
first_layer = vertices_layers[0]
|
first_layer = vertices_layers[0]
|
||||||
# save the only the rest
|
# save the only the rest
|
||||||
self.vertices_layers = vertices_layers[1:]
|
self.vertices_layers = vertices_layers[1:]
|
||||||
self.vertices_to_run = {
|
self.vertices_to_run = {vertex_id for vertex_id in chain.from_iterable(vertices_layers)}
|
||||||
vertex_id for vertex_id in chain.from_iterable(vertices_layers)
|
|
||||||
}
|
|
||||||
# Return just the first layer
|
# Return just the first layer
|
||||||
return first_layer
|
return first_layer
|
||||||
|
|
||||||
|
|
@ -996,15 +949,11 @@ class Graph:
|
||||||
self.vertices_to_run.remove(vertex_id)
|
self.vertices_to_run.remove(vertex_id)
|
||||||
return should_run
|
return should_run
|
||||||
|
|
||||||
def sort_interface_components_first(
|
def sort_interface_components_first(self, vertices_layers: List[List[str]]) -> List[List[str]]:
|
||||||
self, vertices_layers: List[List[str]]
|
|
||||||
) -> List[List[str]]:
|
|
||||||
"""Sorts the vertices in the graph so that vertices containing ChatInput or ChatOutput come first."""
|
"""Sorts the vertices in the graph so that vertices containing ChatInput or ChatOutput come first."""
|
||||||
|
|
||||||
def contains_interface_component(vertex):
|
def contains_interface_component(vertex):
|
||||||
return any(
|
return any(component.value in vertex for component in InterfaceComponentTypes)
|
||||||
component.value in vertex for component in InterfaceComponentTypes
|
|
||||||
)
|
|
||||||
|
|
||||||
# Sort each inner list so that vertices containing ChatInput or ChatOutput come first
|
# Sort each inner list so that vertices containing ChatInput or ChatOutput come first
|
||||||
sorted_vertices = [
|
sorted_vertices = [
|
||||||
|
|
@ -1016,22 +965,16 @@ class Graph:
|
||||||
]
|
]
|
||||||
return sorted_vertices
|
return sorted_vertices
|
||||||
|
|
||||||
def sort_by_avg_build_time(
|
def sort_by_avg_build_time(self, vertices_layers: List[List[str]]) -> List[List[str]]:
|
||||||
self, vertices_layers: List[List[str]]
|
|
||||||
) -> List[List[str]]:
|
|
||||||
"""Sorts the vertices in the graph so that vertices with the lowest average build time come first."""
|
"""Sorts the vertices in the graph so that vertices with the lowest average build time come first."""
|
||||||
|
|
||||||
def sort_layer_by_avg_build_time(vertices_ids: List[str]) -> List[str]:
|
def sort_layer_by_avg_build_time(vertices_ids: List[str]) -> List[str]:
|
||||||
"""Sorts the vertices in the graph so that vertices with the lowest average build time come first."""
|
"""Sorts the vertices in the graph so that vertices with the lowest average build time come first."""
|
||||||
if len(vertices_ids) == 1:
|
if len(vertices_ids) == 1:
|
||||||
return vertices_ids
|
return vertices_ids
|
||||||
vertices_ids.sort(
|
vertices_ids.sort(key=lambda vertex_id: self.get_vertex(vertex_id).avg_build_time)
|
||||||
key=lambda vertex_id: self.get_vertex(vertex_id).avg_build_time
|
|
||||||
)
|
|
||||||
|
|
||||||
return vertices_ids
|
return vertices_ids
|
||||||
|
|
||||||
sorted_vertices = [
|
sorted_vertices = [sort_layer_by_avg_build_time(layer) for layer in vertices_layers]
|
||||||
sort_layer_by_avg_build_time(layer) for layer in vertices_layers
|
|
||||||
]
|
|
||||||
return sorted_vertices
|
return sorted_vertices
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,7 @@ class VertexTypesDict(LazyLoadDictBase):
|
||||||
**{t: types.DocumentLoaderVertex for t in documentloader_creator.to_list()},
|
**{t: types.DocumentLoaderVertex for t in documentloader_creator.to_list()},
|
||||||
**{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()},
|
**{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()},
|
||||||
**{t: types.OutputParserVertex for t in output_parser_creator.to_list()},
|
**{t: types.OutputParserVertex for t in output_parser_creator.to_list()},
|
||||||
**{
|
**{t: types.CustomComponentVertex for t in custom_component_creator.to_list()},
|
||||||
t: types.CustomComponentVertex
|
|
||||||
for t in custom_component_creator.to_list()
|
|
||||||
},
|
|
||||||
**{t: types.RetrieverVertex for t in retriever_creator.to_list()},
|
**{t: types.RetrieverVertex for t in retriever_creator.to_list()},
|
||||||
**{t: types.ChatVertex for t in CHAT_COMPONENTS},
|
**{t: types.ChatVertex for t in CHAT_COMPONENTS},
|
||||||
**{t: types.RoutingVertex for t in ROUTING_COMPONENTS},
|
**{t: types.RoutingVertex for t in ROUTING_COMPONENTS},
|
||||||
|
|
|
||||||
|
|
@ -59,13 +59,8 @@ class Vertex:
|
||||||
self.updated_raw_params = False
|
self.updated_raw_params = False
|
||||||
self.id: str = data["id"]
|
self.id: str = data["id"]
|
||||||
self.is_state = False
|
self.is_state = False
|
||||||
self.is_input = any(
|
self.is_input = any(input_component_name in self.id for input_component_name in INPUT_COMPONENTS)
|
||||||
input_component_name in self.id for input_component_name in INPUT_COMPONENTS
|
self.is_output = any(output_component_name in self.id for output_component_name in OUTPUT_COMPONENTS)
|
||||||
)
|
|
||||||
self.is_output = any(
|
|
||||||
output_component_name in self.id
|
|
||||||
for output_component_name in OUTPUT_COMPONENTS
|
|
||||||
)
|
|
||||||
self.has_session_id = None
|
self.has_session_id = None
|
||||||
self._custom_component = None
|
self._custom_component = None
|
||||||
self.has_external_input = False
|
self.has_external_input = False
|
||||||
|
|
@ -106,17 +101,11 @@ class Vertex:
|
||||||
|
|
||||||
def set_state(self, state: str):
|
def set_state(self, state: str):
|
||||||
self.state = VertexStates[state]
|
self.state = VertexStates[state]
|
||||||
if (
|
if self.state == VertexStates.INACTIVE and self.graph.in_degree_map[self.id] < 2:
|
||||||
self.state == VertexStates.INACTIVE
|
|
||||||
and self.graph.in_degree_map[self.id] < 2
|
|
||||||
):
|
|
||||||
# If the vertex is inactive and has only one in degree
|
# If the vertex is inactive and has only one in degree
|
||||||
# it means that it is not a merge point in the graph
|
# it means that it is not a merge point in the graph
|
||||||
self.graph.inactivated_vertices.add(self.id)
|
self.graph.inactivated_vertices.add(self.id)
|
||||||
elif (
|
elif self.state == VertexStates.ACTIVE and self.id in self.graph.inactivated_vertices:
|
||||||
self.state == VertexStates.ACTIVE
|
|
||||||
and self.id in self.graph.inactivated_vertices
|
|
||||||
):
|
|
||||||
self.graph.inactivated_vertices.remove(self.id)
|
self.graph.inactivated_vertices.remove(self.id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -133,9 +122,7 @@ class Vertex:
|
||||||
# If the Vertex.type is a power component
|
# If the Vertex.type is a power component
|
||||||
# then we need to return the built object
|
# then we need to return the built object
|
||||||
# instead of the result dict
|
# instead of the result dict
|
||||||
if self.is_interface_component and not isinstance(
|
if self.is_interface_component and not isinstance(self._built_object, UnbuiltObject):
|
||||||
self._built_object, UnbuiltObject
|
|
||||||
):
|
|
||||||
result = self._built_object
|
result = self._built_object
|
||||||
# if it is not a dict or a string and hasattr model_dump then
|
# if it is not a dict or a string and hasattr model_dump then
|
||||||
# return the model_dump
|
# return the model_dump
|
||||||
|
|
@ -147,11 +134,7 @@ class Vertex:
|
||||||
|
|
||||||
if isinstance(self._built_result, UnbuiltResult):
|
if isinstance(self._built_result, UnbuiltResult):
|
||||||
return {}
|
return {}
|
||||||
return (
|
return self._built_result if isinstance(self._built_result, dict) else {"result": self._built_result}
|
||||||
self._built_result
|
|
||||||
if isinstance(self._built_result, dict)
|
|
||||||
else {"result": self._built_result}
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_artifacts(self) -> None:
|
def set_artifacts(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
@ -221,31 +204,19 @@ class Vertex:
|
||||||
self.selected_output_type = self.data["node"].get("selected_output_type")
|
self.selected_output_type = self.data["node"].get("selected_output_type")
|
||||||
self.is_input = self.data["node"].get("is_input") or self.is_input
|
self.is_input = self.data["node"].get("is_input") or self.is_input
|
||||||
self.is_output = self.data["node"].get("is_output") or self.is_output
|
self.is_output = self.data["node"].get("is_output") or self.is_output
|
||||||
template_dicts = {
|
template_dicts = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
|
||||||
key: value
|
|
||||||
for key, value in self.data["node"]["template"].items()
|
|
||||||
if isinstance(value, dict)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.has_session_id = "session_id" in template_dicts
|
self.has_session_id = "session_id" in template_dicts
|
||||||
|
|
||||||
self.required_inputs = [
|
self.required_inputs = [
|
||||||
template_dicts[key]["type"]
|
template_dicts[key]["type"] for key, value in template_dicts.items() if value["required"]
|
||||||
for key, value in template_dicts.items()
|
|
||||||
if value["required"]
|
|
||||||
]
|
]
|
||||||
self.optional_inputs = [
|
self.optional_inputs = [
|
||||||
template_dicts[key]["type"]
|
template_dicts[key]["type"] for key, value in template_dicts.items() if not value["required"]
|
||||||
for key, value in template_dicts.items()
|
|
||||||
if not value["required"]
|
|
||||||
]
|
]
|
||||||
# Add the template_dicts[key]["input_types"] to the optional_inputs
|
# Add the template_dicts[key]["input_types"] to the optional_inputs
|
||||||
self.optional_inputs.extend(
|
self.optional_inputs.extend(
|
||||||
[
|
[input_type for value in template_dicts.values() for input_type in value.get("input_types", [])]
|
||||||
input_type
|
|
||||||
for value in template_dicts.values()
|
|
||||||
for input_type in value.get("input_types", [])
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
template_dict = self.data["node"]["template"]
|
template_dict = self.data["node"]["template"]
|
||||||
|
|
@ -292,11 +263,7 @@ class Vertex:
|
||||||
self.updated_raw_params = False
|
self.updated_raw_params = False
|
||||||
return
|
return
|
||||||
|
|
||||||
template_dict = {
|
template_dict = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
|
||||||
key: value
|
|
||||||
for key, value in self.data["node"]["template"].items()
|
|
||||||
if isinstance(value, dict)
|
|
||||||
}
|
|
||||||
params = {}
|
params = {}
|
||||||
|
|
||||||
for edge in self.edges:
|
for edge in self.edges:
|
||||||
|
|
@ -317,10 +284,7 @@ class Vertex:
|
||||||
# we don't know the key of the dict but we need to set the value
|
# we don't know the key of the dict but we need to set the value
|
||||||
# to the vertex that is the source of the edge
|
# to the vertex that is the source of the edge
|
||||||
param_dict = template_dict[param_key]["value"]
|
param_dict = template_dict[param_key]["value"]
|
||||||
params[param_key] = {
|
params[param_key] = {key: self.graph.get_vertex(edge.source_id) for key in param_dict.keys()}
|
||||||
key: self.graph.get_vertex(edge.source_id)
|
|
||||||
for key in param_dict.keys()
|
|
||||||
}
|
|
||||||
else:
|
else:
|
||||||
params[param_key] = self.graph.get_vertex(edge.source_id)
|
params[param_key] = self.graph.get_vertex(edge.source_id)
|
||||||
|
|
||||||
|
|
@ -356,11 +320,7 @@ class Vertex:
|
||||||
# list of dicts, so we need to convert it to a dict
|
# list of dicts, so we need to convert it to a dict
|
||||||
# before passing it to the build method
|
# before passing it to the build method
|
||||||
if isinstance(val, list):
|
if isinstance(val, list):
|
||||||
params[key] = {
|
params[key] = {k: v for item in value.get("value", []) for k, v in item.items()}
|
||||||
k: v
|
|
||||||
for item in value.get("value", [])
|
|
||||||
for k, v in item.items()
|
|
||||||
}
|
|
||||||
elif isinstance(val, dict):
|
elif isinstance(val, dict):
|
||||||
params[key] = val
|
params[key] = val
|
||||||
elif value.get("type") == "int" and val is not None:
|
elif value.get("type") == "int" and val is not None:
|
||||||
|
|
@ -485,9 +445,7 @@ class Vertex:
|
||||||
if isinstance(self._built_object, str):
|
if isinstance(self._built_object, str):
|
||||||
self._built_result = self._built_object
|
self._built_result = self._built_object
|
||||||
|
|
||||||
result = await generate_result(
|
result = await generate_result(self._built_object, inputs, self.has_external_output, session_id)
|
||||||
self._built_object, inputs, self.has_external_output, session_id
|
|
||||||
)
|
|
||||||
self._built_result = result
|
self._built_result = result
|
||||||
|
|
||||||
async def _build_each_node_in_params_dict(self, user_id=None):
|
async def _build_each_node_in_params_dict(self, user_id=None):
|
||||||
|
|
@ -507,9 +465,7 @@ class Vertex:
|
||||||
elif key not in self.params or self.updated_raw_params:
|
elif key not in self.params or self.updated_raw_params:
|
||||||
self.params[key] = value
|
self.params[key] = value
|
||||||
|
|
||||||
async def _build_dict_of_nodes_and_update_params(
|
async def _build_dict_of_nodes_and_update_params(self, key, nodes: Dict[str, "Vertex"], user_id=None):
|
||||||
self, key, nodes: Dict[str, "Vertex"], user_id=None
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Iterates over a dictionary of nodes, builds each and updates the params dictionary.
|
Iterates over a dictionary of nodes, builds each and updates the params dictionary.
|
||||||
"""
|
"""
|
||||||
|
|
@ -529,9 +485,7 @@ class Vertex:
|
||||||
"""
|
"""
|
||||||
return all(self._is_node(node) for node in value)
|
return all(self._is_node(node) for node in value)
|
||||||
|
|
||||||
async def get_result(
|
async def get_result(self, requester: Optional["Vertex"] = None, user_id=None, timeout=None) -> Any:
|
||||||
self, requester: Optional["Vertex"] = None, user_id=None, timeout=None
|
|
||||||
) -> Any:
|
|
||||||
# PLEASE REVIEW THIS IF STATEMENT
|
# PLEASE REVIEW THIS IF STATEMENT
|
||||||
# Check if the Vertex was built already
|
# Check if the Vertex was built already
|
||||||
if self._built:
|
if self._built:
|
||||||
|
|
@ -565,9 +519,7 @@ class Vertex:
|
||||||
self._extend_params_list_with_result(key, result)
|
self._extend_params_list_with_result(key, result)
|
||||||
self.params[key] = result
|
self.params[key] = result
|
||||||
|
|
||||||
async def _build_list_of_nodes_and_update_params(
|
async def _build_list_of_nodes_and_update_params(self, key, nodes: List["Vertex"], user_id=None):
|
||||||
self, key, nodes: List["Vertex"], user_id=None
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Iterates over a list of nodes, builds each and updates the params dictionary.
|
Iterates over a list of nodes, builds each and updates the params dictionary.
|
||||||
"""
|
"""
|
||||||
|
|
@ -634,9 +586,7 @@ class Vertex:
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(exc)
|
logger.exception(exc)
|
||||||
|
|
||||||
raise ValueError(
|
raise ValueError(f"Error building node {self.display_name}: {str(exc)}") from exc
|
||||||
f"Error building node {self.display_name}: {str(exc)}"
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
def _update_built_object_and_artifacts(self, result):
|
def _update_built_object_and_artifacts(self, result):
|
||||||
"""
|
"""
|
||||||
|
|
@ -664,9 +614,7 @@ class Vertex:
|
||||||
logger.warning(message)
|
logger.warning(message)
|
||||||
elif isinstance(self._built_object, (Iterator, AsyncIterator)):
|
elif isinstance(self._built_object, (Iterator, AsyncIterator)):
|
||||||
if self.display_name in ["Text Output"]:
|
if self.display_name in ["Text Output"]:
|
||||||
raise ValueError(
|
raise ValueError(f"You are trying to stream to a {self.display_name}. Try using a Chat Output instead.")
|
||||||
f"You are trying to stream to a {self.display_name}. Try using a Chat Output instead."
|
|
||||||
)
|
|
||||||
|
|
||||||
def _reset(self, params_update: Optional[Dict[str, Any]] = None):
|
def _reset(self, params_update: Optional[Dict[str, Any]] = None):
|
||||||
self._built = False
|
self._built = False
|
||||||
|
|
@ -728,24 +676,16 @@ class Vertex:
|
||||||
return self._built_object
|
return self._built_object
|
||||||
|
|
||||||
# Get the requester edge
|
# Get the requester edge
|
||||||
requester_edge = next(
|
requester_edge = next((edge for edge in self.edges if edge.target_id == requester.id), None)
|
||||||
(edge for edge in self.edges if edge.target_id == requester.id), None
|
|
||||||
)
|
|
||||||
# Return the result of the requester edge
|
# Return the result of the requester edge
|
||||||
return (
|
return None if requester_edge is None else await requester_edge.get_result(source=self, target=requester)
|
||||||
None
|
|
||||||
if requester_edge is None
|
|
||||||
else await requester_edge.get_result(source=self, target=requester)
|
|
||||||
)
|
|
||||||
|
|
||||||
def add_edge(self, edge: "ContractEdge") -> None:
|
def add_edge(self, edge: "ContractEdge") -> None:
|
||||||
if edge not in self.edges:
|
if edge not in self.edges:
|
||||||
self.edges.append(edge)
|
self.edges.append(edge)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return f"Vertex(display_name={self.display_name}, id={self.id}, data={self.data})"
|
||||||
f"Vertex(display_name={self.display_name}, id={self.id}, data={self.data})"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __eq__(self, __o: object) -> bool:
|
def __eq__(self, __o: object) -> bool:
|
||||||
try:
|
try:
|
||||||
|
|
@ -766,8 +706,4 @@ class Vertex:
|
||||||
|
|
||||||
def _built_object_repr(self):
|
def _built_object_repr(self):
|
||||||
# Add a message with an emoji, stars for sucess,
|
# Add a message with an emoji, stars for sucess,
|
||||||
return (
|
return "Built sucessfully ✨" if self._built_object is not None else "Failed to build 😵💫"
|
||||||
"Built sucessfully ✨"
|
|
||||||
if self._built_object is not None
|
|
||||||
else "Failed to build 😵💫"
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import ast
|
import ast
|
||||||
import json
|
import json
|
||||||
from typing import (AsyncIterator, Callable, Dict, Iterator, List, Optional,
|
from typing import AsyncIterator, Callable, Dict, Iterator, List, Optional, Union
|
||||||
Union)
|
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from langchain_core.messages import AIMessage
|
from langchain_core.messages import AIMessage
|
||||||
|
|
@ -124,11 +123,9 @@ class DocumentLoaderVertex(Vertex):
|
||||||
# show how many documents are in the list?
|
# show how many documents are in the list?
|
||||||
|
|
||||||
if not isinstance(self._built_object, UnbuiltObject):
|
if not isinstance(self._built_object, UnbuiltObject):
|
||||||
avg_length = sum(
|
avg_length = sum(len(record.text) for record in self._built_object if hasattr(record, "text")) / len(
|
||||||
len(record.text)
|
self._built_object
|
||||||
for record in self._built_object
|
)
|
||||||
if hasattr(record, "text")
|
|
||||||
) / len(self._built_object)
|
|
||||||
return f"""{self.display_name}({len(self._built_object)} records)
|
return f"""{self.display_name}({len(self._built_object)} records)
|
||||||
\nAvg. Record Length (characters): {int(avg_length)}
|
\nAvg. Record Length (characters): {int(avg_length)}
|
||||||
Records: {self._built_object[:3]}..."""
|
Records: {self._built_object[:3]}..."""
|
||||||
|
|
@ -201,9 +198,7 @@ class TextSplitterVertex(Vertex):
|
||||||
# show how many documents are in the list?
|
# show how many documents are in the list?
|
||||||
|
|
||||||
if not isinstance(self._built_object, UnbuiltObject):
|
if not isinstance(self._built_object, UnbuiltObject):
|
||||||
avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(
|
avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(self._built_object)
|
||||||
self._built_object
|
|
||||||
)
|
|
||||||
return f"""{self.vertex_type}({len(self._built_object)} documents)
|
return f"""{self.vertex_type}({len(self._built_object)} documents)
|
||||||
\nAvg. Document Length (characters): {int(avg_length)}
|
\nAvg. Document Length (characters): {int(avg_length)}
|
||||||
\nDocuments: {self._built_object[:3]}..."""
|
\nDocuments: {self._built_object[:3]}..."""
|
||||||
|
|
@ -250,27 +245,18 @@ class PromptVertex(Vertex):
|
||||||
user_id = kwargs.get("user_id", None)
|
user_id = kwargs.get("user_id", None)
|
||||||
tools = kwargs.get("tools", [])
|
tools = kwargs.get("tools", [])
|
||||||
if not self._built or force:
|
if not self._built or force:
|
||||||
if (
|
if "input_variables" not in self.params or self.params["input_variables"] is None:
|
||||||
"input_variables" not in self.params
|
|
||||||
or self.params["input_variables"] is None
|
|
||||||
):
|
|
||||||
self.params["input_variables"] = []
|
self.params["input_variables"] = []
|
||||||
# Check if it is a ZeroShotPrompt and needs a tool
|
# Check if it is a ZeroShotPrompt and needs a tool
|
||||||
if "ShotPrompt" in self.vertex_type:
|
if "ShotPrompt" in self.vertex_type:
|
||||||
tools = (
|
tools = [tool_node.build(user_id=user_id) for tool_node in tools] if tools is not None else []
|
||||||
[tool_node.build(user_id=user_id) for tool_node in tools]
|
|
||||||
if tools is not None
|
|
||||||
else []
|
|
||||||
)
|
|
||||||
# flatten the list of tools if it is a list of lists
|
# flatten the list of tools if it is a list of lists
|
||||||
# first check if it is a list
|
# first check if it is a list
|
||||||
if tools and isinstance(tools, list) and isinstance(tools[0], list):
|
if tools and isinstance(tools, list) and isinstance(tools[0], list):
|
||||||
tools = flatten_list(tools)
|
tools = flatten_list(tools)
|
||||||
self.params["tools"] = tools
|
self.params["tools"] = tools
|
||||||
prompt_params = [
|
prompt_params = [
|
||||||
key
|
key for key, value in self.params.items() if isinstance(value, str) and key != "format_instructions"
|
||||||
for key, value in self.params.items()
|
|
||||||
if isinstance(value, str) and key != "format_instructions"
|
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
prompt_params = ["template"]
|
prompt_params = ["template"]
|
||||||
|
|
@ -280,20 +266,14 @@ class PromptVertex(Vertex):
|
||||||
prompt_text = self.params[param]
|
prompt_text = self.params[param]
|
||||||
variables = extract_input_variables_from_prompt(prompt_text)
|
variables = extract_input_variables_from_prompt(prompt_text)
|
||||||
self.params["input_variables"].extend(variables)
|
self.params["input_variables"].extend(variables)
|
||||||
self.params["input_variables"] = list(
|
self.params["input_variables"] = list(set(self.params["input_variables"]))
|
||||||
set(self.params["input_variables"])
|
|
||||||
)
|
|
||||||
elif isinstance(self.params, dict):
|
elif isinstance(self.params, dict):
|
||||||
self.params.pop("input_variables", None)
|
self.params.pop("input_variables", None)
|
||||||
|
|
||||||
await self._build(user_id=user_id)
|
await self._build(user_id=user_id)
|
||||||
|
|
||||||
def _built_object_repr(self):
|
def _built_object_repr(self):
|
||||||
if (
|
if not self.artifacts or self._built_object is None or not hasattr(self._built_object, "format"):
|
||||||
not self.artifacts
|
|
||||||
or self._built_object is None
|
|
||||||
or not hasattr(self._built_object, "format")
|
|
||||||
):
|
|
||||||
return super()._built_object_repr()
|
return super()._built_object_repr()
|
||||||
elif isinstance(self._built_object, UnbuiltObject):
|
elif isinstance(self._built_object, UnbuiltObject):
|
||||||
return super()._built_object_repr()
|
return super()._built_object_repr()
|
||||||
|
|
@ -305,9 +285,7 @@ class PromptVertex(Vertex):
|
||||||
# so the prompt format doesn't break
|
# so the prompt format doesn't break
|
||||||
artifacts.pop("handle_keys", None)
|
artifacts.pop("handle_keys", None)
|
||||||
try:
|
try:
|
||||||
if not hasattr(self._built_object, "template") and hasattr(
|
if not hasattr(self._built_object, "template") and hasattr(self._built_object, "prompt"):
|
||||||
self._built_object, "prompt"
|
|
||||||
):
|
|
||||||
template = self._built_object.prompt.template
|
template = self._built_object.prompt.template
|
||||||
else:
|
else:
|
||||||
template = self._built_object.template
|
template = self._built_object.template
|
||||||
|
|
@ -315,11 +293,7 @@ class PromptVertex(Vertex):
|
||||||
if value:
|
if value:
|
||||||
replace_key = "{" + key + "}"
|
replace_key = "{" + key + "}"
|
||||||
template = template.replace(replace_key, value)
|
template = template.replace(replace_key, value)
|
||||||
return (
|
return template if isinstance(template, str) else f"{self.vertex_type}({template})"
|
||||||
template
|
|
||||||
if isinstance(template, str)
|
|
||||||
else f"{self.vertex_type}({template})"
|
|
||||||
)
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return str(self._built_object)
|
return str(self._built_object)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,5 @@ def records_to_text(template: str, records: list[Record]) -> list[str]:
|
||||||
records = [records]
|
records = [records]
|
||||||
# Check if there are any format strings in the template
|
# Check if there are any format strings in the template
|
||||||
|
|
||||||
formated_records = [
|
formated_records = [template.format(text=record.text, data=record.data, **record.data) for record in records]
|
||||||
template.format(text=record.text, data=record.data, **record.data)
|
|
||||||
for record in records
|
|
||||||
]
|
|
||||||
return "\n".join(formated_records)
|
return "\n".join(formated_records)
|
||||||
|
|
|
||||||
|
|
@ -89,11 +89,6 @@ def create_new_project(
|
||||||
)
|
)
|
||||||
db_flow = Flow.model_validate(new_project, from_attributes=True)
|
db_flow = Flow.model_validate(new_project, from_attributes=True)
|
||||||
session.add(db_flow)
|
session.add(db_flow)
|
||||||
flows = session.exec(
|
|
||||||
select(Flow).where(
|
|
||||||
Flow.name == project_name,
|
|
||||||
)
|
|
||||||
).all()
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_flows_similar_to_project(session, project_name):
|
def get_all_flows_similar_to_project(session, project_name):
|
||||||
|
|
@ -117,7 +112,6 @@ def delete_start_projects(session):
|
||||||
|
|
||||||
|
|
||||||
def create_or_update_starter_projects():
|
def create_or_update_starter_projects():
|
||||||
|
|
||||||
with session_scope() as session:
|
with session_scope() as session:
|
||||||
starter_projects = load_starter_projects()
|
starter_projects = load_starter_projects()
|
||||||
delete_start_projects(session)
|
delete_start_projects(session)
|
||||||
|
|
@ -132,9 +126,7 @@ def create_or_update_starter_projects():
|
||||||
project_icon_bg_color,
|
project_icon_bg_color,
|
||||||
) = get_project_data(project)
|
) = get_project_data(project)
|
||||||
if project_name and project_data:
|
if project_name and project_data:
|
||||||
for existing_project in get_all_flows_similar_to_project(
|
for existing_project in get_all_flows_similar_to_project(session, project_name):
|
||||||
session, project_name
|
|
||||||
):
|
|
||||||
session.delete(existing_project)
|
session.delete(existing_project)
|
||||||
|
|
||||||
create_new_project(
|
create_new_project(
|
||||||
|
|
|
||||||
|
|
@ -95,9 +95,7 @@ class CodeParser:
|
||||||
elif isinstance(node, ast.ImportFrom):
|
elif isinstance(node, ast.ImportFrom):
|
||||||
for alias in node.names:
|
for alias in node.names:
|
||||||
if alias.asname:
|
if alias.asname:
|
||||||
self.data["imports"].append(
|
self.data["imports"].append((node.module, f"{alias.name} as {alias.asname}"))
|
||||||
(node.module, f"{alias.name} as {alias.asname}")
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.data["imports"].append((node.module, alias.name))
|
self.data["imports"].append((node.module, alias.name))
|
||||||
|
|
||||||
|
|
@ -146,9 +144,7 @@ class CodeParser:
|
||||||
return_type = None
|
return_type = None
|
||||||
if node.returns:
|
if node.returns:
|
||||||
return_type_str = ast.unparse(node.returns)
|
return_type_str = ast.unparse(node.returns)
|
||||||
eval_env = self.construct_eval_env(
|
eval_env = self.construct_eval_env(return_type_str, tuple(self.data["imports"]))
|
||||||
return_type_str, tuple(self.data["imports"])
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return_type = eval(return_type_str, eval_env)
|
return_type = eval(return_type_str, eval_env)
|
||||||
|
|
@ -190,22 +186,14 @@ class CodeParser:
|
||||||
num_defaults = len(node.args.defaults)
|
num_defaults = len(node.args.defaults)
|
||||||
num_missing_defaults = num_args - num_defaults
|
num_missing_defaults = num_args - num_defaults
|
||||||
missing_defaults = [None] * num_missing_defaults
|
missing_defaults = [None] * num_missing_defaults
|
||||||
default_values = [
|
default_values = [ast.unparse(default).strip("'") if default else None for default in node.args.defaults]
|
||||||
ast.unparse(default).strip("'") if default else None
|
|
||||||
for default in node.args.defaults
|
|
||||||
]
|
|
||||||
# Now check all default values to see if there
|
# Now check all default values to see if there
|
||||||
# are any "None" values in the middle
|
# are any "None" values in the middle
|
||||||
default_values = [
|
default_values = [None if value == "None" else value for value in default_values]
|
||||||
None if value == "None" else value for value in default_values
|
|
||||||
]
|
|
||||||
|
|
||||||
defaults = missing_defaults + default_values
|
defaults = missing_defaults + default_values
|
||||||
|
|
||||||
args = [
|
args = [self.parse_arg(arg, default) for arg, default in zip(node.args.args, defaults)]
|
||||||
self.parse_arg(arg, default)
|
|
||||||
for arg, default in zip(node.args.args, defaults)
|
|
||||||
]
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
def parse_varargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
|
def parse_varargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
|
||||||
|
|
@ -223,17 +211,11 @@ class CodeParser:
|
||||||
"""
|
"""
|
||||||
Parses the keyword-only arguments of a function or method node.
|
Parses the keyword-only arguments of a function or method node.
|
||||||
"""
|
"""
|
||||||
kw_defaults = [None] * (
|
kw_defaults = [None] * (len(node.args.kwonlyargs) - len(node.args.kw_defaults)) + [
|
||||||
len(node.args.kwonlyargs) - len(node.args.kw_defaults)
|
ast.unparse(default) if default else None for default in node.args.kw_defaults
|
||||||
) + [
|
|
||||||
ast.unparse(default) if default else None
|
|
||||||
for default in node.args.kw_defaults
|
|
||||||
]
|
]
|
||||||
|
|
||||||
args = [
|
args = [self.parse_arg(arg, default) for arg, default in zip(node.args.kwonlyargs, kw_defaults)]
|
||||||
self.parse_arg(arg, default)
|
|
||||||
for arg, default in zip(node.args.kwonlyargs, kw_defaults)
|
|
||||||
]
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
def parse_kwargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
|
def parse_kwargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]:
|
||||||
|
|
@ -337,9 +319,7 @@ class CodeParser:
|
||||||
Extracts global variables from the code.
|
Extracts global variables from the code.
|
||||||
"""
|
"""
|
||||||
global_var = {
|
global_var = {
|
||||||
"targets": [
|
"targets": [t.id if hasattr(t, "id") else ast.dump(t) for t in node.targets],
|
||||||
t.id if hasattr(t, "id") else ast.dump(t) for t in node.targets
|
|
||||||
],
|
|
||||||
"value": ast.unparse(node.value),
|
"value": ast.unparse(node.value),
|
||||||
}
|
}
|
||||||
self.data["global_vars"].append(global_var)
|
self.data["global_vars"].append(global_var)
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,7 @@ class ComponentFunctionEntrypointNameNullError(HTTPException):
|
||||||
|
|
||||||
class Component:
|
class Component:
|
||||||
ERROR_CODE_NULL: ClassVar[str] = "Python code must be provided."
|
ERROR_CODE_NULL: ClassVar[str] = "Python code must be provided."
|
||||||
ERROR_FUNCTION_ENTRYPOINT_NAME_NULL: ClassVar[str] = (
|
ERROR_FUNCTION_ENTRYPOINT_NAME_NULL: ClassVar[str] = "The name of the entrypoint function must be provided."
|
||||||
"The name of the entrypoint function must be provided."
|
|
||||||
)
|
|
||||||
|
|
||||||
code: Optional[str] = None
|
code: Optional[str] = None
|
||||||
_function_entrypoint_name: str = "build"
|
_function_entrypoint_name: str = "build"
|
||||||
|
|
|
||||||
|
|
@ -77,17 +77,13 @@ class CustomComponent(Component):
|
||||||
|
|
||||||
def update_state(self, name: str, value: Any):
|
def update_state(self, name: str, value: Any):
|
||||||
try:
|
try:
|
||||||
self.vertex.graph.update_state(
|
self.vertex.graph.update_state(name=name, record=value, caller=self.vertex.id)
|
||||||
name=name, record=value, caller=self.vertex.id
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(f"Error updating state: {e}")
|
raise ValueError(f"Error updating state: {e}")
|
||||||
|
|
||||||
def append_state(self, name: str, value: Any):
|
def append_state(self, name: str, value: Any):
|
||||||
try:
|
try:
|
||||||
self.vertex.graph.append_state(
|
self.vertex.graph.append_state(name=name, record=value, caller=self.vertex.id)
|
||||||
name=name, record=value, caller=self.vertex.id
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(f"Error appending state: {e}")
|
raise ValueError(f"Error appending state: {e}")
|
||||||
|
|
||||||
|
|
@ -138,9 +134,7 @@ class CustomComponent(Component):
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
return self.field_config
|
return self.field_config
|
||||||
|
|
||||||
def update_build_config(
|
def update_build_config(self, build_config: dict, field_name: str, field_value: Any):
|
||||||
self, build_config: dict, field_name: str, field_value: Any
|
|
||||||
):
|
|
||||||
build_config[field_name] = field_value
|
build_config[field_name] = field_value
|
||||||
return build_config
|
return build_config
|
||||||
|
|
||||||
|
|
@ -148,9 +142,7 @@ class CustomComponent(Component):
|
||||||
def tree(self):
|
def tree(self):
|
||||||
return self.get_code_tree(self.code or "")
|
return self.get_code_tree(self.code or "")
|
||||||
|
|
||||||
def to_records(
|
def to_records(self, data: Any, keys: Optional[List[str]] = None, silent_errors: bool = False) -> List[Record]:
|
||||||
self, data: Any, keys: Optional[List[str]] = None, silent_errors: bool = False
|
|
||||||
) -> List[Record]:
|
|
||||||
"""
|
"""
|
||||||
Converts input data into a list of Record objects.
|
Converts input data into a list of Record objects.
|
||||||
|
|
||||||
|
|
@ -199,9 +191,7 @@ class CustomComponent(Component):
|
||||||
|
|
||||||
return records
|
return records
|
||||||
|
|
||||||
def create_references_from_records(
|
def create_references_from_records(self, records: List[Record], include_data: bool = False) -> str:
|
||||||
self, records: List[Record], include_data: bool = False
|
|
||||||
) -> str:
|
|
||||||
"""
|
"""
|
||||||
Create references from a list of records.
|
Create references from a list of records.
|
||||||
|
|
||||||
|
|
@ -240,20 +230,14 @@ class CustomComponent(Component):
|
||||||
if not self.code:
|
if not self.code:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
component_classes = [
|
component_classes = [cls for cls in self.tree["classes"] if self.code_class_base_inheritance in cls["bases"]]
|
||||||
cls
|
|
||||||
for cls in self.tree["classes"]
|
|
||||||
if self.code_class_base_inheritance in cls["bases"]
|
|
||||||
]
|
|
||||||
if not component_classes:
|
if not component_classes:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
# Assume the first Component class is the one we're interested in
|
# Assume the first Component class is the one we're interested in
|
||||||
component_class = component_classes[0]
|
component_class = component_classes[0]
|
||||||
build_methods = [
|
build_methods = [
|
||||||
method
|
method for method in component_class["methods"] if method["name"] == self.function_entrypoint_name
|
||||||
for method in component_class["methods"]
|
|
||||||
if method["name"] == self.function_entrypoint_name
|
|
||||||
]
|
]
|
||||||
|
|
||||||
return build_methods[0] if build_methods else {}
|
return build_methods[0] if build_methods else {}
|
||||||
|
|
@ -310,9 +294,7 @@ class CustomComponent(Component):
|
||||||
# Retrieve and decrypt the credential by name for the current user
|
# Retrieve and decrypt the credential by name for the current user
|
||||||
db_service = get_db_service()
|
db_service = get_db_service()
|
||||||
with session_getter(db_service) as session:
|
with session_getter(db_service) as session:
|
||||||
return credential_service.get_credential(
|
return credential_service.get_credential(user_id=self._user_id or "", name=name, session=session)
|
||||||
user_id=self._user_id or "", name=name, session=session
|
|
||||||
)
|
|
||||||
|
|
||||||
return get_credential
|
return get_credential
|
||||||
|
|
||||||
|
|
@ -322,9 +304,7 @@ class CustomComponent(Component):
|
||||||
credential_service = get_credential_service()
|
credential_service = get_credential_service()
|
||||||
db_service = get_db_service()
|
db_service = get_db_service()
|
||||||
with session_getter(db_service) as session:
|
with session_getter(db_service) as session:
|
||||||
return credential_service.list_credentials(
|
return credential_service.list_credentials(user_id=self._user_id, session=session)
|
||||||
user_id=self._user_id, session=session
|
|
||||||
)
|
|
||||||
|
|
||||||
def index(self, value: int = 0):
|
def index(self, value: int = 0):
|
||||||
"""Returns a function that returns the value at the given index in the iterable."""
|
"""Returns a function that returns the value at the given index in the iterable."""
|
||||||
|
|
@ -363,11 +343,7 @@ class CustomComponent(Component):
|
||||||
if not self._flows_records:
|
if not self._flows_records:
|
||||||
self.list_flows()
|
self.list_flows()
|
||||||
if not flow_id and self._flows_records:
|
if not flow_id and self._flows_records:
|
||||||
flow_ids = [
|
flow_ids = [flow.data["id"] for flow in self._flows_records if flow.data["name"] == flow_name]
|
||||||
flow.data["id"]
|
|
||||||
for flow in self._flows_records
|
|
||||||
if flow.data["name"] == flow_name
|
|
||||||
]
|
|
||||||
if not flow_ids:
|
if not flow_ids:
|
||||||
raise ValueError(f"Flow {flow_name} not found")
|
raise ValueError(f"Flow {flow_name} not found")
|
||||||
elif len(flow_ids) > 1:
|
elif len(flow_ids) > 1:
|
||||||
|
|
@ -389,9 +365,7 @@ class CustomComponent(Component):
|
||||||
db_service = get_db_service()
|
db_service = get_db_service()
|
||||||
with get_session(db_service) as session:
|
with get_session(db_service) as session:
|
||||||
flows = session.exec(
|
flows = session.exec(
|
||||||
select(Flow)
|
select(Flow).where(Flow.user_id == self._user_id).where(Flow.is_component == False) # noqa
|
||||||
.where(Flow.user_id == self._user_id)
|
|
||||||
.where(Flow.is_component == False)
|
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
flows_records = [flow.to_record() for flow in flows]
|
flows_records = [flow.to_record() for flow in flows]
|
||||||
|
|
|
||||||
|
|
@ -80,13 +80,9 @@ class DirectoryReader:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error while loading component: {e}")
|
logger.error(f"Error while loading component: {e}")
|
||||||
continue
|
continue
|
||||||
items.append(
|
items.append({"name": menu["name"], "path": menu["path"], "components": components})
|
||||||
{"name": menu["name"], "path": menu["path"], "components": components}
|
|
||||||
)
|
|
||||||
filtered = [menu for menu in items if menu["components"]]
|
filtered = [menu for menu in items if menu["components"]]
|
||||||
logger.debug(
|
logger.debug(f'Filtered components {"with errors" if with_errors else ""}: {len(filtered)}')
|
||||||
f'Filtered components {"with errors" if with_errors else ""}: {len(filtered)}'
|
|
||||||
)
|
|
||||||
return {"menu": filtered}
|
return {"menu": filtered}
|
||||||
|
|
||||||
def validate_code(self, file_content):
|
def validate_code(self, file_content):
|
||||||
|
|
@ -119,9 +115,7 @@ class DirectoryReader:
|
||||||
Walk through the directory path and return a list of all .py files.
|
Walk through the directory path and return a list of all .py files.
|
||||||
"""
|
"""
|
||||||
if not (safe_path := self.get_safe_path()):
|
if not (safe_path := self.get_safe_path()):
|
||||||
raise CustomComponentPathValueError(
|
raise CustomComponentPathValueError(f"The path needs to start with '{self.base_path}'.")
|
||||||
f"The path needs to start with '{self.base_path}'."
|
|
||||||
)
|
|
||||||
|
|
||||||
file_list = []
|
file_list = []
|
||||||
safe_path_obj = Path(safe_path)
|
safe_path_obj = Path(safe_path)
|
||||||
|
|
@ -131,11 +125,7 @@ class DirectoryReader:
|
||||||
# any folders below [folder] will be ignored
|
# any folders below [folder] will be ignored
|
||||||
# basically the parent folder of the file should be a
|
# basically the parent folder of the file should be a
|
||||||
# folder in the safe_path
|
# folder in the safe_path
|
||||||
if (
|
if file_path.is_file() and file_path.parent.parent == safe_path_obj and not file_path.name.startswith("__"):
|
||||||
file_path.is_file()
|
|
||||||
and file_path.parent.parent == safe_path_obj
|
|
||||||
and not file_path.name.startswith("__")
|
|
||||||
):
|
|
||||||
file_list.append(str(file_path))
|
file_list.append(str(file_path))
|
||||||
return file_list
|
return file_list
|
||||||
|
|
||||||
|
|
@ -173,9 +163,7 @@ class DirectoryReader:
|
||||||
for node in ast.walk(module):
|
for node in ast.walk(module):
|
||||||
if isinstance(node, ast.FunctionDef):
|
if isinstance(node, ast.FunctionDef):
|
||||||
for arg in node.args.args:
|
for arg in node.args.args:
|
||||||
if self._is_type_hint_in_arg_annotation(
|
if self._is_type_hint_in_arg_annotation(arg.annotation, type_hint_name):
|
||||||
arg.annotation, type_hint_name
|
|
||||||
):
|
|
||||||
return True
|
return True
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
# Returns False if the code is not valid Python
|
# Returns False if the code is not valid Python
|
||||||
|
|
@ -193,16 +181,14 @@ class DirectoryReader:
|
||||||
and annotation.value.id == type_hint_name
|
and annotation.value.id == type_hint_name
|
||||||
)
|
)
|
||||||
|
|
||||||
def is_type_hint_used_but_not_imported(
|
def is_type_hint_used_but_not_imported(self, type_hint_name: str, code: str) -> bool:
|
||||||
self, type_hint_name: str, code: str
|
|
||||||
) -> bool:
|
|
||||||
"""
|
"""
|
||||||
Check if a type hint is used but not imported in the given code.
|
Check if a type hint is used but not imported in the given code.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return self._is_type_hint_used_in_args(
|
return self._is_type_hint_used_in_args(type_hint_name, code) and not self._is_type_hint_imported(
|
||||||
type_hint_name, code
|
type_hint_name, code
|
||||||
) and not self._is_type_hint_imported(type_hint_name, code)
|
)
|
||||||
except SyntaxError:
|
except SyntaxError:
|
||||||
# Returns True if there's something wrong with the code
|
# Returns True if there's something wrong with the code
|
||||||
# TODO : Find a better way to handle this
|
# TODO : Find a better way to handle this
|
||||||
|
|
@ -223,9 +209,9 @@ class DirectoryReader:
|
||||||
return False, "Syntax error"
|
return False, "Syntax error"
|
||||||
elif not self.validate_build(file_content):
|
elif not self.validate_build(file_content):
|
||||||
return False, "Missing build function"
|
return False, "Missing build function"
|
||||||
elif self._is_type_hint_used_in_args(
|
elif self._is_type_hint_used_in_args("Optional", file_content) and not self._is_type_hint_imported(
|
||||||
"Optional", file_content
|
"Optional", file_content
|
||||||
) and not self._is_type_hint_imported("Optional", file_content):
|
):
|
||||||
return (
|
return (
|
||||||
False,
|
False,
|
||||||
"Type hint 'Optional' is used but not imported in the code.",
|
"Type hint 'Optional' is used but not imported in the code.",
|
||||||
|
|
@ -241,18 +227,14 @@ class DirectoryReader:
|
||||||
from the .py files in the directory.
|
from the .py files in the directory.
|
||||||
"""
|
"""
|
||||||
response = {"menu": []}
|
response = {"menu": []}
|
||||||
logger.debug(
|
logger.debug("-------------------- Building component menu list --------------------")
|
||||||
"-------------------- Building component menu list --------------------"
|
|
||||||
)
|
|
||||||
|
|
||||||
for file_path in file_paths:
|
for file_path in file_paths:
|
||||||
menu_name = os.path.basename(os.path.dirname(file_path))
|
menu_name = os.path.basename(os.path.dirname(file_path))
|
||||||
filename = os.path.basename(file_path)
|
filename = os.path.basename(file_path)
|
||||||
validation_result, result_content = self.process_file(file_path)
|
validation_result, result_content = self.process_file(file_path)
|
||||||
if not validation_result:
|
if not validation_result:
|
||||||
logger.error(
|
logger.error(f"Error while processing file {file_path}: {result_content}")
|
||||||
f"Error while processing file {file_path}: {result_content}"
|
|
||||||
)
|
|
||||||
|
|
||||||
menu_result = self.find_menu(response, menu_name) or {
|
menu_result = self.find_menu(response, menu_name) or {
|
||||||
"name": menu_name,
|
"name": menu_name,
|
||||||
|
|
@ -265,9 +247,7 @@ class DirectoryReader:
|
||||||
|
|
||||||
# first check if it's already CamelCase
|
# first check if it's already CamelCase
|
||||||
if "_" in component_name:
|
if "_" in component_name:
|
||||||
component_name_camelcase = " ".join(
|
component_name_camelcase = " ".join(word.title() for word in component_name.split("_"))
|
||||||
word.title() for word in component_name.split("_")
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
component_name_camelcase = component_name
|
component_name_camelcase = component_name
|
||||||
|
|
||||||
|
|
@ -275,9 +255,7 @@ class DirectoryReader:
|
||||||
try:
|
try:
|
||||||
output_types = self.get_output_types_from_code(result_content)
|
output_types = self.get_output_types_from_code(result_content)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(
|
logger.exception(f"Error while getting output types from code: {str(exc)}")
|
||||||
f"Error while getting output types from code: {str(exc)}"
|
|
||||||
)
|
|
||||||
output_types = [component_name_camelcase]
|
output_types = [component_name_camelcase]
|
||||||
else:
|
else:
|
||||||
output_types = [component_name_camelcase]
|
output_types = [component_name_camelcase]
|
||||||
|
|
@ -293,9 +271,7 @@ class DirectoryReader:
|
||||||
|
|
||||||
if menu_result not in response["menu"]:
|
if menu_result not in response["menu"]:
|
||||||
response["menu"].append(menu_result)
|
response["menu"].append(menu_result)
|
||||||
logger.debug(
|
logger.debug("-------------------- Component menu list built --------------------")
|
||||||
"-------------------- Component menu list built --------------------"
|
|
||||||
)
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
||||||
|
|
@ -32,18 +32,14 @@ class UpdateBuildConfigError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def add_output_types(
|
def add_output_types(frontend_node: CustomComponentFrontendNode, return_types: List[str]):
|
||||||
frontend_node: CustomComponentFrontendNode, return_types: List[str]
|
|
||||||
):
|
|
||||||
"""Add output types to the frontend node"""
|
"""Add output types to the frontend node"""
|
||||||
for return_type in return_types:
|
for return_type in return_types:
|
||||||
if return_type is None:
|
if return_type is None:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail={
|
detail={
|
||||||
"error": (
|
"error": ("Invalid return type. Please check your code and try again."),
|
||||||
"Invalid return type. Please check your code and try again."
|
|
||||||
),
|
|
||||||
"traceback": traceback.format_exc(),
|
"traceback": traceback.format_exc(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -75,18 +71,14 @@ def reorder_fields(frontend_node: CustomComponentFrontendNode, field_order: List
|
||||||
frontend_node.field_order = field_order
|
frontend_node.field_order = field_order
|
||||||
|
|
||||||
|
|
||||||
def add_base_classes(
|
def add_base_classes(frontend_node: CustomComponentFrontendNode, return_types: List[str]):
|
||||||
frontend_node: CustomComponentFrontendNode, return_types: List[str]
|
|
||||||
):
|
|
||||||
"""Add base classes to the frontend node"""
|
"""Add base classes to the frontend node"""
|
||||||
for return_type_instance in return_types:
|
for return_type_instance in return_types:
|
||||||
if return_type_instance is None:
|
if return_type_instance is None:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail={
|
detail={
|
||||||
"error": (
|
"error": ("Invalid return type. Please check your code and try again."),
|
||||||
"Invalid return type. Please check your code and try again."
|
|
||||||
),
|
|
||||||
"traceback": traceback.format_exc(),
|
"traceback": traceback.format_exc(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -163,14 +155,10 @@ def add_new_custom_field(
|
||||||
# If options is a list, then it's a dropdown
|
# If options is a list, then it's a dropdown
|
||||||
# If options is None, then it's a list of strings
|
# If options is None, then it's a list of strings
|
||||||
is_list = isinstance(field_config.get("options"), list)
|
is_list = isinstance(field_config.get("options"), list)
|
||||||
field_config["is_list"] = (
|
field_config["is_list"] = is_list or field_config.get("is_list", False) or field_contains_list
|
||||||
is_list or field_config.get("is_list", False) or field_contains_list
|
|
||||||
)
|
|
||||||
|
|
||||||
if "name" in field_config:
|
if "name" in field_config:
|
||||||
warnings.warn(
|
warnings.warn("The 'name' key in field_config is used to build the object and can't be changed.")
|
||||||
"The 'name' key in field_config is used to build the object and can't be changed."
|
|
||||||
)
|
|
||||||
required = field_config.pop("required", field_required)
|
required = field_config.pop("required", field_required)
|
||||||
placeholder = field_config.pop("placeholder", "")
|
placeholder = field_config.pop("placeholder", "")
|
||||||
|
|
||||||
|
|
@ -209,9 +197,7 @@ def add_extra_fields(frontend_node, field_config, function_args):
|
||||||
]:
|
]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
field_name, field_type, field_value, field_required = get_field_properties(
|
field_name, field_type, field_value, field_required = get_field_properties(extra_field)
|
||||||
extra_field
|
|
||||||
)
|
|
||||||
config = _field_config.pop(field_name, {})
|
config = _field_config.pop(field_name, {})
|
||||||
frontend_node = add_new_custom_field(
|
frontend_node = add_new_custom_field(
|
||||||
frontend_node,
|
frontend_node,
|
||||||
|
|
@ -221,17 +207,13 @@ def add_extra_fields(frontend_node, field_config, function_args):
|
||||||
field_required,
|
field_required,
|
||||||
config,
|
config,
|
||||||
)
|
)
|
||||||
if "kwargs" in function_args_names and not all(
|
if "kwargs" in function_args_names and not all(key in function_args_names for key in field_config.keys()):
|
||||||
key in function_args_names for key in field_config.keys()
|
|
||||||
):
|
|
||||||
for field_name, field_config in _field_config.copy().items():
|
for field_name, field_config in _field_config.copy().items():
|
||||||
if "name" not in field_config or field_name == "code":
|
if "name" not in field_config or field_name == "code":
|
||||||
continue
|
continue
|
||||||
config = _field_config.get(field_name, {})
|
config = _field_config.get(field_name, {})
|
||||||
config = config.model_dump() if isinstance(config, BaseModel) else config
|
config = config.model_dump() if isinstance(config, BaseModel) else config
|
||||||
field_name, field_type, field_value, field_required = get_field_properties(
|
field_name, field_type, field_value, field_required = get_field_properties(extra_field=config)
|
||||||
extra_field=config
|
|
||||||
)
|
|
||||||
frontend_node = add_new_custom_field(
|
frontend_node = add_new_custom_field(
|
||||||
frontend_node,
|
frontend_node,
|
||||||
field_name,
|
field_name,
|
||||||
|
|
@ -269,9 +251,7 @@ def run_build_config(
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail={
|
detail={
|
||||||
"error": (
|
"error": ("Invalid type convertion. Please check your code and try again."),
|
||||||
"Invalid type convertion. Please check your code and try again."
|
|
||||||
),
|
|
||||||
"traceback": traceback.format_exc(),
|
"traceback": traceback.format_exc(),
|
||||||
},
|
},
|
||||||
) from exc
|
) from exc
|
||||||
|
|
@ -383,16 +363,10 @@ def build_custom_component_template(
|
||||||
|
|
||||||
add_extra_fields(frontend_node, field_config, entrypoint_args)
|
add_extra_fields(frontend_node, field_config, entrypoint_args)
|
||||||
|
|
||||||
frontend_node = add_code_field(
|
frontend_node = add_code_field(frontend_node, custom_component.code, field_config.get("code", {}))
|
||||||
frontend_node, custom_component.code, field_config.get("code", {})
|
|
||||||
)
|
|
||||||
|
|
||||||
add_base_classes(
|
add_base_classes(frontend_node, custom_component.get_function_entrypoint_return_type)
|
||||||
frontend_node, custom_component.get_function_entrypoint_return_type
|
add_output_types(frontend_node, custom_component.get_function_entrypoint_return_type)
|
||||||
)
|
|
||||||
add_output_types(
|
|
||||||
frontend_node, custom_component.get_function_entrypoint_return_type
|
|
||||||
)
|
|
||||||
|
|
||||||
reorder_fields(frontend_node, custom_instance._get_field_order())
|
reorder_fields(frontend_node, custom_instance._get_field_order())
|
||||||
|
|
||||||
|
|
@ -403,9 +377,7 @@ def build_custom_component_template(
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail={
|
detail={
|
||||||
"error": (
|
"error": ("Invalid type convertion. Please check your code and try again."),
|
||||||
"Invalid type convertion. Please check your code and try again."
|
|
||||||
),
|
|
||||||
"traceback": traceback.format_exc(),
|
"traceback": traceback.format_exc(),
|
||||||
},
|
},
|
||||||
) from exc
|
) from exc
|
||||||
|
|
@ -431,9 +403,7 @@ def build_custom_components(settings_service):
|
||||||
if not settings_service.settings.COMPONENTS_PATH:
|
if not settings_service.settings.COMPONENTS_PATH:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
logger.info(
|
logger.info(f"Building custom components from {settings_service.settings.COMPONENTS_PATH}")
|
||||||
f"Building custom components from {settings_service.settings.COMPONENTS_PATH}"
|
|
||||||
)
|
|
||||||
custom_components_from_file = {}
|
custom_components_from_file = {}
|
||||||
processed_paths = set()
|
processed_paths = set()
|
||||||
for path in settings_service.settings.COMPONENTS_PATH:
|
for path in settings_service.settings.COMPONENTS_PATH:
|
||||||
|
|
@ -444,9 +414,7 @@ def build_custom_components(settings_service):
|
||||||
custom_component_dict = build_custom_component_list_from_path(path_str)
|
custom_component_dict = build_custom_component_list_from_path(path_str)
|
||||||
if custom_component_dict:
|
if custom_component_dict:
|
||||||
category = next(iter(custom_component_dict))
|
category = next(iter(custom_component_dict))
|
||||||
logger.info(
|
logger.info(f"Loading {len(custom_component_dict[category])} component(s) from category {category}")
|
||||||
f"Loading {len(custom_component_dict[category])} component(s) from category {category}"
|
|
||||||
)
|
|
||||||
custom_components_from_file = merge_nested_dicts_with_renaming(
|
custom_components_from_file = merge_nested_dicts_with_renaming(
|
||||||
custom_components_from_file, custom_component_dict
|
custom_components_from_file, custom_component_dict
|
||||||
)
|
)
|
||||||
|
|
@ -467,14 +435,10 @@ def update_field_dict(
|
||||||
if "refresh" in field_dict:
|
if "refresh" in field_dict:
|
||||||
if call:
|
if call:
|
||||||
try:
|
try:
|
||||||
custom_component_instance.update_build_config(
|
custom_component_instance.update_build_config(build_config, update_field, update_field_value)
|
||||||
build_config, update_field, update_field_value
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(f"Error while running update_build_config: {str(exc)}")
|
logger.error(f"Error while running update_build_config: {str(exc)}")
|
||||||
raise UpdateBuildConfigError(
|
raise UpdateBuildConfigError(f"Error while running update_build_config: {str(exc)}") from exc
|
||||||
f"Error while running update_build_config: {str(exc)}"
|
|
||||||
) from exc
|
|
||||||
field_dict["refresh"] = True
|
field_dict["refresh"] = True
|
||||||
|
|
||||||
# Let's check if "range_spec" is a RangeSpec object
|
# Let's check if "range_spec" is a RangeSpec object
|
||||||
|
|
|
||||||
|
|
@ -144,13 +144,9 @@ async def instantiate_based_on_type(
|
||||||
return class_object(**params)
|
return class_object(**params)
|
||||||
|
|
||||||
|
|
||||||
async def instantiate_custom_component(
|
async def instantiate_custom_component(node_type, class_object, params, user_id, vertex):
|
||||||
node_type, class_object, params, user_id, vertex
|
|
||||||
):
|
|
||||||
params_copy = params.copy()
|
params_copy = params.copy()
|
||||||
class_object: Type["CustomComponent"] = eval_custom_component_code(
|
class_object: Type["CustomComponent"] = eval_custom_component_code(params_copy.pop("code"))
|
||||||
params_copy.pop("code")
|
|
||||||
)
|
|
||||||
custom_component: "CustomComponent" = class_object(
|
custom_component: "CustomComponent" = class_object(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
parameters=params_copy,
|
parameters=params_copy,
|
||||||
|
|
@ -226,9 +222,7 @@ def instantiate_memory(node_type, class_object, params):
|
||||||
# I want to catch a specific attribute error that happens
|
# I want to catch a specific attribute error that happens
|
||||||
# when the object does not have a cursor attribute
|
# when the object does not have a cursor attribute
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
if "object has no attribute 'cursor'" in str(
|
if "object has no attribute 'cursor'" in str(exc) or 'object has no field "conn"' in str(exc):
|
||||||
exc
|
|
||||||
) or 'object has no field "conn"' in str(exc):
|
|
||||||
raise AttributeError(
|
raise AttributeError(
|
||||||
(
|
(
|
||||||
"Failed to build connection to database."
|
"Failed to build connection to database."
|
||||||
|
|
@ -271,9 +265,7 @@ def instantiate_agent(node_type, class_object: Type[agent_module.Agent], params:
|
||||||
if class_method := getattr(class_object, method, None):
|
if class_method := getattr(class_object, method, None):
|
||||||
agent = class_method(**params)
|
agent = class_method(**params)
|
||||||
tools = params.get("tools", [])
|
tools = params.get("tools", [])
|
||||||
return AgentExecutor.from_agent_and_tools(
|
return AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, handle_parsing_errors=True)
|
||||||
agent=agent, tools=tools, handle_parsing_errors=True
|
|
||||||
)
|
|
||||||
return load_agent_executor(class_object, params)
|
return load_agent_executor(class_object, params)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -329,11 +321,7 @@ def instantiate_embedding(node_type, class_object, params: Dict):
|
||||||
try:
|
try:
|
||||||
return class_object(**params)
|
return class_object(**params)
|
||||||
except ValidationError:
|
except ValidationError:
|
||||||
params = {
|
params = {key: value for key, value in params.items() if key in class_object.model_fields}
|
||||||
key: value
|
|
||||||
for key, value in params.items()
|
|
||||||
if key in class_object.model_fields
|
|
||||||
}
|
|
||||||
return class_object(**params)
|
return class_object(**params)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -345,9 +333,7 @@ def instantiate_vectorstore(class_object: Type[VectorStore], params: Dict):
|
||||||
if "texts" in params:
|
if "texts" in params:
|
||||||
params["documents"] = params.pop("texts")
|
params["documents"] = params.pop("texts")
|
||||||
if "documents" in params:
|
if "documents" in params:
|
||||||
params["documents"] = [
|
params["documents"] = [doc for doc in params["documents"] if isinstance(doc, Document)]
|
||||||
doc for doc in params["documents"] if isinstance(doc, Document)
|
|
||||||
]
|
|
||||||
if initializer := vecstore_initializer.get(class_object.__name__):
|
if initializer := vecstore_initializer.get(class_object.__name__):
|
||||||
vecstore = initializer(class_object, params)
|
vecstore = initializer(class_object, params)
|
||||||
else:
|
else:
|
||||||
|
|
@ -362,9 +348,7 @@ def instantiate_vectorstore(class_object: Type[VectorStore], params: Dict):
|
||||||
return vecstore
|
return vecstore
|
||||||
|
|
||||||
|
|
||||||
def instantiate_documentloader(
|
def instantiate_documentloader(node_type: str, class_object: Type[BaseLoader], params: Dict):
|
||||||
node_type: str, class_object: Type[BaseLoader], params: Dict
|
|
||||||
):
|
|
||||||
if "file_filter" in params:
|
if "file_filter" in params:
|
||||||
# file_filter will be a string but we need a function
|
# file_filter will be a string but we need a function
|
||||||
# that will be used to filter the files using file_filter
|
# that will be used to filter the files using file_filter
|
||||||
|
|
@ -373,17 +357,13 @@ def instantiate_documentloader(
|
||||||
# in x and if it is, we will return True
|
# in x and if it is, we will return True
|
||||||
file_filter = params.pop("file_filter")
|
file_filter = params.pop("file_filter")
|
||||||
extensions = file_filter.split(",")
|
extensions = file_filter.split(",")
|
||||||
params["file_filter"] = lambda x: any(
|
params["file_filter"] = lambda x: any(extension.strip() in x for extension in extensions)
|
||||||
extension.strip() in x for extension in extensions
|
|
||||||
)
|
|
||||||
metadata = params.pop("metadata", None)
|
metadata = params.pop("metadata", None)
|
||||||
if metadata and isinstance(metadata, str):
|
if metadata and isinstance(metadata, str):
|
||||||
try:
|
try:
|
||||||
metadata = orjson.loads(metadata)
|
metadata = orjson.loads(metadata)
|
||||||
except json.JSONDecodeError as exc:
|
except json.JSONDecodeError as exc:
|
||||||
raise ValueError(
|
raise ValueError("The metadata you provided is not a valid JSON string.") from exc
|
||||||
"The metadata you provided is not a valid JSON string."
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
if node_type == "WebBaseLoader":
|
if node_type == "WebBaseLoader":
|
||||||
if web_path := params.pop("web_path", None):
|
if web_path := params.pop("web_path", None):
|
||||||
|
|
@ -416,16 +396,12 @@ def instantiate_textsplitter(
|
||||||
"Try changing the chunk_size of the Text Splitter."
|
"Try changing the chunk_size of the Text Splitter."
|
||||||
) from exc
|
) from exc
|
||||||
|
|
||||||
if (
|
if ("separator_type" in params and params["separator_type"] == "Text") or "separator_type" not in params:
|
||||||
"separator_type" in params and params["separator_type"] == "Text"
|
|
||||||
) or "separator_type" not in params:
|
|
||||||
params.pop("separator_type", None)
|
params.pop("separator_type", None)
|
||||||
# separators might come in as an escaped string like \\n
|
# separators might come in as an escaped string like \\n
|
||||||
# so we need to convert it to a string
|
# so we need to convert it to a string
|
||||||
if "separators" in params:
|
if "separators" in params:
|
||||||
params["separators"] = (
|
params["separators"] = params["separators"].encode().decode("unicode-escape")
|
||||||
params["separators"].encode().decode("unicode-escape")
|
|
||||||
)
|
|
||||||
text_splitter = class_object(**params)
|
text_splitter = class_object(**params)
|
||||||
else:
|
else:
|
||||||
from langchain.text_splitter import Language
|
from langchain.text_splitter import Language
|
||||||
|
|
@ -452,8 +428,7 @@ def replace_zero_shot_prompt_with_prompt_template(nodes):
|
||||||
tools = [
|
tools = [
|
||||||
tool
|
tool
|
||||||
for tool in nodes
|
for tool in nodes
|
||||||
if tool["type"] != "chatOutputNode"
|
if tool["type"] != "chatOutputNode" and "Tool" in tool["data"]["node"]["base_classes"]
|
||||||
and "Tool" in tool["data"]["node"]["base_classes"]
|
|
||||||
]
|
]
|
||||||
node["data"] = build_prompt_template(prompt=node["data"], tools=tools)
|
node["data"] = build_prompt_template(prompt=node["data"], tools=tools)
|
||||||
break
|
break
|
||||||
|
|
@ -467,9 +442,7 @@ def load_agent_executor(agent_class: type[agent_module.Agent], params, **kwargs)
|
||||||
# agent has hidden args for memory. might need to be support
|
# agent has hidden args for memory. might need to be support
|
||||||
# memory = params["memory"]
|
# memory = params["memory"]
|
||||||
# if allowed_tools is not a list or set, make it a list
|
# if allowed_tools is not a list or set, make it a list
|
||||||
if not isinstance(allowed_tools, (list, set)) and isinstance(
|
if not isinstance(allowed_tools, (list, set)) and isinstance(allowed_tools, BaseTool):
|
||||||
allowed_tools, BaseTool
|
|
||||||
):
|
|
||||||
allowed_tools = [allowed_tools]
|
allowed_tools = [allowed_tools]
|
||||||
tool_names = [tool.name for tool in allowed_tools]
|
tool_names = [tool.name for tool in allowed_tools]
|
||||||
# Agent class requires an output_parser but Agent classes
|
# Agent class requires an output_parser but Agent classes
|
||||||
|
|
@ -497,10 +470,7 @@ def build_prompt_template(prompt, tools):
|
||||||
format_instructions = prompt["node"]["template"]["format_instructions"]["value"]
|
format_instructions = prompt["node"]["template"]["format_instructions"]["value"]
|
||||||
|
|
||||||
tool_strings = "\n".join(
|
tool_strings = "\n".join(
|
||||||
[
|
[f"{tool['data']['node']['name']}: {tool['data']['node']['description']}" for tool in tools]
|
||||||
f"{tool['data']['node']['name']}: {tool['data']['node']['description']}"
|
|
||||||
for tool in tools
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
tool_names = ", ".join([tool["data"]["node"]["name"] for tool in tools])
|
tool_names = ", ".join([tool["data"]["node"]["name"] for tool in tools])
|
||||||
format_instructions = format_instructions.format(tool_names=tool_names)
|
format_instructions = format_instructions.format(tool_names=tool_names)
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,4 @@ def get_all_types_dict(settings_service):
|
||||||
"""Get all types dictionary combining native and custom components."""
|
"""Get all types dictionary combining native and custom components."""
|
||||||
native_components = build_langchain_types_dict()
|
native_components = build_langchain_types_dict()
|
||||||
custom_components_from_file = build_custom_components(settings_service)
|
custom_components_from_file = build_custom_components(settings_service)
|
||||||
return merge_nested_dicts_with_renaming(
|
return merge_nested_dicts_with_renaming(native_components, custom_components_from_file)
|
||||||
native_components, custom_components_from_file
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,7 @@ def try_setting_streaming_options(langchain_object):
|
||||||
llm = None
|
llm = None
|
||||||
if hasattr(langchain_object, "llm"):
|
if hasattr(langchain_object, "llm"):
|
||||||
llm = langchain_object.llm
|
llm = langchain_object.llm
|
||||||
elif hasattr(langchain_object, "llm_chain") and hasattr(
|
elif hasattr(langchain_object, "llm_chain") and hasattr(langchain_object.llm_chain, "llm"):
|
||||||
langchain_object.llm_chain, "llm"
|
|
||||||
):
|
|
||||||
llm = langchain_object.llm_chain.llm
|
llm = langchain_object.llm_chain.llm
|
||||||
|
|
||||||
if isinstance(llm, BaseLanguageModel):
|
if isinstance(llm, BaseLanguageModel):
|
||||||
|
|
@ -71,9 +69,7 @@ def extract_input_variables_from_prompt(prompt: str) -> list[str]:
|
||||||
|
|
||||||
# Extract the variable name from either the single or double brace match
|
# Extract the variable name from either the single or double brace match
|
||||||
if match.group(1): # Match found in double braces
|
if match.group(1): # Match found in double braces
|
||||||
variable_name = (
|
variable_name = "{{" + match.group(1) + "}}" # Re-add single braces for JSON strings
|
||||||
"{{" + match.group(1) + "}}"
|
|
||||||
) # Re-add single braces for JSON strings
|
|
||||||
else: # Match found in single braces
|
else: # Match found in single braces
|
||||||
variable_name = match.group(2)
|
variable_name = match.group(2)
|
||||||
if variable_name is not None:
|
if variable_name is not None:
|
||||||
|
|
@ -109,9 +105,7 @@ def set_langchain_cache(settings):
|
||||||
|
|
||||||
if cache_type := os.getenv("LANGFLOW_LANGCHAIN_CACHE"):
|
if cache_type := os.getenv("LANGFLOW_LANGCHAIN_CACHE"):
|
||||||
try:
|
try:
|
||||||
cache_class = import_class(
|
cache_class = import_class(f"langchain.cache.{cache_type or settings.LANGCHAIN_CACHE}")
|
||||||
f"langchain.cache.{cache_type or settings.LANGCHAIN_CACHE}"
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug(f"Setting up LLM caching with {cache_class.__name__}")
|
logger.debug(f"Setting up LLM caching with {cache_class.__name__}")
|
||||||
set_llm_cache(cache_class())
|
set_llm_cache(cache_class())
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,7 @@ from langflow.utils.logger import configure
|
||||||
def get_lifespan(fix_migration=False, socketio_server=None):
|
def get_lifespan(fix_migration=False, socketio_server=None):
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
initialize_services(
|
initialize_services(fix_migration=fix_migration, socketio_server=socketio_server)
|
||||||
fix_migration=fix_migration, socketio_server=socketio_server
|
|
||||||
)
|
|
||||||
setup_llm_caching()
|
setup_llm_caching()
|
||||||
LangfuseInstance.update()
|
LangfuseInstance.update()
|
||||||
create_or_update_starter_projects()
|
create_or_update_starter_projects()
|
||||||
|
|
@ -36,9 +34,7 @@ def create_app():
|
||||||
"""Create the FastAPI app and include the router."""
|
"""Create the FastAPI app and include the router."""
|
||||||
|
|
||||||
configure()
|
configure()
|
||||||
socketio_server = socketio.AsyncServer(
|
socketio_server = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins="*", logger=True)
|
||||||
async_mode="asgi", cors_allowed_origins="*", logger=True
|
|
||||||
)
|
|
||||||
lifespan = get_lifespan(socketio_server=socketio_server)
|
lifespan = get_lifespan(socketio_server=socketio_server)
|
||||||
app = FastAPI(lifespan=lifespan)
|
app = FastAPI(lifespan=lifespan)
|
||||||
origins = ["*"]
|
origins = ["*"]
|
||||||
|
|
@ -105,9 +101,7 @@ def get_static_files_dir():
|
||||||
return frontend_path / "frontend"
|
return frontend_path / "frontend"
|
||||||
|
|
||||||
|
|
||||||
def setup_app(
|
def setup_app(static_files_dir: Optional[Path] = None, backend_only: bool = False) -> FastAPI:
|
||||||
static_files_dir: Optional[Path] = None, backend_only: bool = False
|
|
||||||
) -> FastAPI:
|
|
||||||
"""Setup the FastAPI app."""
|
"""Setup the FastAPI app."""
|
||||||
# get the directory of the current file
|
# get the directory of the current file
|
||||||
if not static_files_dir:
|
if not static_files_dir:
|
||||||
|
|
|
||||||
|
|
@ -126,9 +126,7 @@ async def process_runnable(runnable: Runnable, inputs: Union[dict, List[dict]]):
|
||||||
elif isinstance(inputs, dict) and hasattr(runnable, "ainvoke"):
|
elif isinstance(inputs, dict) and hasattr(runnable, "ainvoke"):
|
||||||
result = await runnable.ainvoke(inputs)
|
result = await runnable.ainvoke(inputs)
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(f"Runnable {runnable} does not support inputs of type {type(inputs)}")
|
||||||
f"Runnable {runnable} does not support inputs of type {type(inputs)}"
|
|
||||||
)
|
|
||||||
# Check if the result is a list of AIMessages
|
# Check if the result is a list of AIMessages
|
||||||
if isinstance(result, list) and all(isinstance(r, AIMessage) for r in result):
|
if isinstance(result, list) and all(isinstance(r, AIMessage) for r in result):
|
||||||
result = [r.content for r in result]
|
result = [r.content for r in result]
|
||||||
|
|
@ -137,9 +135,7 @@ async def process_runnable(runnable: Runnable, inputs: Union[dict, List[dict]]):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
async def process_inputs_dict(
|
async def process_inputs_dict(built_object: Union[Chain, VectorStore, Runnable], inputs: dict):
|
||||||
built_object: Union[Chain, VectorStore, Runnable], inputs: dict
|
|
||||||
):
|
|
||||||
if isinstance(built_object, Chain):
|
if isinstance(built_object, Chain):
|
||||||
if inputs is None:
|
if inputs is None:
|
||||||
raise ValueError("Inputs must be provided for a Chain")
|
raise ValueError("Inputs must be provided for a Chain")
|
||||||
|
|
@ -174,9 +170,7 @@ async def process_inputs_list(built_object: Runnable, inputs: List[dict]):
|
||||||
return await process_runnable(built_object, inputs)
|
return await process_runnable(built_object, inputs)
|
||||||
|
|
||||||
|
|
||||||
async def generate_result(
|
async def generate_result(built_object: Union[Chain, VectorStore, Runnable], inputs: Union[dict, List[dict]]):
|
||||||
built_object: Union[Chain, VectorStore, Runnable], inputs: Union[dict, List[dict]]
|
|
||||||
):
|
|
||||||
if isinstance(inputs, dict):
|
if isinstance(inputs, dict):
|
||||||
result = await process_inputs_dict(built_object, inputs)
|
result = await process_inputs_dict(built_object, inputs)
|
||||||
elif isinstance(inputs, List) and isinstance(built_object, Runnable):
|
elif isinstance(inputs, List) and isinstance(built_object, Runnable):
|
||||||
|
|
@ -215,9 +209,7 @@ async def run_graph(
|
||||||
else:
|
else:
|
||||||
graph_data = graph._graph_data
|
graph_data = graph._graph_data
|
||||||
if not session_id and session_service is not None:
|
if not session_id and session_service is not None:
|
||||||
session_id = session_service.generate_key(
|
session_id = session_service.generate_key(session_id=flow_id, data_graph=graph_data)
|
||||||
session_id=flow_id, data_graph=graph_data
|
|
||||||
)
|
|
||||||
if inputs is None:
|
if inputs is None:
|
||||||
inputs = {}
|
inputs = {}
|
||||||
|
|
||||||
|
|
@ -232,18 +224,14 @@ async def run_graph(
|
||||||
return outputs, session_id
|
return outputs, session_id
|
||||||
|
|
||||||
|
|
||||||
def validate_input(
|
def validate_input(graph_data: Dict[str, Any], tweaks: Dict[str, Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||||
graph_data: Dict[str, Any], tweaks: Dict[str, Dict[str, Any]]
|
|
||||||
) -> List[Dict[str, Any]]:
|
|
||||||
if not isinstance(graph_data, dict) or not isinstance(tweaks, dict):
|
if not isinstance(graph_data, dict) or not isinstance(tweaks, dict):
|
||||||
raise ValueError("graph_data and tweaks should be dictionaries")
|
raise ValueError("graph_data and tweaks should be dictionaries")
|
||||||
|
|
||||||
nodes = graph_data.get("data", {}).get("nodes") or graph_data.get("nodes")
|
nodes = graph_data.get("data", {}).get("nodes") or graph_data.get("nodes")
|
||||||
|
|
||||||
if not isinstance(nodes, list):
|
if not isinstance(nodes, list):
|
||||||
raise ValueError(
|
raise ValueError("graph_data should contain a list of nodes under 'data' key or directly under 'nodes' key")
|
||||||
"graph_data should contain a list of nodes under 'data' key or directly under 'nodes' key"
|
|
||||||
)
|
|
||||||
|
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
|
|
@ -252,9 +240,7 @@ def apply_tweaks(node: Dict[str, Any], node_tweaks: Dict[str, Any]) -> None:
|
||||||
template_data = node.get("data", {}).get("node", {}).get("template")
|
template_data = node.get("data", {}).get("node", {}).get("template")
|
||||||
|
|
||||||
if not isinstance(template_data, dict):
|
if not isinstance(template_data, dict):
|
||||||
logger.warning(
|
logger.warning(f"Template data for node {node.get('id')} should be a dictionary")
|
||||||
f"Template data for node {node.get('id')} should be a dictionary"
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
for tweak_name, tweak_value in node_tweaks.items():
|
for tweak_name, tweak_value in node_tweaks.items():
|
||||||
|
|
@ -269,9 +255,7 @@ def apply_tweaks_on_vertex(vertex: Vertex, node_tweaks: Dict[str, Any]) -> None:
|
||||||
vertex.params[tweak_name] = tweak_value
|
vertex.params[tweak_name] = tweak_value
|
||||||
|
|
||||||
|
|
||||||
def process_tweaks(
|
def process_tweaks(graph_data: Dict[str, Any], tweaks: Dict[str, Dict[str, Any]]) -> Dict[str, Any]:
|
||||||
graph_data: Dict[str, Any], tweaks: Dict[str, Dict[str, Any]]
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
"""
|
||||||
This function is used to tweak the graph data using the node id and the tweaks dict.
|
This function is used to tweak the graph data using the node id and the tweaks dict.
|
||||||
|
|
||||||
|
|
@ -307,8 +291,6 @@ def process_tweaks_on_graph(graph: Graph, tweaks: Dict[str, Dict[str, Any]]):
|
||||||
if node_tweaks := tweaks.get(node_id):
|
if node_tweaks := tweaks.get(node_id):
|
||||||
apply_tweaks_on_vertex(vertex, node_tweaks)
|
apply_tweaks_on_vertex(vertex, node_tweaks)
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning("Each node should be a Vertex with an 'id' attribute of type str")
|
||||||
"Each node should be a Vertex with an 'id' attribute of type str"
|
|
||||||
)
|
|
||||||
|
|
||||||
return graph
|
return graph
|
||||||
|
|
|
||||||
|
|
@ -63,9 +63,7 @@ class Record(BaseModel):
|
||||||
return self.data.get(key, self._default_value)
|
return self.data.get(key, self._default_value)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# Fallback to default behavior to raise AttributeError for undefined attributes
|
# Fallback to default behavior to raise AttributeError for undefined attributes
|
||||||
raise AttributeError(
|
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{key}'")
|
||||||
f"'{type(self).__name__}' object has no attribute '{key}'"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __setattr__(self, key, value):
|
def __setattr__(self, key, value):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,7 @@ async def process_graph(
|
||||||
|
|
||||||
if build_result is None:
|
if build_result is None:
|
||||||
# Raise user facing error
|
# Raise user facing error
|
||||||
raise ValueError(
|
raise ValueError("There was an error loading the langchain_object. Please, check all the nodes and try again.")
|
||||||
"There was an error loading the langchain_object. Please, check all the nodes and try again."
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generate result and thought
|
# Generate result and thought
|
||||||
try:
|
try:
|
||||||
|
|
@ -50,7 +48,5 @@ async def process_graph(
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
||||||
async def run_build_result(
|
async def run_build_result(build_result: Any, chat_inputs: ChatMessage, client_id: str, session_id: str):
|
||||||
build_result: Any, chat_inputs: ChatMessage, client_id: str, session_id: str
|
|
||||||
):
|
|
||||||
return build_result(inputs=chat_inputs.message)
|
return build_result(inputs=chat_inputs.message)
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,7 @@ class FlowBase(SQLModel):
|
||||||
icon_bg_color: Optional[str] = Field(default=None, nullable=True)
|
icon_bg_color: Optional[str] = Field(default=None, nullable=True)
|
||||||
data: Optional[Dict] = Field(default=None, nullable=True)
|
data: Optional[Dict] = Field(default=None, nullable=True)
|
||||||
is_component: Optional[bool] = Field(default=False, nullable=True)
|
is_component: Optional[bool] = Field(default=False, nullable=True)
|
||||||
updated_at: Optional[datetime] = Field(
|
updated_at: Optional[datetime] = Field(default_factory=datetime.utcnow, nullable=True)
|
||||||
default_factory=datetime.utcnow, nullable=True
|
|
||||||
)
|
|
||||||
folder: Optional[str] = Field(default=None, nullable=True)
|
folder: Optional[str] = Field(default=None, nullable=True)
|
||||||
|
|
||||||
@field_validator("icon_bg_color")
|
@field_validator("icon_bg_color")
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,7 @@ class DatabaseService(Service):
|
||||||
def _create_engine(self) -> "Engine":
|
def _create_engine(self) -> "Engine":
|
||||||
"""Create the engine for the database."""
|
"""Create the engine for the database."""
|
||||||
settings_service = get_settings_service()
|
settings_service = get_settings_service()
|
||||||
if (
|
if settings_service.settings.DATABASE_URL and settings_service.settings.DATABASE_URL.startswith("sqlite"):
|
||||||
settings_service.settings.DATABASE_URL
|
|
||||||
and settings_service.settings.DATABASE_URL.startswith("sqlite")
|
|
||||||
):
|
|
||||||
connect_args = {"check_same_thread": False}
|
connect_args = {"check_same_thread": False}
|
||||||
else:
|
else:
|
||||||
connect_args = {}
|
connect_args = {}
|
||||||
|
|
@ -51,9 +48,7 @@ class DatabaseService(Service):
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, traceback):
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
if exc_type is not None: # If an exception has been raised
|
if exc_type is not None: # If an exception has been raised
|
||||||
logger.error(
|
logger.error(f"Session rollback because of exception: {exc_type.__name__} {exc_value}")
|
||||||
f"Session rollback because of exception: {exc_type.__name__} {exc_value}"
|
|
||||||
)
|
|
||||||
self._session.rollback()
|
self._session.rollback()
|
||||||
else:
|
else:
|
||||||
self._session.commit()
|
self._session.commit()
|
||||||
|
|
@ -70,9 +65,7 @@ class DatabaseService(Service):
|
||||||
settings_service = get_settings_service()
|
settings_service = get_settings_service()
|
||||||
if settings_service.auth_settings.AUTO_LOGIN:
|
if settings_service.auth_settings.AUTO_LOGIN:
|
||||||
with Session(self.engine) as session:
|
with Session(self.engine) as session:
|
||||||
flows = session.exec(
|
flows = session.exec(select(models.Flow).where(models.Flow.user_id is None)).all()
|
||||||
select(models.Flow).where(models.Flow.user_id is None)
|
|
||||||
).all()
|
|
||||||
if flows:
|
if flows:
|
||||||
logger.debug("Migrating flows to default superuser")
|
logger.debug("Migrating flows to default superuser")
|
||||||
username = settings_service.auth_settings.SUPERUSER
|
username = settings_service.auth_settings.SUPERUSER
|
||||||
|
|
@ -102,9 +95,7 @@ class DatabaseService(Service):
|
||||||
expected_columns = list(model.model_fields.keys())
|
expected_columns = list(model.model_fields.keys())
|
||||||
|
|
||||||
try:
|
try:
|
||||||
available_columns = [
|
available_columns = [col["name"] for col in inspector.get_columns(table)]
|
||||||
col["name"] for col in inspector.get_columns(table)
|
|
||||||
]
|
|
||||||
except sa.exc.NoSuchTableError:
|
except sa.exc.NoSuchTableError:
|
||||||
logger.error(f"Missing table: {table}")
|
logger.error(f"Missing table: {table}")
|
||||||
return False
|
return False
|
||||||
|
|
@ -161,9 +152,7 @@ class DatabaseService(Service):
|
||||||
try:
|
try:
|
||||||
command.check(alembic_cfg)
|
command.check(alembic_cfg)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
if isinstance(
|
if isinstance(exc, (util.exc.CommandError, util.exc.AutogenerateDiffsDetected)):
|
||||||
exc, (util.exc.CommandError, util.exc.AutogenerateDiffsDetected)
|
|
||||||
):
|
|
||||||
command.upgrade(alembic_cfg, "head")
|
command.upgrade(alembic_cfg, "head")
|
||||||
time.sleep(3)
|
time.sleep(3)
|
||||||
|
|
||||||
|
|
@ -199,10 +188,7 @@ class DatabaseService(Service):
|
||||||
# We will check that all models are in the database
|
# We will check that all models are in the database
|
||||||
# and that the database is up to date with all columns
|
# and that the database is up to date with all columns
|
||||||
sql_models = [models.Flow, models.User, models.ApiKey]
|
sql_models = [models.Flow, models.User, models.ApiKey]
|
||||||
return [
|
return [TableResults(sql_model.__tablename__, self.check_table(sql_model)) for sql_model in sql_models]
|
||||||
TableResults(sql_model.__tablename__, self.check_table(sql_model))
|
|
||||||
for sql_model in sql_models
|
|
||||||
]
|
|
||||||
|
|
||||||
def check_table(self, model):
|
def check_table(self, model):
|
||||||
results = []
|
results = []
|
||||||
|
|
@ -211,9 +197,7 @@ class DatabaseService(Service):
|
||||||
expected_columns = list(model.__fields__.keys())
|
expected_columns = list(model.__fields__.keys())
|
||||||
available_columns = []
|
available_columns = []
|
||||||
try:
|
try:
|
||||||
available_columns = [
|
available_columns = [col["name"] for col in inspector.get_columns(table_name)]
|
||||||
col["name"] for col in inspector.get_columns(table_name)
|
|
||||||
]
|
|
||||||
results.append(Result(name=table_name, type="table", success=True))
|
results.append(Result(name=table_name, type="table", success=True))
|
||||||
except sa.exc.NoSuchTableError:
|
except sa.exc.NoSuchTableError:
|
||||||
logger.error(f"Missing table: {table_name}")
|
logger.error(f"Missing table: {table_name}")
|
||||||
|
|
@ -244,9 +228,7 @@ class DatabaseService(Service):
|
||||||
try:
|
try:
|
||||||
table.create(self.engine, checkfirst=True)
|
table.create(self.engine, checkfirst=True)
|
||||||
except OperationalError as oe:
|
except OperationalError as oe:
|
||||||
logger.warning(
|
logger.warning(f"Table {table} already exists, skipping. Exception: {oe}")
|
||||||
f"Table {table} already exists, skipping. Exception: {oe}"
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(f"Error creating table {table}: {exc}")
|
logger.error(f"Error creating table {table}: {exc}")
|
||||||
raise RuntimeError(f"Error creating table {table}") from exc
|
raise RuntimeError(f"Error creating table {table}") from exc
|
||||||
|
|
@ -258,9 +240,7 @@ class DatabaseService(Service):
|
||||||
if table not in table_names:
|
if table not in table_names:
|
||||||
logger.error("Something went wrong creating the database and tables.")
|
logger.error("Something went wrong creating the database and tables.")
|
||||||
logger.error("Please check your database settings.")
|
logger.error("Please check your database settings.")
|
||||||
raise RuntimeError(
|
raise RuntimeError("Something went wrong creating the database and tables.")
|
||||||
"Something went wrong creating the database and tables."
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug("Database and tables created successfully")
|
logger.debug("Database and tables created successfully")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
class TransactionModel(BaseModel):
|
class TransactionModel(BaseModel):
|
||||||
id: Optional[int] = Field(default=None, alias="id")
|
id: Optional[int] = Field(default=None, alias="id")
|
||||||
timestamp: Optional[datetime] = Field(
|
timestamp: Optional[datetime] = Field(default_factory=datetime.now, alias="timestamp")
|
||||||
default_factory=datetime.now, alias="timestamp"
|
|
||||||
)
|
|
||||||
source: str
|
source: str
|
||||||
target: str
|
target: str
|
||||||
target_args: dict
|
target_args: dict
|
||||||
|
|
@ -53,12 +51,8 @@ class MessageModel(BaseModel):
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_record(cls, record: "Record"):
|
def from_record(cls, record: "Record"):
|
||||||
# first check if the record has all the required fields
|
# first check if the record has all the required fields
|
||||||
if not record.data or (
|
if not record.data or ("sender" not in record.data and "sender_name" not in record.data):
|
||||||
"sender" not in record.data and "sender_name" not in record.data
|
raise ValueError("The record does not have the required fields 'sender' and 'sender_name' in the data.")
|
||||||
):
|
|
||||||
raise ValueError(
|
|
||||||
"The record does not have the required fields 'sender' and 'sender_name' in the data."
|
|
||||||
)
|
|
||||||
return cls(
|
return cls(
|
||||||
sender=record.data["sender"],
|
sender=record.data["sender"],
|
||||||
sender_name=record.data["sender_name"],
|
sender_name=record.data["sender_name"],
|
||||||
|
|
|
||||||
|
|
@ -58,12 +58,10 @@ class Settings(BaseSettings):
|
||||||
|
|
||||||
STORE: Optional[bool] = True
|
STORE: Optional[bool] = True
|
||||||
STORE_URL: Optional[str] = "https://api.langflow.store"
|
STORE_URL: Optional[str] = "https://api.langflow.store"
|
||||||
DOWNLOAD_WEBHOOK_URL: Optional[str] = (
|
DOWNLOAD_WEBHOOK_URL: Optional[
|
||||||
"https://api.langflow.store/flows/trigger/ec611a61-8460-4438-b187-a4f65e5559d4"
|
str
|
||||||
)
|
] = "https://api.langflow.store/flows/trigger/ec611a61-8460-4438-b187-a4f65e5559d4"
|
||||||
LIKE_WEBHOOK_URL: Optional[str] = (
|
LIKE_WEBHOOK_URL: Optional[str] = "https://api.langflow.store/flows/trigger/64275852-ec00-45c1-984e-3bff814732da"
|
||||||
"https://api.langflow.store/flows/trigger/64275852-ec00-45c1-984e-3bff814732da"
|
|
||||||
)
|
|
||||||
|
|
||||||
STORAGE_TYPE: str = "local"
|
STORAGE_TYPE: str = "local"
|
||||||
|
|
||||||
|
|
@ -95,9 +93,7 @@ class Settings(BaseSettings):
|
||||||
@validator("DATABASE_URL", pre=True)
|
@validator("DATABASE_URL", pre=True)
|
||||||
def set_database_url(cls, value, values):
|
def set_database_url(cls, value, values):
|
||||||
if not value:
|
if not value:
|
||||||
logger.debug(
|
logger.debug("No database_url provided, trying LANGFLOW_DATABASE_URL env variable")
|
||||||
"No database_url provided, trying LANGFLOW_DATABASE_URL env variable"
|
|
||||||
)
|
|
||||||
if langflow_database_url := os.getenv("LANGFLOW_DATABASE_URL"):
|
if langflow_database_url := os.getenv("LANGFLOW_DATABASE_URL"):
|
||||||
value = langflow_database_url
|
value = langflow_database_url
|
||||||
logger.debug("Using LANGFLOW_DATABASE_URL env variable.")
|
logger.debug("Using LANGFLOW_DATABASE_URL env variable.")
|
||||||
|
|
@ -107,9 +103,7 @@ class Settings(BaseSettings):
|
||||||
# so we need to migrate to the new format
|
# so we need to migrate to the new format
|
||||||
# if there is a database in that location
|
# if there is a database in that location
|
||||||
if not values["CONFIG_DIR"]:
|
if not values["CONFIG_DIR"]:
|
||||||
raise ValueError(
|
raise ValueError("CONFIG_DIR not set, please set it or provide a DATABASE_URL")
|
||||||
"CONFIG_DIR not set, please set it or provide a DATABASE_URL"
|
|
||||||
)
|
|
||||||
|
|
||||||
new_path = f"{values['CONFIG_DIR']}/langflow.db"
|
new_path = f"{values['CONFIG_DIR']}/langflow.db"
|
||||||
if Path("./langflow.db").exists():
|
if Path("./langflow.db").exists():
|
||||||
|
|
@ -133,22 +127,15 @@ class Settings(BaseSettings):
|
||||||
if os.getenv("LANGFLOW_COMPONENTS_PATH"):
|
if os.getenv("LANGFLOW_COMPONENTS_PATH"):
|
||||||
logger.debug("Adding LANGFLOW_COMPONENTS_PATH to components_path")
|
logger.debug("Adding LANGFLOW_COMPONENTS_PATH to components_path")
|
||||||
langflow_component_path = os.getenv("LANGFLOW_COMPONENTS_PATH")
|
langflow_component_path = os.getenv("LANGFLOW_COMPONENTS_PATH")
|
||||||
if (
|
if Path(langflow_component_path).exists() and langflow_component_path not in value:
|
||||||
Path(langflow_component_path).exists()
|
|
||||||
and langflow_component_path not in value
|
|
||||||
):
|
|
||||||
if isinstance(langflow_component_path, list):
|
if isinstance(langflow_component_path, list):
|
||||||
for path in langflow_component_path:
|
for path in langflow_component_path:
|
||||||
if path not in value:
|
if path not in value:
|
||||||
value.append(path)
|
value.append(path)
|
||||||
logger.debug(
|
logger.debug(f"Extending {langflow_component_path} to components_path")
|
||||||
f"Extending {langflow_component_path} to components_path"
|
|
||||||
)
|
|
||||||
elif langflow_component_path not in value:
|
elif langflow_component_path not in value:
|
||||||
value.append(langflow_component_path)
|
value.append(langflow_component_path)
|
||||||
logger.debug(
|
logger.debug(f"Appending {langflow_component_path} to components_path")
|
||||||
f"Appending {langflow_component_path} to components_path"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not value:
|
if not value:
|
||||||
value = [BASE_COMPONENTS_PATH]
|
value = [BASE_COMPONENTS_PATH]
|
||||||
|
|
@ -160,9 +147,7 @@ class Settings(BaseSettings):
|
||||||
logger.debug(f"Components path: {value}")
|
logger.debug(f"Components path: {value}")
|
||||||
return value
|
return value
|
||||||
|
|
||||||
model_config = SettingsConfigDict(
|
model_config = SettingsConfigDict(validate_assignment=True, extra="ignore", env_prefix="LANGFLOW_")
|
||||||
validate_assignment=True, extra="ignore", env_prefix="LANGFLOW_"
|
|
||||||
)
|
|
||||||
|
|
||||||
# @model_validator()
|
# @model_validator()
|
||||||
# @classmethod
|
# @classmethod
|
||||||
|
|
|
||||||
|
|
@ -96,9 +96,7 @@ async def build_vertex(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Emit the vertex build response
|
# Emit the vertex build response
|
||||||
response = VertexBuildResponse(
|
response = VertexBuildResponse(valid=valid, params=params, id=vertex.id, data=result_dict)
|
||||||
valid=valid, params=params, id=vertex.id, data=result_dict
|
|
||||||
)
|
|
||||||
await sio.emit("vertex_build", data=response.model_dump(), to=sid)
|
await sio.emit("vertex_build", data=response.model_dump(), to=sid)
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
|
|
||||||
|
|
@ -74,9 +74,7 @@ class TaskService(Service):
|
||||||
result = await result
|
result = await result
|
||||||
return task.id, result
|
return task.id, result
|
||||||
|
|
||||||
async def launch_task(
|
async def launch_task(self, task_func: Callable[..., Any], *args: Any, **kwargs: Any) -> Any:
|
||||||
self, task_func: Callable[..., Any], *args: Any, **kwargs: Any
|
|
||||||
) -> Any:
|
|
||||||
logger.debug(f"Launching task {task_func} with args {args} and kwargs {kwargs}")
|
logger.debug(f"Launching task {task_func} with args {args} and kwargs {kwargs}")
|
||||||
logger.debug(f"Using backend {self.backend}")
|
logger.debug(f"Using backend {self.backend}")
|
||||||
task = self.backend.launch_task(task_func, *args, **kwargs)
|
task = self.backend.launch_task(task_func, *args, **kwargs)
|
||||||
|
|
|
||||||
|
|
@ -92,16 +92,12 @@ def get_or_create_super_user(session: Session, username, password, is_default):
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
logger.debug(
|
logger.debug("User with superuser credentials exists but is not a superuser.")
|
||||||
"User with superuser credentials exists but is not a superuser."
|
|
||||||
)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if user:
|
if user:
|
||||||
if verify_password(password, user.password):
|
if verify_password(password, user.password):
|
||||||
raise ValueError(
|
raise ValueError("User with superuser credentials exists but is not a superuser.")
|
||||||
"User with superuser credentials exists but is not a superuser."
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
raise ValueError("Incorrect superuser credentials")
|
raise ValueError("Incorrect superuser credentials")
|
||||||
|
|
||||||
|
|
@ -130,21 +126,15 @@ def setup_superuser(settings_service, session: Session):
|
||||||
username = settings_service.auth_settings.SUPERUSER
|
username = settings_service.auth_settings.SUPERUSER
|
||||||
password = settings_service.auth_settings.SUPERUSER_PASSWORD
|
password = settings_service.auth_settings.SUPERUSER_PASSWORD
|
||||||
|
|
||||||
is_default = (username == DEFAULT_SUPERUSER) and (
|
is_default = (username == DEFAULT_SUPERUSER) and (password == DEFAULT_SUPERUSER_PASSWORD)
|
||||||
password == DEFAULT_SUPERUSER_PASSWORD
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user = get_or_create_super_user(
|
user = get_or_create_super_user(session=session, username=username, password=password, is_default=is_default)
|
||||||
session=session, username=username, password=password, is_default=is_default
|
|
||||||
)
|
|
||||||
if user is not None:
|
if user is not None:
|
||||||
logger.debug("Superuser created successfully.")
|
logger.debug("Superuser created successfully.")
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(exc)
|
logger.exception(exc)
|
||||||
raise RuntimeError(
|
raise RuntimeError("Could not create superuser. Please create a superuser manually.") from exc
|
||||||
"Could not create superuser. Please create a superuser manually."
|
|
||||||
) from exc
|
|
||||||
finally:
|
finally:
|
||||||
settings_service.auth_settings.reset_credentials()
|
settings_service.auth_settings.reset_credentials()
|
||||||
|
|
||||||
|
|
@ -158,9 +148,7 @@ def teardown_superuser(settings_service, session):
|
||||||
|
|
||||||
if not settings_service.auth_settings.AUTO_LOGIN:
|
if not settings_service.auth_settings.AUTO_LOGIN:
|
||||||
try:
|
try:
|
||||||
logger.debug(
|
logger.debug("AUTO_LOGIN is set to False. Removing default superuser if exists.")
|
||||||
"AUTO_LOGIN is set to False. Removing default superuser if exists."
|
|
||||||
)
|
|
||||||
username = DEFAULT_SUPERUSER
|
username = DEFAULT_SUPERUSER
|
||||||
from langflow.services.database.models.user.model import User
|
from langflow.services.database.models.user.model import User
|
||||||
|
|
||||||
|
|
@ -210,9 +198,7 @@ def initialize_session_service():
|
||||||
|
|
||||||
initialize_settings_service()
|
initialize_settings_service()
|
||||||
|
|
||||||
service_manager.register_factory(
|
service_manager.register_factory(cache_factory.CacheServiceFactory(), dependencies=[ServiceType.SETTINGS_SERVICE])
|
||||||
cache_factory.CacheServiceFactory(), dependencies=[ServiceType.SETTINGS_SERVICE]
|
|
||||||
)
|
|
||||||
|
|
||||||
service_manager.register_factory(
|
service_manager.register_factory(
|
||||||
session_service_factory.SessionServiceFactory(),
|
session_service_factory.SessionServiceFactory(),
|
||||||
|
|
@ -229,9 +215,7 @@ def initialize_services(fix_migration: bool = False, socketio_server=None):
|
||||||
service_manager.register_factory(factory, dependencies=dependencies)
|
service_manager.register_factory(factory, dependencies=dependencies)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(exc)
|
logger.exception(exc)
|
||||||
raise RuntimeError(
|
raise RuntimeError("Could not initialize services. Please check your settings.") from exc
|
||||||
"Could not initialize services. Please check your settings."
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
# Test cache connection
|
# Test cache connection
|
||||||
service_manager.get(ServiceType.CACHE_SERVICE)
|
service_manager.get(ServiceType.CACHE_SERVICE)
|
||||||
|
|
@ -241,9 +225,7 @@ def initialize_services(fix_migration: bool = False, socketio_server=None):
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.error(exc)
|
logger.error(exc)
|
||||||
raise exc
|
raise exc
|
||||||
setup_superuser(
|
setup_superuser(service_manager.get(ServiceType.SETTINGS_SERVICE), next(get_session()))
|
||||||
service_manager.get(ServiceType.SETTINGS_SERVICE), next(get_session())
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
get_db_service().migrate_flows_if_auto_login()
|
get_db_service().migrate_flows_if_auto_login()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
|
|
||||||
|
|
@ -68,9 +68,7 @@ class TemplateField(BaseModel):
|
||||||
refresh: Optional[bool] = None
|
refresh: Optional[bool] = None
|
||||||
"""Specifies if the field should be refreshed. Defaults to False."""
|
"""Specifies if the field should be refreshed. Defaults to False."""
|
||||||
|
|
||||||
range_spec: Optional[RangeSpec] = Field(
|
range_spec: Optional[RangeSpec] = Field(default=None, serialization_alias="rangeSpec")
|
||||||
default=None, serialization_alias="rangeSpec"
|
|
||||||
)
|
|
||||||
"""Range specification for the field. Defaults to None."""
|
"""Range specification for the field. Defaults to None."""
|
||||||
|
|
||||||
title_case: bool = False
|
title_case: bool = False
|
||||||
|
|
@ -119,10 +117,6 @@ class TemplateField(BaseModel):
|
||||||
if not isinstance(value, list):
|
if not isinstance(value, list):
|
||||||
raise ValueError("file_types must be a list")
|
raise ValueError("file_types must be a list")
|
||||||
return [
|
return [
|
||||||
(
|
(f".{file_type}" if isinstance(file_type, str) and not file_type.startswith(".") else file_type)
|
||||||
f".{file_type}"
|
|
||||||
if isinstance(file_type, str) and not file_type.startswith(".")
|
|
||||||
else file_type
|
|
||||||
)
|
|
||||||
for file_type in value
|
for file_type in value
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -174,9 +174,7 @@ class FrontendNode(BaseModel):
|
||||||
return _type
|
return _type
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def handle_special_field(
|
def handle_special_field(field, key: str, _type: str, SPECIAL_FIELD_HANDLERS) -> str:
|
||||||
field, key: str, _type: str, SPECIAL_FIELD_HANDLERS
|
|
||||||
) -> str:
|
|
||||||
"""Handles special field by using the respective handler if present."""
|
"""Handles special field by using the respective handler if present."""
|
||||||
handler = SPECIAL_FIELD_HANDLERS.get(key)
|
handler = SPECIAL_FIELD_HANDLERS.get(key)
|
||||||
return handler(field) if handler else _type
|
return handler(field) if handler else _type
|
||||||
|
|
@ -187,11 +185,7 @@ class FrontendNode(BaseModel):
|
||||||
if "dict" in _type.lower() and field.name == "dict_":
|
if "dict" in _type.lower() and field.name == "dict_":
|
||||||
field.field_type = "file"
|
field.field_type = "file"
|
||||||
field.file_types = [".json", ".yaml", ".yml"]
|
field.file_types = [".json", ".yaml", ".yml"]
|
||||||
elif (
|
elif _type.startswith("Dict") or _type.startswith("Mapping") or _type.startswith("dict"):
|
||||||
_type.startswith("Dict")
|
|
||||||
or _type.startswith("Mapping")
|
|
||||||
or _type.startswith("dict")
|
|
||||||
):
|
|
||||||
field.field_type = "dict"
|
field.field_type = "dict"
|
||||||
return _type
|
return _type
|
||||||
|
|
||||||
|
|
@ -202,9 +196,7 @@ class FrontendNode(BaseModel):
|
||||||
field.value = value["default"]
|
field.value = value["default"]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def handle_specific_field_values(
|
def handle_specific_field_values(field: TemplateField, key: str, name: Optional[str] = None) -> None:
|
||||||
field: TemplateField, key: str, name: Optional[str] = None
|
|
||||||
) -> None:
|
|
||||||
"""Handles specific field values for certain fields."""
|
"""Handles specific field values for certain fields."""
|
||||||
if key == "headers":
|
if key == "headers":
|
||||||
field.value = """{"Authorization": "Bearer <token>"}"""
|
field.value = """{"Authorization": "Bearer <token>"}"""
|
||||||
|
|
@ -212,9 +204,7 @@ class FrontendNode(BaseModel):
|
||||||
FrontendNode._handle_api_key_specific_field_values(field, key, name)
|
FrontendNode._handle_api_key_specific_field_values(field, key, name)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _handle_model_specific_field_values(
|
def _handle_model_specific_field_values(field: TemplateField, key: str, name: Optional[str] = None) -> None:
|
||||||
field: TemplateField, key: str, name: Optional[str] = None
|
|
||||||
) -> None:
|
|
||||||
"""Handles specific field values related to models."""
|
"""Handles specific field values related to models."""
|
||||||
model_dict = {
|
model_dict = {
|
||||||
"OpenAI": constants.OPENAI_MODELS,
|
"OpenAI": constants.OPENAI_MODELS,
|
||||||
|
|
@ -227,9 +217,7 @@ class FrontendNode(BaseModel):
|
||||||
field.is_list = True
|
field.is_list = True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _handle_api_key_specific_field_values(
|
def _handle_api_key_specific_field_values(field: TemplateField, key: str, name: Optional[str] = None) -> None:
|
||||||
field: TemplateField, key: str, name: Optional[str] = None
|
|
||||||
) -> None:
|
|
||||||
"""Handles specific field values related to API keys."""
|
"""Handles specific field values related to API keys."""
|
||||||
if "api_key" in key and "OpenAI" in str(name):
|
if "api_key" in key and "OpenAI" in str(name):
|
||||||
field.display_name = "OpenAI API Key"
|
field.display_name = "OpenAI API Key"
|
||||||
|
|
@ -269,10 +257,7 @@ class FrontendNode(BaseModel):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def should_be_password(key: str, show: bool) -> bool:
|
def should_be_password(key: str, show: bool) -> bool:
|
||||||
"""Determines whether the field should be a password field."""
|
"""Determines whether the field should be a password field."""
|
||||||
return (
|
return any(text in key.lower() for text in {"password", "token", "api", "key"}) and show
|
||||||
any(text in key.lower() for text in {"password", "token", "api", "key"})
|
|
||||||
and show
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def should_be_multiline(key: str) -> bool:
|
def should_be_multiline(key: str) -> bool:
|
||||||
|
|
|
||||||
|
|
@ -80,9 +80,7 @@ class MemoryFrontendNode(FrontendNode):
|
||||||
field.show = True
|
field.show = True
|
||||||
field.advanced = False
|
field.advanced = False
|
||||||
field.value = ""
|
field.value = ""
|
||||||
field.info = (
|
field.info = INPUT_KEY_INFO if field.name == "input_key" else OUTPUT_KEY_INFO
|
||||||
INPUT_KEY_INFO if field.name == "input_key" else OUTPUT_KEY_INFO
|
|
||||||
)
|
|
||||||
|
|
||||||
if field.name == "memory_key":
|
if field.name == "memory_key":
|
||||||
field.value = "chat_history"
|
field.value = "chat_history"
|
||||||
|
|
|
||||||
|
|
@ -45,9 +45,7 @@ class Template(BaseModel):
|
||||||
"""Returns the field with the given name."""
|
"""Returns the field with the given name."""
|
||||||
field = next((field for field in self.fields if field.name == field_name), None)
|
field = next((field for field in self.fields if field.name == field_name), None)
|
||||||
if field is None:
|
if field is None:
|
||||||
raise ValueError(
|
raise ValueError(f"Field {field_name} not found in template {self.type_name}")
|
||||||
f"Field {field_name} not found in template {self.type_name}"
|
|
||||||
)
|
|
||||||
return field
|
return field
|
||||||
|
|
||||||
def update_field(self, field_name: str, field: TemplateField) -> None:
|
def update_field(self, field_name: str, field: TemplateField) -> None:
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,8 @@ def remove_ansi_escape_codes(text):
|
||||||
return re.sub(r"\x1b\[[0-9;]*[a-zA-Z]", "", text)
|
return re.sub(r"\x1b\[[0-9;]*[a-zA-Z]", "", text)
|
||||||
|
|
||||||
|
|
||||||
def build_template_from_function(
|
def build_template_from_function(name: str, type_to_loader_dict: Dict, add_function: bool = False):
|
||||||
name: str, type_to_loader_dict: Dict, add_function: bool = False
|
classes = [item.__annotations__["return"].__name__ for item in type_to_loader_dict.values()]
|
||||||
):
|
|
||||||
classes = [
|
|
||||||
item.__annotations__["return"].__name__ for item in type_to_loader_dict.values()
|
|
||||||
]
|
|
||||||
|
|
||||||
# Raise error if name is not in chains
|
# Raise error if name is not in chains
|
||||||
if name not in classes:
|
if name not in classes:
|
||||||
|
|
@ -41,10 +37,8 @@ def build_template_from_function(
|
||||||
for name_, value_ in value.__repr_args__():
|
for name_, value_ in value.__repr_args__():
|
||||||
if name_ == "default_factory":
|
if name_ == "default_factory":
|
||||||
try:
|
try:
|
||||||
variables[class_field_items]["default"] = (
|
variables[class_field_items]["default"] = get_default_factory(
|
||||||
get_default_factory(
|
module=_class.__base__.__module__, function=value_
|
||||||
module=_class.__base__.__module__, function=value_
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
variables[class_field_items]["default"] = None
|
variables[class_field_items]["default"] = None
|
||||||
|
|
@ -52,9 +46,7 @@ def build_template_from_function(
|
||||||
variables[class_field_items][name_] = value_
|
variables[class_field_items][name_] = value_
|
||||||
|
|
||||||
variables[class_field_items]["placeholder"] = (
|
variables[class_field_items]["placeholder"] = (
|
||||||
docs.params[class_field_items]
|
docs.params[class_field_items] if class_field_items in docs.params else ""
|
||||||
if class_field_items in docs.params
|
|
||||||
else ""
|
|
||||||
)
|
)
|
||||||
# Adding function to base classes to allow
|
# Adding function to base classes to allow
|
||||||
# the output to be a function
|
# the output to be a function
|
||||||
|
|
@ -69,9 +61,7 @@ def build_template_from_function(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def build_template_from_class(
|
def build_template_from_class(name: str, type_to_cls_dict: Dict, add_function: bool = False):
|
||||||
name: str, type_to_cls_dict: Dict, add_function: bool = False
|
|
||||||
):
|
|
||||||
classes = [item.__name__ for item in type_to_cls_dict.values()]
|
classes = [item.__name__ for item in type_to_cls_dict.values()]
|
||||||
|
|
||||||
# Raise error if name is not in chains
|
# Raise error if name is not in chains
|
||||||
|
|
@ -95,11 +85,9 @@ def build_template_from_class(
|
||||||
for name_, value_ in value.__repr_args__():
|
for name_, value_ in value.__repr_args__():
|
||||||
if name_ == "default_factory":
|
if name_ == "default_factory":
|
||||||
try:
|
try:
|
||||||
variables[class_field_items]["default"] = (
|
variables[class_field_items]["default"] = get_default_factory(
|
||||||
get_default_factory(
|
module=_class.__base__.__module__,
|
||||||
module=_class.__base__.__module__,
|
function=value_,
|
||||||
function=value_,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
variables[class_field_items]["default"] = None
|
variables[class_field_items]["default"] = None
|
||||||
|
|
@ -107,9 +95,7 @@ def build_template_from_class(
|
||||||
variables[class_field_items][name_] = value_
|
variables[class_field_items][name_] = value_
|
||||||
|
|
||||||
variables[class_field_items]["placeholder"] = (
|
variables[class_field_items]["placeholder"] = (
|
||||||
docs.params[class_field_items]
|
docs.params[class_field_items] if class_field_items in docs.params else ""
|
||||||
if class_field_items in docs.params
|
|
||||||
else ""
|
|
||||||
)
|
)
|
||||||
base_classes = get_base_classes(_class)
|
base_classes = get_base_classes(_class)
|
||||||
# Adding function to base classes to allow
|
# Adding function to base classes to allow
|
||||||
|
|
@ -141,9 +127,7 @@ def build_template_from_method(
|
||||||
|
|
||||||
# Check if the method exists in this class
|
# Check if the method exists in this class
|
||||||
if not hasattr(_class, method_name):
|
if not hasattr(_class, method_name):
|
||||||
raise ValueError(
|
raise ValueError(f"Method {method_name} not found in class {class_name}")
|
||||||
f"Method {method_name} not found in class {class_name}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Get the method
|
# Get the method
|
||||||
method = getattr(_class, method_name)
|
method = getattr(_class, method_name)
|
||||||
|
|
@ -162,14 +146,8 @@ def build_template_from_method(
|
||||||
"_type": _type,
|
"_type": _type,
|
||||||
**{
|
**{
|
||||||
name: {
|
name: {
|
||||||
"default": (
|
"default": (param.default if param.default != param.empty else None),
|
||||||
param.default if param.default != param.empty else None
|
"type": (param.annotation if param.annotation != param.empty else None),
|
||||||
),
|
|
||||||
"type": (
|
|
||||||
param.annotation
|
|
||||||
if param.annotation != param.empty
|
|
||||||
else None
|
|
||||||
),
|
|
||||||
"required": param.default == param.empty,
|
"required": param.default == param.empty,
|
||||||
}
|
}
|
||||||
for name, param in params.items()
|
for name, param in params.items()
|
||||||
|
|
@ -256,9 +234,7 @@ def sync_to_async(func):
|
||||||
return async_wrapper
|
return async_wrapper
|
||||||
|
|
||||||
|
|
||||||
def format_dict(
|
def format_dict(dictionary: Dict[str, Any], class_name: Optional[str] = None) -> Dict[str, Any]:
|
||||||
dictionary: Dict[str, Any], class_name: Optional[str] = None
|
|
||||||
) -> Dict[str, Any]:
|
|
||||||
"""
|
"""
|
||||||
Formats a dictionary by removing certain keys and modifying the
|
Formats a dictionary by removing certain keys and modifying the
|
||||||
values of other keys.
|
values of other keys.
|
||||||
|
|
@ -344,9 +320,7 @@ def check_list_type(_type: str, value: Dict[str, Any]) -> str:
|
||||||
The modified type string.
|
The modified type string.
|
||||||
"""
|
"""
|
||||||
if any(list_type in _type for list_type in ["List", "Sequence", "Set"]):
|
if any(list_type in _type for list_type in ["List", "Sequence", "Set"]):
|
||||||
_type = (
|
_type = _type.replace("List[", "").replace("Sequence[", "").replace("Set[", "")[:-1]
|
||||||
_type.replace("List[", "").replace("Sequence[", "").replace("Set[", "")[:-1]
|
|
||||||
)
|
|
||||||
value["list"] = True
|
value["list"] = True
|
||||||
else:
|
else:
|
||||||
value["list"] = False
|
value["list"] = False
|
||||||
|
|
@ -449,9 +423,7 @@ def set_headers_value(value: Dict[str, Any]) -> None:
|
||||||
value["value"] = """{"Authorization": "Bearer <token>"}"""
|
value["value"] = """{"Authorization": "Bearer <token>"}"""
|
||||||
|
|
||||||
|
|
||||||
def add_options_to_field(
|
def add_options_to_field(value: Dict[str, Any], class_name: Optional[str], key: str) -> None:
|
||||||
value: Dict[str, Any], class_name: Optional[str], key: str
|
|
||||||
) -> None:
|
|
||||||
"""
|
"""
|
||||||
Adds options to the field based on the class name and key.
|
Adds options to the field based on the class name and key.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,7 @@ def validate_code(code):
|
||||||
# Evaluate the function definition
|
# Evaluate the function definition
|
||||||
for node in tree.body:
|
for node in tree.body:
|
||||||
if isinstance(node, ast.FunctionDef):
|
if isinstance(node, ast.FunctionDef):
|
||||||
code_obj = compile(
|
code_obj = compile(ast.Module(body=[node], type_ignores=[]), "<string>", "exec")
|
||||||
ast.Module(body=[node], type_ignores=[]), "<string>", "exec"
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
exec(code_obj)
|
exec(code_obj)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -89,23 +87,15 @@ def execute_function(code, function_name, *args, **kwargs):
|
||||||
exec_globals,
|
exec_globals,
|
||||||
locals(),
|
locals(),
|
||||||
)
|
)
|
||||||
exec_globals[alias.asname or alias.name] = importlib.import_module(
|
exec_globals[alias.asname or alias.name] = importlib.import_module(alias.name)
|
||||||
alias.name
|
|
||||||
)
|
|
||||||
except ModuleNotFoundError as e:
|
except ModuleNotFoundError as e:
|
||||||
raise ModuleNotFoundError(
|
raise ModuleNotFoundError(f"Module {alias.name} not found. Please install it and try again.") from e
|
||||||
f"Module {alias.name} not found. Please install it and try again."
|
|
||||||
) from e
|
|
||||||
|
|
||||||
function_code = next(
|
function_code = next(
|
||||||
node
|
node for node in module.body if isinstance(node, ast.FunctionDef) and node.name == function_name
|
||||||
for node in module.body
|
|
||||||
if isinstance(node, ast.FunctionDef) and node.name == function_name
|
|
||||||
)
|
)
|
||||||
function_code.parent = None
|
function_code.parent = None
|
||||||
code_obj = compile(
|
code_obj = compile(ast.Module(body=[function_code], type_ignores=[]), "<string>", "exec")
|
||||||
ast.Module(body=[function_code], type_ignores=[]), "<string>", "exec"
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
exec(code_obj, exec_globals, locals())
|
exec(code_obj, exec_globals, locals())
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
|
@ -132,23 +122,15 @@ def create_function(code, function_name):
|
||||||
if isinstance(node, ast.Import):
|
if isinstance(node, ast.Import):
|
||||||
for alias in node.names:
|
for alias in node.names:
|
||||||
try:
|
try:
|
||||||
exec_globals[alias.asname or alias.name] = importlib.import_module(
|
exec_globals[alias.asname or alias.name] = importlib.import_module(alias.name)
|
||||||
alias.name
|
|
||||||
)
|
|
||||||
except ModuleNotFoundError as e:
|
except ModuleNotFoundError as e:
|
||||||
raise ModuleNotFoundError(
|
raise ModuleNotFoundError(f"Module {alias.name} not found. Please install it and try again.") from e
|
||||||
f"Module {alias.name} not found. Please install it and try again."
|
|
||||||
) from e
|
|
||||||
|
|
||||||
function_code = next(
|
function_code = next(
|
||||||
node
|
node for node in module.body if isinstance(node, ast.FunctionDef) and node.name == function_name
|
||||||
for node in module.body
|
|
||||||
if isinstance(node, ast.FunctionDef) and node.name == function_name
|
|
||||||
)
|
)
|
||||||
function_code.parent = None
|
function_code.parent = None
|
||||||
code_obj = compile(
|
code_obj = compile(ast.Module(body=[function_code], type_ignores=[]), "<string>", "exec")
|
||||||
ast.Module(body=[function_code], type_ignores=[]), "<string>", "exec"
|
|
||||||
)
|
|
||||||
with contextlib.suppress(Exception):
|
with contextlib.suppress(Exception):
|
||||||
exec(code_obj, exec_globals, locals())
|
exec(code_obj, exec_globals, locals())
|
||||||
exec_globals[function_name] = locals()[function_name]
|
exec_globals[function_name] = locals()[function_name]
|
||||||
|
|
@ -210,13 +192,9 @@ def prepare_global_scope(code, module):
|
||||||
if isinstance(node, ast.Import):
|
if isinstance(node, ast.Import):
|
||||||
for alias in node.names:
|
for alias in node.names:
|
||||||
try:
|
try:
|
||||||
exec_globals[alias.asname or alias.name] = importlib.import_module(
|
exec_globals[alias.asname or alias.name] = importlib.import_module(alias.name)
|
||||||
alias.name
|
|
||||||
)
|
|
||||||
except ModuleNotFoundError as e:
|
except ModuleNotFoundError as e:
|
||||||
raise ModuleNotFoundError(
|
raise ModuleNotFoundError(f"Module {alias.name} not found. Please install it and try again.") from e
|
||||||
f"Module {alias.name} not found. Please install it and try again."
|
|
||||||
) from e
|
|
||||||
elif isinstance(node, ast.ImportFrom) and node.module is not None:
|
elif isinstance(node, ast.ImportFrom) and node.module is not None:
|
||||||
try:
|
try:
|
||||||
imported_module = importlib.import_module(node.module)
|
imported_module = importlib.import_module(node.module)
|
||||||
|
|
@ -237,11 +215,7 @@ def extract_class_code(module, class_name):
|
||||||
:param class_name: Name of the class to extract
|
:param class_name: Name of the class to extract
|
||||||
:return: AST node of the specified class
|
:return: AST node of the specified class
|
||||||
"""
|
"""
|
||||||
class_code = next(
|
class_code = next(node for node in module.body if isinstance(node, ast.ClassDef) and node.name == class_name)
|
||||||
node
|
|
||||||
for node in module.body
|
|
||||||
if isinstance(node, ast.ClassDef) and node.name == class_name
|
|
||||||
)
|
|
||||||
|
|
||||||
class_code.parent = None
|
class_code.parent = None
|
||||||
return class_code
|
return class_code
|
||||||
|
|
@ -254,9 +228,7 @@ def compile_class_code(class_code):
|
||||||
:param class_code: AST node of the class
|
:param class_code: AST node of the class
|
||||||
:return: Compiled code object of the class
|
:return: Compiled code object of the class
|
||||||
"""
|
"""
|
||||||
code_obj = compile(
|
code_obj = compile(ast.Module(body=[class_code], type_ignores=[]), "<string>", "exec")
|
||||||
ast.Module(body=[class_code], type_ignores=[]), "<string>", "exec"
|
|
||||||
)
|
|
||||||
return code_obj
|
return code_obj
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -300,9 +272,7 @@ def get_default_imports(code_string):
|
||||||
langflow_imports = list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys())
|
langflow_imports = list(CUSTOM_COMPONENT_SUPPORTED_TYPES.keys())
|
||||||
necessary_imports = find_names_in_code(code_string, langflow_imports)
|
necessary_imports = find_names_in_code(code_string, langflow_imports)
|
||||||
langflow_module = importlib.import_module("langflow.field_typing")
|
langflow_module = importlib.import_module("langflow.field_typing")
|
||||||
default_imports.update(
|
default_imports.update({name: getattr(langflow_module, name) for name in necessary_imports})
|
||||||
{name: getattr(langflow_module, name) for name in necessary_imports}
|
|
||||||
)
|
|
||||||
|
|
||||||
return default_imports
|
return default_imports
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,16 @@ import {
|
||||||
OUTPUT_HANDLER_HOVER,
|
OUTPUT_HANDLER_HOVER,
|
||||||
TOOLTIP_EMPTY,
|
TOOLTIP_EMPTY,
|
||||||
} from "../../../../constants/constants";
|
} from "../../../../constants/constants";
|
||||||
import { postCustomComponentUpdate } from "../../../../controllers/API";
|
|
||||||
import useAlertStore from "../../../../stores/alertStore";
|
import useAlertStore from "../../../../stores/alertStore";
|
||||||
import useFlowStore from "../../../../stores/flowStore";
|
import useFlowStore from "../../../../stores/flowStore";
|
||||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||||
import { useTypesStore } from "../../../../stores/typesStore";
|
import { useTypesStore } from "../../../../stores/typesStore";
|
||||||
import { APIClassType } from "../../../../types/api";
|
import { APIClassType, ResponseErrorTypeAPI } from "../../../../types/api";
|
||||||
import { ParameterComponentType } from "../../../../types/components";
|
import { ParameterComponentType } from "../../../../types/components";
|
||||||
import { NodeDataType } from "../../../../types/flow";
|
import {
|
||||||
|
handleUpdateValues,
|
||||||
|
throttledHandleUpdateValues,
|
||||||
|
} from "../../../../utils/parameterUtils";
|
||||||
import {
|
import {
|
||||||
convertObjToArray,
|
convertObjToArray,
|
||||||
convertValuesToNumbers,
|
convertValuesToNumbers,
|
||||||
|
|
@ -86,81 +88,70 @@ export default function ParameterComponent({
|
||||||
|
|
||||||
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||||
|
|
||||||
const handleUpdateValues = async (
|
|
||||||
name: string,
|
|
||||||
data: NodeDataType,
|
|
||||||
delayAnimation: boolean = true
|
|
||||||
) => {
|
|
||||||
setIsLoading(true);
|
|
||||||
const code = data.node?.template["code"]?.value;
|
|
||||||
if (!code) {
|
|
||||||
console.error("Code not found in the template");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await postCustomComponentUpdate(
|
|
||||||
code,
|
|
||||||
name,
|
|
||||||
data.node?.template[name]?.value
|
|
||||||
)
|
|
||||||
.then((res) => {
|
|
||||||
if (res.status === 200 && data.node?.template) {
|
|
||||||
setNode(data.id, (oldNode) => {
|
|
||||||
let newNode = cloneDeep(oldNode);
|
|
||||||
|
|
||||||
newNode.data = {
|
|
||||||
...newNode.data,
|
|
||||||
};
|
|
||||||
|
|
||||||
newNode.data.node.template = res.data.template;
|
|
||||||
|
|
||||||
return newNode;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error("Error occurred while updating the node:", error);
|
|
||||||
setErrorData({
|
|
||||||
title: "Error while updating the Component",
|
|
||||||
list: [error.response.data.detail.error ?? "Unknown error"],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
renderTooltips();
|
|
||||||
if (delayAnimation) {
|
|
||||||
try {
|
|
||||||
// Wait for at least 500 milliseconds
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
||||||
// Continue with the request
|
|
||||||
// If the request takes longer than 500 milliseconds, it will not wait an additional 500 milliseconds
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error occurred while waiting for refresh:", error);
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
} else setIsLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function fetchData() {
|
async function fetchData() {
|
||||||
if (
|
if (
|
||||||
data.node?.template[name]?.refresh &&
|
data.node?.template[name]?.refresh &&
|
||||||
Object.keys(data.node?.template[name]?.options ?? {}).length === 0
|
// options can be undefined but not an empty array
|
||||||
|
(data.node?.template[name]?.options?.length ?? 0) === 0
|
||||||
) {
|
) {
|
||||||
handleUpdateValues(name, data, false);
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
let newTemplate = await handleUpdateValues(name, data);
|
||||||
|
if (newTemplate) {
|
||||||
|
setNode(data.id, (oldNode) => {
|
||||||
|
let newNode = cloneDeep(oldNode);
|
||||||
|
newNode.data = {
|
||||||
|
...newNode.data,
|
||||||
|
};
|
||||||
|
newNode.data.node.template = newTemplate;
|
||||||
|
return newNode;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
let responseError = error as ResponseErrorTypeAPI;
|
||||||
|
setErrorData({
|
||||||
|
title: "Error while updating the Component",
|
||||||
|
list: [responseError.response.data.detail.error ?? "Unknown error"],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setIsLoading(false);
|
||||||
|
renderTooltips();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fetchData();
|
fetchData();
|
||||||
}, []);
|
}, []);
|
||||||
const handleOnNewValue = (
|
const handleOnNewValue = async (
|
||||||
newValue: string | string[] | boolean | Object[]
|
newValue: string | string[] | boolean | Object[]
|
||||||
): void => {
|
): Promise<void> => {
|
||||||
if (data.node!.template[name].value !== newValue) {
|
if (data.node!.template[name].value !== newValue) {
|
||||||
takeSnapshot();
|
takeSnapshot();
|
||||||
}
|
}
|
||||||
|
const shouldUpdate =
|
||||||
|
data.node?.template[name].refresh &&
|
||||||
|
data.node!.template[name].value !== newValue;
|
||||||
|
console.log("shouldUpdate", shouldUpdate);
|
||||||
|
console.log(
|
||||||
|
"data.node!.template[name].value",
|
||||||
|
data.node!.template[name].value
|
||||||
|
);
|
||||||
|
console.log("newValue", newValue);
|
||||||
data.node!.template[name].value = newValue; // necessary to enable ctrl+z inside the input
|
data.node!.template[name].value = newValue; // necessary to enable ctrl+z inside the input
|
||||||
|
let newTemplate;
|
||||||
|
if (shouldUpdate) {
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
newTemplate = await throttledHandleUpdateValues(name, data);
|
||||||
|
} catch (error) {
|
||||||
|
let responseError = error as ResponseErrorTypeAPI;
|
||||||
|
setErrorData({
|
||||||
|
title: "Error while updating the Component",
|
||||||
|
list: [responseError.response.data.detail.error ?? "Unknown error"],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setIsLoading(false);
|
||||||
|
// this de
|
||||||
|
}
|
||||||
setNode(data.id, (oldNode) => {
|
setNode(data.id, (oldNode) => {
|
||||||
let newNode = cloneDeep(oldNode);
|
let newNode = cloneDeep(oldNode);
|
||||||
|
|
||||||
|
|
@ -168,7 +159,9 @@ export default function ParameterComponent({
|
||||||
...newNode.data,
|
...newNode.data,
|
||||||
};
|
};
|
||||||
|
|
||||||
newNode.data.node.template[name].value = newValue;
|
if (data.node?.template[name].refresh && newTemplate) {
|
||||||
|
newNode.data.node.template = newTemplate;
|
||||||
|
} else newNode.data.node.template[name].value = newValue;
|
||||||
|
|
||||||
return newNode;
|
return newNode;
|
||||||
});
|
});
|
||||||
|
|
@ -300,11 +293,14 @@ export default function ParameterComponent({
|
||||||
refHtml.current = <span>{TOOLTIP_EMPTY}</span>;
|
refHtml.current = <span>{TOOLTIP_EMPTY}</span>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// If optionalHandle is an empty list, then it is not an optional handle
|
||||||
|
if (optionalHandle && optionalHandle.length === 0) {
|
||||||
|
optionalHandle = null;
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
renderTooltips();
|
renderTooltips();
|
||||||
}, [tooltipTitle, flow]);
|
}, [tooltipTitle, flow]);
|
||||||
|
|
||||||
return !showNode ? (
|
return !showNode ? (
|
||||||
left && LANGFLOW_SUPPORTED_TYPES.has(type ?? "") && !optionalHandle ? (
|
left && LANGFLOW_SUPPORTED_TYPES.has(type ?? "") && !optionalHandle ? (
|
||||||
<></>
|
<></>
|
||||||
|
|
@ -451,7 +447,9 @@ export default function ParameterComponent({
|
||||||
{data.node?.template[name].list ? (
|
{data.node?.template[name].list ? (
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
(data.node?.template[name].refresh ? "w-5/6 " : "") +
|
// Commenting this out until we have a better
|
||||||
|
// way to display
|
||||||
|
// (data.node?.template[name].refresh ? "w-5/6 " : "") +
|
||||||
"flex-grow"
|
"flex-grow"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -465,7 +463,7 @@ export default function ParameterComponent({
|
||||||
}
|
}
|
||||||
onChange={handleOnNewValue}
|
onChange={handleOnNewValue}
|
||||||
/>
|
/>
|
||||||
{data.node?.template[name].refresh && (
|
{/* {data.node?.template[name].refresh && (
|
||||||
<div className="w-1/6">
|
<div className="w-1/6">
|
||||||
<RefreshButton
|
<RefreshButton
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
|
@ -477,7 +475,7 @@ export default function ParameterComponent({
|
||||||
id={"refresh-button-" + name}
|
id={"refresh-button-" + name}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
) : data.node?.template[name].multiline ? (
|
) : data.node?.template[name].multiline ? (
|
||||||
<TextAreaComponent
|
<TextAreaComponent
|
||||||
|
|
@ -489,7 +487,12 @@ export default function ParameterComponent({
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="mt-2 flex w-full items-center">
|
<div className="mt-2 flex w-full items-center">
|
||||||
<div className="w-5/6 flex-grow">
|
<div
|
||||||
|
className={
|
||||||
|
"flex-grow " +
|
||||||
|
(data.node?.template[name].refresh ? "w-5/6" : "")
|
||||||
|
}
|
||||||
|
>
|
||||||
<InputComponent
|
<InputComponent
|
||||||
id={"input-" + name}
|
id={"input-" + name}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
@ -648,8 +651,14 @@ export default function ParameterComponent({
|
||||||
onChange={(newValue) => {
|
onChange={(newValue) => {
|
||||||
const valueToNumbers = convertValuesToNumbers(newValue);
|
const valueToNumbers = convertValuesToNumbers(newValue);
|
||||||
setErrorDuplicateKey(hasDuplicateKeys(valueToNumbers));
|
setErrorDuplicateKey(hasDuplicateKeys(valueToNumbers));
|
||||||
handleOnNewValue(valueToNumbers);
|
// if data.node?.template[name].list is true, then the value is an array of objects
|
||||||
|
// else we need to get the first object of the array
|
||||||
|
|
||||||
|
if (data.node?.template[name].list) {
|
||||||
|
handleOnNewValue(valueToNumbers);
|
||||||
|
} else handleOnNewValue(valueToNumbers[0]);
|
||||||
}}
|
}}
|
||||||
|
isList={data.node?.template[name].list ?? false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,7 @@ export default function AccordionComponent({
|
||||||
{trigger}
|
{trigger}
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
<div className="AccordionContent flex flex-col">
|
<div className="AccordionContent flex flex-col">{children}</div>
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ export default function CrashErrorComponent({
|
||||||
<CardContent className="grid">
|
<CardContent className="grid">
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
Please report errors with detailed tracebacks on the{" "}
|
Please report errors with detailed tracebacks on the{" "}
|
||||||
<a
|
<a
|
||||||
href="https://github.com/logspace-ai/langflow/issues"
|
href="https://github.com/logspace-ai/langflow/issues"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|
@ -37,7 +37,7 @@ export default function CrashErrorComponent({
|
||||||
</a>{" "}
|
</a>{" "}
|
||||||
page.
|
page.
|
||||||
<br></br>
|
<br></br>
|
||||||
Thank you!
|
Thank you!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
|
||||||
|
|
@ -1,54 +1,33 @@
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { getComponent, postLikeComponent } from "../../controllers/API";
|
|
||||||
import DeleteConfirmationModal from "../../modals/DeleteConfirmationModal";
|
|
||||||
import useAlertStore from "../../stores/alertStore";
|
|
||||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
|
||||||
import { useStoreStore } from "../../stores/storeStore";
|
|
||||||
import { storeComponent } from "../../types/store";
|
|
||||||
import cloneFLowWithParent from "../../utils/storeUtils";
|
|
||||||
import { cn } from "../../utils/utils";
|
|
||||||
import ShadTooltip from "../ShadTooltipComponent";
|
|
||||||
import IconComponent from "../genericIconComponent";
|
|
||||||
import { Badge } from "../ui/badge";
|
|
||||||
import { Button } from "../ui/button";
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardFooter,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "../ui/card";
|
|
||||||
import { FlowType } from "../../types/flow";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
|
import { cn } from "../../utils/utils";
|
||||||
|
import IconComponent from "../genericIconComponent";
|
||||||
|
import { Card, CardContent } from "../ui/card";
|
||||||
|
|
||||||
export default function NewFlowCardComponent({
|
export default function NewFlowCardComponent({}: {}) {
|
||||||
}: {
|
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||||
}) {
|
const navigate = useNavigate();
|
||||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
|
className={cn(
|
||||||
className={cn(
|
"group relative flex h-48 w-2/6 flex-col justify-between overflow-hidden transition-all hover:shadow-md"
|
||||||
"group relative h-48 w-2/6 flex flex-col justify-between overflow-hidden transition-all hover:shadow-md",
|
)}
|
||||||
)}
|
>
|
||||||
|
<CardContent className="flex h-full w-full items-center justify-center align-middle">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
addFlow(true).then((id) => {
|
||||||
|
navigate("/flow/" + id);
|
||||||
|
});
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<CardContent className="w-full h-full flex align-middle items-center justify-center">
|
<IconComponent
|
||||||
<button onClick={() => {
|
className={cn("h-12 w-12 text-muted-foreground")}
|
||||||
addFlow(true).then((id) => {
|
name="PlusCircle"
|
||||||
navigate("/flow/" + id);
|
/>
|
||||||
});
|
</button>
|
||||||
}}>
|
</CardContent>
|
||||||
<IconComponent
|
</Card>
|
||||||
className={cn(
|
);
|
||||||
"h-12 w-12 text-muted-foreground",
|
|
||||||
)}
|
|
||||||
name="PlusCircle"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ export default function CodeTabsComponent({
|
||||||
<Tabs
|
<Tabs
|
||||||
value={activeTab}
|
value={activeTab}
|
||||||
className={
|
className={
|
||||||
"api-modal-tabs m-0 inset-0 " +
|
"api-modal-tabs inset-0 m-0 " +
|
||||||
(isMessage ? "dark " : "") +
|
(isMessage ? "dark " : "") +
|
||||||
(dark && isMessage ? "bg-background" : "")
|
(dark && isMessage ? "bg-background" : "")
|
||||||
}
|
}
|
||||||
|
|
@ -195,7 +195,7 @@ export default function CodeTabsComponent({
|
||||||
<SyntaxHighlighter
|
<SyntaxHighlighter
|
||||||
language={tab.language}
|
language={tab.language}
|
||||||
style={oneDark}
|
style={oneDark}
|
||||||
className="mt-0 h-full overflow-auto custom-scroll rounded-sm text-left"
|
className="mt-0 h-full overflow-auto rounded-sm text-left custom-scroll"
|
||||||
>
|
>
|
||||||
{tab.code}
|
{tab.code}
|
||||||
</SyntaxHighlighter>
|
</SyntaxHighlighter>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,10 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { getComponent, postLikeComponent } from "../../controllers/API";
|
|
||||||
import DeleteConfirmationModal from "../../modals/DeleteConfirmationModal";
|
|
||||||
import useAlertStore from "../../stores/alertStore";
|
|
||||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
import { useStoreStore } from "../../stores/storeStore";
|
import { FlowType } from "../../types/flow";
|
||||||
import { storeComponent } from "../../types/store";
|
import { updateIds } from "../../utils/reactflowUtils";
|
||||||
import cloneFLowWithParent from "../../utils/storeUtils";
|
|
||||||
import { cn } from "../../utils/utils";
|
import { cn } from "../../utils/utils";
|
||||||
import ShadTooltip from "../ShadTooltipComponent";
|
import ShadTooltip from "../ShadTooltipComponent";
|
||||||
import IconComponent from "../genericIconComponent";
|
import IconComponent from "../genericIconComponent";
|
||||||
import { Badge } from "../ui/badge";
|
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
|
|
@ -18,20 +13,17 @@ import {
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "../ui/card";
|
} from "../ui/card";
|
||||||
import { FlowType } from "../../types/flow";
|
|
||||||
import { updateIds } from "../../utils/reactflowUtils";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
|
|
||||||
export default function CollectionCardComponent({
|
export default function CollectionCardComponent({
|
||||||
flow,
|
flow,
|
||||||
}: {
|
}: {
|
||||||
flow: FlowType;
|
flow: FlowType;
|
||||||
authorized?: boolean;
|
authorized?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const emojiRegex = /\p{Emoji}/u;
|
const emojiRegex = /\p{Emoji}/u;
|
||||||
const isEmoji= (str:string)=> emojiRegex.test(str);
|
const isEmoji = (str: string) => emojiRegex.test(str);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
|
|
@ -43,20 +35,17 @@ export default function CollectionCardComponent({
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div>
|
<div>
|
||||||
<CardTitle className="flex w-full items-center justify-between gap-3 text-xl">
|
<CardTitle className="flex w-full items-center justify-between gap-3 text-xl">
|
||||||
{flow.icon && isEmoji(flow.icon) && (
|
{flow.icon && (
|
||||||
<div className="rounded-md p-2 flex align-middle items-center justify-center" style={{backgroundColor:flow.icon_bg_color}}>
|
<div className="flex-shrink-0 h-7 w-7">
|
||||||
|
{flow.icon}
|
||||||
<div className="pl-0.5 h-7 w-7">
|
|
||||||
{flow.icon}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
{(!flow.icon || !isEmoji(flow.icon)) && <IconComponent
|
{!flow.icon && <IconComponent
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex-shrink-0 h-7 w-7 text-flow-icon",
|
"flex-shrink-0 h-7 w-7 text-flow-icon",
|
||||||
)}
|
)}
|
||||||
name={flow.icon || "Group"}
|
name="Group"
|
||||||
/>}
|
/>}
|
||||||
<ShadTooltip content={flow.name}>
|
<ShadTooltip content={flow.name}>
|
||||||
<div className="w-full truncate">{flow.name}</div>
|
<div className="w-full truncate">{flow.name}</div>
|
||||||
|
|
@ -73,12 +62,11 @@ export default function CollectionCardComponent({
|
||||||
|
|
||||||
<CardFooter>
|
<CardFooter>
|
||||||
<div className="flex w-full items-center justify-between gap-2">
|
<div className="flex w-full items-center justify-between gap-2">
|
||||||
<div className="flex w-full justify-end flex-wrap gap-2">
|
<div className="flex w-full flex-wrap justify-end gap-2">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateIds(flow.data!)
|
updateIds(flow.data!);
|
||||||
addFlow(true, flow).then((id) => {
|
addFlow(true, flow).then((id) => {
|
||||||
|
|
||||||
navigate("/flow/" + id);
|
navigate("/flow/" + id);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,11 @@ export default function KeypairListComponent({
|
||||||
disabled,
|
disabled,
|
||||||
editNode = false,
|
editNode = false,
|
||||||
duplicateKey,
|
duplicateKey,
|
||||||
|
isList = true,
|
||||||
}: KeyPairListComponentType): JSX.Element {
|
}: KeyPairListComponentType): JSX.Element {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled && value.length > 0 && value[0] !== "") {
|
if (disabled && value.length > 0 && value[0] !== "") {
|
||||||
onChange([""]);
|
onChange([{ "": "" }]);
|
||||||
}
|
}
|
||||||
}, [disabled]);
|
}, [disabled]);
|
||||||
|
|
||||||
|
|
@ -79,6 +80,7 @@ export default function KeypairListComponent({
|
||||||
: "keypair" + (index + 100).toString()
|
: "keypair" + (index + 100).toString()
|
||||||
}
|
}
|
||||||
type="text"
|
type="text"
|
||||||
|
disabled={disabled}
|
||||||
value={obj[key]}
|
value={obj[key]}
|
||||||
className={editNode ? "input-edit-node" : ""}
|
className={editNode ? "input-edit-node" : ""}
|
||||||
placeholder="Type a value..."
|
placeholder="Type a value..."
|
||||||
|
|
@ -87,7 +89,7 @@ export default function KeypairListComponent({
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{index === ref.current.length - 1 ? (
|
{isList && index === ref.current.length - 1 ? (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
let newInputList = _.cloneDeep(ref.current);
|
let newInputList = _.cloneDeep(ref.current);
|
||||||
|
|
@ -110,7 +112,7 @@ export default function KeypairListComponent({
|
||||||
className={"h-4 w-4 hover:text-accent-foreground"}
|
className={"h-4 w-4 hover:text-accent-foreground"}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : isList ? (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
let newInputList = _.cloneDeep(ref.current);
|
let newInputList = _.cloneDeep(ref.current);
|
||||||
|
|
@ -133,6 +135,8 @@ export default function KeypairListComponent({
|
||||||
className="h-4 w-4 hover:text-status-red"
|
className="h-4 w-4 hover:text-status-red"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ export default function ChatMessage({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!chat.isSend ? (
|
{!chat.isSend ? (
|
||||||
<div className="form-modal-chat-text-position flex-grow min-w-96">
|
<div className="form-modal-chat-text-position min-w-96 flex-grow">
|
||||||
<div className="form-modal-chat-text">
|
<div className="form-modal-chat-text">
|
||||||
{hidden && chat.thought && chat.thought !== "" && (
|
{hidden && chat.thought && chat.thought !== "" && (
|
||||||
<div
|
<div
|
||||||
|
|
@ -155,9 +155,9 @@ export default function ChatMessage({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
|
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
|
||||||
<div className="w-full flex flex-col">
|
<div className="flex w-full flex-col">
|
||||||
<div className="w-full flex flex-col dark:text-white">
|
<div className="flex w-full flex-col dark:text-white">
|
||||||
<div className="w-full flex flex-col">
|
<div className="flex w-full flex-col">
|
||||||
{useMemo(
|
{useMemo(
|
||||||
() =>
|
() =>
|
||||||
chatMessage === "" && lockChat ? (
|
chatMessage === "" && lockChat ? (
|
||||||
|
|
@ -169,7 +169,7 @@ export default function ChatMessage({
|
||||||
<Markdown
|
<Markdown
|
||||||
remarkPlugins={[remarkGfm, remarkMath]}
|
remarkPlugins={[remarkGfm, remarkMath]}
|
||||||
rehypePlugins={[rehypeMathjax]}
|
rehypePlugins={[rehypeMathjax]}
|
||||||
className="markdown flex flex-col prose text-primary word-break-break-word
|
className="markdown prose flex flex-col text-primary word-break-break-word
|
||||||
dark:prose-invert"
|
dark:prose-invert"
|
||||||
components={{
|
components={{
|
||||||
pre({ node, ...props }) {
|
pre({ node, ...props }) {
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ const DialogContent = React.forwardRef<
|
||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col fixed left-[50%] top-[50%] z-50 w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full",
|
"fixed left-[50%] top-[50%] z-50 flex w-full max-w-lg translate-x-[-50%] translate-y-[-50%] flex-col gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
|
|
@ -58,4 +58,3 @@ export const FLOW_BUILD_SUCCESS_ALERT = `Flow built successfully`;
|
||||||
export const SAVE_SUCCESS_ALERT = "Changes saved successfully!";
|
export const SAVE_SUCCESS_ALERT = "Changes saved successfully!";
|
||||||
|
|
||||||
// Generic Node
|
// Generic Node
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -737,4 +737,5 @@ export const PRIORITY_SIDEBAR_ORDER = [
|
||||||
"prompt",
|
"prompt",
|
||||||
"models",
|
"models",
|
||||||
"helpers",
|
"helpers",
|
||||||
|
"experimental",
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,7 @@ export default function DeleteConfirmationModal({
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<span>
|
<span>
|
||||||
Confirm deletion of {description ?? "component"}?
|
Confirm deletion of {description ?? "component"}?<br></br>
|
||||||
<br></br>
|
|
||||||
Note: This action is irreversible.
|
Note: This action is irreversible.
|
||||||
</span>
|
</span>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
|
|
|
||||||
|
|
@ -165,8 +165,19 @@ const EditNodeModal = forwardRef(
|
||||||
)
|
)
|
||||||
) ?? false;
|
) ?? false;
|
||||||
return (
|
return (
|
||||||
<TableRow key={index} className={"h-10 " + ((templateParam==="code" && myData.node?.template[templateParam].type==="code") || (templateParam.includes("code") && myData.node?.template[templateParam].proxy) ? " hidden " : "")
|
<TableRow
|
||||||
}>
|
key={index}
|
||||||
|
className={
|
||||||
|
"h-10 " +
|
||||||
|
((templateParam === "code" &&
|
||||||
|
myData.node?.template[templateParam].type ===
|
||||||
|
"code") ||
|
||||||
|
(templateParam.includes("code") &&
|
||||||
|
myData.node?.template[templateParam].proxy)
|
||||||
|
? " hidden "
|
||||||
|
: "")
|
||||||
|
}
|
||||||
|
>
|
||||||
<TableCell className="truncate p-0 text-center text-sm text-foreground sm:px-3">
|
<TableCell className="truncate p-0 text-center text-sm text-foreground sm:px-3">
|
||||||
<ShadTooltip
|
<ShadTooltip
|
||||||
content={
|
content={
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import { postCustomComponent, postValidateCode } from "../../controllers/API";
|
||||||
import useAlertStore from "../../stores/alertStore";
|
import useAlertStore from "../../stores/alertStore";
|
||||||
import { useDarkStore } from "../../stores/darkStore";
|
import { useDarkStore } from "../../stores/darkStore";
|
||||||
import useFlowStore from "../../stores/flowStore";
|
import useFlowStore from "../../stores/flowStore";
|
||||||
|
import { CodeErrorDataTypeAPI } from "../../types/api";
|
||||||
import { codeAreaModalPropsType } from "../../types/components";
|
import { codeAreaModalPropsType } from "../../types/components";
|
||||||
import BaseModal from "../baseModal";
|
import BaseModal from "../baseModal";
|
||||||
|
|
||||||
|
|
@ -50,7 +51,7 @@ export default function CodeAreaModal({
|
||||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||||
const [error, setError] = useState<{
|
const [error, setError] = useState<{
|
||||||
detail: { error: string | undefined; traceback: string | undefined };
|
detail: CodeErrorDataTypeAPI;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -179,7 +179,7 @@ export default function ShareModal({
|
||||||
</span>
|
</span>
|
||||||
<br></br>
|
<br></br>
|
||||||
<span className=" text-xs text-destructive ">
|
<span className=" text-xs text-destructive ">
|
||||||
Note: This action is irreversible.
|
Note: This action is irreversible.
|
||||||
</span>
|
</span>
|
||||||
</ConfirmationModal.Content>
|
</ConfirmationModal.Content>
|
||||||
</ConfirmationModal>
|
</ConfirmationModal>
|
||||||
|
|
@ -204,7 +204,9 @@ export default function ShareModal({
|
||||||
{children ? children : <></>}
|
{children ? children : <></>}
|
||||||
</BaseModal.Trigger>
|
</BaseModal.Trigger>
|
||||||
<BaseModal.Header
|
<BaseModal.Header
|
||||||
description={`Publish ${is_component ? "your component" : "workflow"} to the Langflow Store.`}
|
description={`Publish ${
|
||||||
|
is_component ? "your component" : "workflow"
|
||||||
|
} to the Langflow Store.`}
|
||||||
>
|
>
|
||||||
<span className="pr-2">Share</span>
|
<span className="pr-2">Share</span>
|
||||||
<IconComponent
|
<IconComponent
|
||||||
|
|
@ -239,7 +241,8 @@ export default function ShareModal({
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<span className=" text-xs text-destructive ">
|
<span className=" text-xs text-destructive ">
|
||||||
<b>Attention:</b> API keys in specified fields are automatically removed upon sharing.
|
<b>Attention:</b> API keys in specified fields are automatically
|
||||||
|
removed upon sharing.
|
||||||
</span>
|
</span>
|
||||||
</BaseModal.Content>
|
</BaseModal.Content>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ import {
|
||||||
import useAlertStore from "../../../../stores/alertStore";
|
import useAlertStore from "../../../../stores/alertStore";
|
||||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||||
import { FlowType } from "../../../../types/flow";
|
import { FlowType } from "../../../../types/flow";
|
||||||
import { STARTER_FOLDER_NAME } from "../../../../constants/constants";
|
|
||||||
|
|
||||||
export default function ComponentsComponent({
|
export default function ComponentsComponent({
|
||||||
is_component = true,
|
is_component = true,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ import { Group, ToyBrick } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||||
import DropdownButton from "../../components/DropdownButtonComponent";
|
import DropdownButton from "../../components/DropdownButtonComponent";
|
||||||
|
import NewFlowCardComponent from "../../components/NewFlowCardComponent";
|
||||||
|
import ExampleCardComponent from "../../components/exampleComponent";
|
||||||
import IconComponent from "../../components/genericIconComponent";
|
import IconComponent from "../../components/genericIconComponent";
|
||||||
import PageLayout from "../../components/pageLayout";
|
import PageLayout from "../../components/pageLayout";
|
||||||
import SidebarNav from "../../components/sidebarComponent";
|
import SidebarNav from "../../components/sidebarComponent";
|
||||||
|
|
@ -11,12 +13,10 @@ import {
|
||||||
MY_COLLECTION_DESC,
|
MY_COLLECTION_DESC,
|
||||||
USER_PROJECTS_HEADER,
|
USER_PROJECTS_HEADER,
|
||||||
} from "../../constants/constants";
|
} from "../../constants/constants";
|
||||||
|
import BaseModal from "../../modals/baseModal";
|
||||||
import useAlertStore from "../../stores/alertStore";
|
import useAlertStore from "../../stores/alertStore";
|
||||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||||
import { downloadFlows } from "../../utils/reactflowUtils";
|
import { downloadFlows } from "../../utils/reactflowUtils";
|
||||||
import BaseModal from "../../modals/baseModal";
|
|
||||||
import ExampleCardComponent from "../../components/exampleComponent";
|
|
||||||
import NewFlowCardComponent from "../../components/NewFlowCardComponent";
|
|
||||||
export default function HomePage(): JSX.Element {
|
export default function HomePage(): JSX.Element {
|
||||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||||
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
|
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
|
||||||
|
|
@ -41,8 +41,9 @@ export default function HomePage(): JSX.Element {
|
||||||
})
|
})
|
||||||
.then((id) => {
|
.then((id) => {
|
||||||
setSuccessData({
|
setSuccessData({
|
||||||
title: `${is_component ? "Component" : "Flow"
|
title: `${
|
||||||
} uploaded successfully`,
|
is_component ? "Component" : "Flow"
|
||||||
|
} uploaded successfully`,
|
||||||
});
|
});
|
||||||
if (!is_component) navigate("/flow/" + id);
|
if (!is_component) navigate("/flow/" + id);
|
||||||
})
|
})
|
||||||
|
|
@ -117,24 +118,25 @@ export default function HomePage(): JSX.Element {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<BaseModal open={openModal} setOpen={setOpenModal}>
|
<BaseModal open={openModal} setOpen={setOpenModal}>
|
||||||
<BaseModal.Header description={"Select a template or start from scratch"}>
|
<BaseModal.Header
|
||||||
|
description={"Select a template or start from scratch"}
|
||||||
|
>
|
||||||
<span className="pr-2" data-testid="modal-title">
|
<span className="pr-2" data-testid="modal-title">
|
||||||
Create a New Flow
|
Create a New Flow
|
||||||
</span>
|
</span>
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name="Group"
|
name="Group"
|
||||||
className="h-6 w-6 text-primary stroke-2 "
|
className="h-6 w-6 stroke-2 text-primary "
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</BaseModal.Header>
|
</BaseModal.Header>
|
||||||
<BaseModal.Content>
|
<BaseModal.Content>
|
||||||
<div className="flex flex-wrap w-full h-full p-4 gap-3 overflow-auto custom-scroll">
|
<div className="flex h-full w-full flex-wrap gap-3 overflow-auto p-4 custom-scroll">
|
||||||
{examples.map((example, idx) => {
|
{examples.map((example, idx) => {
|
||||||
return(
|
return <ExampleCardComponent key={idx} flow={example} />;
|
||||||
<ExampleCardComponent key={idx} flow={example} />)
|
|
||||||
})}
|
})}
|
||||||
<NewFlowCardComponent/>
|
<NewFlowCardComponent />
|
||||||
</div>
|
</div>
|
||||||
</BaseModal.Content>
|
</BaseModal.Content>
|
||||||
</BaseModal>
|
</BaseModal>
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { AxiosError } from "axios";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import { Edge, Node, Viewport, XYPosition } from "reactflow";
|
import { Edge, Node, Viewport, XYPosition } from "reactflow";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
|
import { STARTER_FOLDER_NAME } from "../constants/constants";
|
||||||
import {
|
import {
|
||||||
deleteFlowFromDatabase,
|
deleteFlowFromDatabase,
|
||||||
readFlowsFromDatabase,
|
readFlowsFromDatabase,
|
||||||
|
|
@ -25,7 +26,6 @@ import useAlertStore from "./alertStore";
|
||||||
import { useDarkStore } from "./darkStore";
|
import { useDarkStore } from "./darkStore";
|
||||||
import useFlowStore from "./flowStore";
|
import useFlowStore from "./flowStore";
|
||||||
import { useTypesStore } from "./typesStore";
|
import { useTypesStore } from "./typesStore";
|
||||||
import { STARTER_FOLDER_NAME } from "../constants/constants";
|
|
||||||
|
|
||||||
let saveTimeoutId: NodeJS.Timeout | null = null;
|
let saveTimeoutId: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
|
|
@ -38,7 +38,7 @@ const past = {};
|
||||||
const future = {};
|
const future = {};
|
||||||
|
|
||||||
const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
||||||
examples:[],
|
examples: [],
|
||||||
setExamples: (examples: FlowType[]) => {
|
setExamples: (examples: FlowType[]) => {
|
||||||
set({ examples });
|
set({ examples });
|
||||||
},
|
},
|
||||||
|
|
@ -67,8 +67,16 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
||||||
.then((dbData) => {
|
.then((dbData) => {
|
||||||
if (dbData) {
|
if (dbData) {
|
||||||
const { data, flows } = processFlows(dbData, false);
|
const { data, flows } = processFlows(dbData, false);
|
||||||
get().setExamples(flows.filter(f=>(f.folder===STARTER_FOLDER_NAME && !f.user_id)));
|
get().setExamples(
|
||||||
get().setFlows(flows.filter(f=>!(f.folder===STARTER_FOLDER_NAME && !f.user_id)));
|
flows.filter(
|
||||||
|
(f) => f.folder === STARTER_FOLDER_NAME && !f.user_id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
get().setFlows(
|
||||||
|
flows.filter(
|
||||||
|
(f) => !(f.folder === STARTER_FOLDER_NAME && !f.user_id)
|
||||||
|
)
|
||||||
|
);
|
||||||
useTypesStore.setState((state) => ({
|
useTypesStore.setState((state) => ({
|
||||||
data: { ...state.data, ["saved_components"]: data },
|
data: { ...state.data, ["saved_components"]: data },
|
||||||
}));
|
}));
|
||||||
|
|
|
||||||
|
|
@ -159,3 +159,17 @@ export type VertexDataTypeAPI = {
|
||||||
timedelta?: number;
|
timedelta?: number;
|
||||||
duration?: string;
|
duration?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CodeErrorDataTypeAPI = {
|
||||||
|
error: string | undefined;
|
||||||
|
traceback: string | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
// the error above is inside this error.response.data.detail.error
|
||||||
|
// which comes from a request to the API
|
||||||
|
// to type the error we need to know the structure of the object
|
||||||
|
|
||||||
|
// error that has a response, that has a data, that has a detail, that has an error
|
||||||
|
export type ResponseErrorTypeAPI = {
|
||||||
|
response: { data: { detail: CodeErrorDataTypeAPI } };
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ export type FlowType = {
|
||||||
parent?: string;
|
parent?: string;
|
||||||
folder?: string;
|
folder?: string;
|
||||||
user_id?: string;
|
user_id?: string;
|
||||||
icon?:string;
|
icon?: string;
|
||||||
icon_bg_color?:string;
|
icon_bg_color?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NodeType = {
|
export type NodeType = {
|
||||||
|
|
|
||||||
35
src/frontend/src/utils/parameterUtils.ts
Normal file
35
src/frontend/src/utils/parameterUtils.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { throttle } from "lodash";
|
||||||
|
import { postCustomComponentUpdate } from "../controllers/API";
|
||||||
|
import { ResponseErrorTypeAPI } from "../types/api";
|
||||||
|
import { NodeDataType } from "../types/flow";
|
||||||
|
|
||||||
|
export const handleUpdateValues = async (name: string, data: NodeDataType) => {
|
||||||
|
const code = data.node?.template["code"]?.value;
|
||||||
|
if (!code) {
|
||||||
|
console.error("Code not found in the template");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
let newTemplate = await postCustomComponentUpdate(
|
||||||
|
code,
|
||||||
|
name,
|
||||||
|
data.node?.template[name]?.value
|
||||||
|
)
|
||||||
|
.then((res) => {
|
||||||
|
console.log("res", res);
|
||||||
|
if (res.status === 200 && data.node?.template) {
|
||||||
|
return res.data.template;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
return newTemplate;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error occurred while updating the node:", error);
|
||||||
|
let errorType = error as ResponseErrorTypeAPI;
|
||||||
|
throw errorType;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const throttledHandleUpdateValues = throttle(handleUpdateValues, 10);
|
||||||
|
|
@ -231,6 +231,7 @@ export const nodeColors: { [char: string]: string } = {
|
||||||
toolkits: "#DB2C2C",
|
toolkits: "#DB2C2C",
|
||||||
wrappers: "#E6277A",
|
wrappers: "#E6277A",
|
||||||
helpers: "#31A3CC",
|
helpers: "#31A3CC",
|
||||||
|
experimental: "#E6277A",
|
||||||
langchain_utilities: "#31A3CC",
|
langchain_utilities: "#31A3CC",
|
||||||
output_parsers: "#E6A627",
|
output_parsers: "#E6A627",
|
||||||
str: "#31a3cc",
|
str: "#31a3cc",
|
||||||
|
|
@ -262,6 +263,7 @@ export const nodeNames: { [char: string]: string } = {
|
||||||
textsplitters: "Text Splitters",
|
textsplitters: "Text Splitters",
|
||||||
retrievers: "Retrievers",
|
retrievers: "Retrievers",
|
||||||
helpers: "Helpers",
|
helpers: "Helpers",
|
||||||
|
experimental: "Experimental",
|
||||||
langchain_utilities: "Utilities",
|
langchain_utilities: "Utilities",
|
||||||
output_parsers: "Output Parsers",
|
output_parsers: "Output Parsers",
|
||||||
custom_components: "Custom",
|
custom_components: "Custom",
|
||||||
|
|
@ -354,6 +356,7 @@ export const nodeIconsLucide: iconsType = {
|
||||||
textsplitters: Scissors,
|
textsplitters: Scissors,
|
||||||
wrappers: Gift,
|
wrappers: Gift,
|
||||||
helpers: Wand2,
|
helpers: Wand2,
|
||||||
|
experimental: FlaskConical,
|
||||||
langchain_utilities: PocketKnife,
|
langchain_utilities: PocketKnife,
|
||||||
WolframAlphaAPIWrapper: SvgWolfram,
|
WolframAlphaAPIWrapper: SvgWolfram,
|
||||||
output_parsers: Compass,
|
output_parsers: Compass,
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ module.exports = {
|
||||||
"beta-foreground": "var(--beta-foreground)",
|
"beta-foreground": "var(--beta-foreground)",
|
||||||
"chat-bot-icon": "var(--chat-bot-icon)",
|
"chat-bot-icon": "var(--chat-bot-icon)",
|
||||||
"chat-user-icon": "var(--chat-user-icon)",
|
"chat-user-icon": "var(--chat-user-icon)",
|
||||||
"ice": "var(--ice)",
|
ice: "var(--ice)",
|
||||||
|
|
||||||
white: "var(--white)",
|
white: "var(--white)",
|
||||||
border: "hsl(var(--border))",
|
border: "hsl(var(--border))",
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue