add status on building components

This commit is contained in:
cristhianzl 2024-02-19 20:43:58 -03:00
commit e395107473
23 changed files with 412 additions and 106 deletions

8
poetry.lock generated
View file

@ -385,13 +385,13 @@ lxml = ["lxml"]
[[package]]
name = "bidict"
version = "0.23.0"
version = "0.23.1"
description = "The bidirectional mapping library for Python."
optional = false
python-versions = ">=3.8"
files = [
{file = "bidict-0.23.0-py3-none-any.whl", hash = "sha256:f5154a0e42926b122f9b2cb06aeeb9af317f626eb864bd34c1b9cdc5eca8b040"},
{file = "bidict-0.23.0.tar.gz", hash = "sha256:3959ca59d4d6997702d642bf1e5fd93cba299863723fc289545198f70c468578"},
{file = "bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5"},
{file = "bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71"},
]
[[package]]
@ -9383,4 +9383,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.9,<3.12"
content-hash = "e2791fcc55065da8d8d57b7b768a5301b1c967a905596a2944472fccfc3152d8"
content-hash = "20b71a34763c614436d35902b6648a2a3bd970fc1abba31f21b19ec3ee4a2bba"

View file

@ -87,7 +87,7 @@ python-jose = "^3.3.0"
metaphor-python = "^0.1.11"
pydantic = "^2.6.0"
pydantic-settings = "^2.1.0"
zep-python = "*"
zep-python = "1.5.0"
pywin32 = { version = "^306", markers = "sys_platform == 'win32'" }
loguru = "^0.7.1"
langfuse = "^2.9.0"

View file

@ -284,14 +284,28 @@ async def get_vertices(
):
"""Check the flow_id is in the flow_data_store."""
try:
# First, we need to check if the flow_id is in the cache
graph = None
if cache := chat_service.get_cache(flow_id):
graph: Graph = cache.get("result")
flow: Flow = session.get(Flow, flow_id)
if not flow or not flow.data:
raise ValueError("Invalid flow ID")
graph = Graph.from_payload(flow.data)
other_graph = Graph.from_payload(flow.data)
if graph is None:
graph = other_graph
else:
graph = graph.update(other_graph)
chat_service.set_cache(flow_id, graph)
if component_id:
vertices = graph.sort_up_to_vertex(component_id)
try:
vertices = graph.sort_up_to_vertex(component_id)
except Exception as exc:
logger.error(f"IN DEVELOPMENT: Error getting vertices: {exc}")
logger.exception(exc)
vertices = graph.layered_topological_sort()
else:
vertices = graph.layered_topological_sort()
# Now vertices is a list of lists
@ -345,12 +359,15 @@ async def build_vertex(
duration=duration,
timedelta=timedelta,
)
chat_service.set_cache(flow_id, graph)
except Exception as exc:
params = str(exc)
valid = False
result_dict = ResultDict(results={})
artifacts = {}
chat_service.set_cache(flow_id, graph)
# If there's an error building the vertex
# we need to clear the cache
chat_service.clear_cache(flow_id)
await log_vertex_build(
flow_id=flow_id,
vertex_id=vertex_id,

View file

@ -161,7 +161,9 @@ class StreamData(BaseModel):
data: dict
def __str__(self) -> str:
return f"event: {self.event}\ndata: {orjson_dumps(self.data, indent_2=False)}\n\n"
return (
f"event: {self.event}\ndata: {orjson_dumps(self.data, indent_2=False)}\n\n"
)
class CustomComponentCode(BaseModel):
@ -246,4 +248,3 @@ class VertexBuildResponse(BaseModel):
class VerticesBuiltResponse(BaseModel):
vertices: List[VertexBuildResponse]
vertices: List[VertexBuildResponse]

View file

@ -1,7 +1,8 @@
from typing import Optional
from typing import Optional, Union
from langflow import CustomComponent
from langflow.field_typing import Text
from langflow.schema import Record
class ChatInput(CustomComponent):
@ -10,18 +11,35 @@ class ChatInput(CustomComponent):
def build_config(self):
return {
"message": {"input_types": ["Text"], "display_name": "Message"},
"sender": {"options": ["Machine", "User"], "display_name": "Sender Type"},
"message": {"input_types": ["Text"], "display_name": "Message","multiline": True},
"sender_type": {
"options": ["Machine", "User"],
"display_name": "Sender Type",
},
"sender_name": {"display_name": "Sender Name"},
"as_record": {
"display_name": "As Record",
"info": "If true, the message will be returned as a Record.",
},
}
def build(
self,
sender: Optional[str] = "User",
sender_type: Optional[str] = "User",
sender_name: Optional[str] = "You",
message: Optional[str] = None,
) -> Text:
self.repr_value = message
as_record: Optional[bool] = False,
) -> Union[Text, Record]:
self.status = message
if as_record:
if isinstance(message, Record):
# Update the data of the record
message.data["sender"] = sender_type
message.data["sender_name"] = sender_name
return message
return Record(
text=message, data={"sender": sender_type, "sender_name": sender_name}
)
if not message:
message = ""
return message

View file

@ -1,7 +1,8 @@
from typing import Optional
from typing import Optional, Union
from langflow import CustomComponent
from langflow.field_typing import Text
from langflow.schema import Record
class ChatOutput(CustomComponent):
@ -17,23 +18,47 @@ class ChatOutput(CustomComponent):
def build_config(self):
return {
"message": {"input_types": ["Text"], "display_name": "Message"},
"sender": {"options": ["Machine", "User"], "display_name": "Sender Type"},
"sender_type": {
"options": ["Machine", "User"],
"display_name": "Sender Type",
},
"sender_name": {"display_name": "Sender Name"},
"session_id": {
"display_name": "Session ID",
"info": "Session ID of the chat history.",
"input_types": ["Text"],
},
"as_record": {
"display_name": "As Record",
"info": "If true, the message will be returned as a Record.",
},
}
def build(
self,
sender: Optional[str] = "Machine",
sender_type: Optional[str] = "Machine",
sender_name: Optional[str] = "AI",
session_id: Optional[str] = None,
message: Optional[str] = None,
) -> Text:
self.repr_value = message
as_record: Optional[bool] = False,
) -> Union[Text, Record]:
self.status = message
if as_record:
if isinstance(message, Record):
# Update the data of the record
message.data["sender"] = sender_type
message.data["sender_name"] = sender_name
message.data["session_id"] = session_id
return message
return Record(
text=message,
data={
"sender": sender_type,
"sender_name": sender_name,
"session_id": session_id,
},
)
if not message:
message = ""
return message

View file

@ -20,6 +20,7 @@ class PromptComponent(CustomComponent):
**kwargs,
) -> Text:
prompt_template = PromptTemplate.from_template(template)
try:
formated_prompt = prompt_template.format(**kwargs)
except Exception as exc:

View file

@ -0,0 +1,18 @@
import uuid
from langflow import CustomComponent
class UUIDGeneratorComponent(CustomComponent):
documentation: str = "http://docs.langflow.org/components/custom"
display_name = "Unique ID Generator"
description = "Generates a unique ID."
def generate(self, *args, **kwargs):
return str(uuid.uuid4().hex)
def build_config(self):
return {"unique_id": {"display_name": "Value", "value": self.generate}}
def build(self, unique_id: str) -> str:
return unique_id

View file

@ -99,7 +99,7 @@ class ChromaSearchComponent(CustomComponent):
chroma_server_grpc_port=chroma_server_grpc_port or None,
chroma_server_ssl_enabled=chroma_server_ssl_enabled,
)
index_directory = self.resolve_path(index_directory)
chroma = Chroma(
embedding_function=embedding,
collection_name=collection_name,

View file

@ -76,6 +76,28 @@ class Graph:
return False
return self.__repr__() == other.__repr__()
# update this graph with another graph by comparing the __repr__ of each vertex
# and if the __repr__ of a vertex is not the same as the other
# then update the .data of the vertex to the self
# both graphs have the same vertices and edges
# but the data of the vertices might be different
def update(self, other: "Graph", different_vertices: List[str] = None) -> None:
if different_vertices is None:
different_vertices = []
for vertex in self.vertices:
other_vertex = other.get_vertex(vertex.id)
if other_vertex is None:
continue
if (
vertex.id in different_vertices
or vertex.__repr__() != other.get_vertex(vertex.id).__repr__()
):
vertex.data = other.get_vertex(vertex.id).data
vertex._build_params()
vertex.graph = self
vertex._built = False
return self
def _build_graph(self) -> None:
"""Builds the graph from the vertices and edges."""
self.vertices = self._build_vertices()
@ -314,9 +336,22 @@ class Graph:
"""Cuts the graph up to a given vertex."""
# Get the vertices that are connected to the vertex
# and the vertex itself
vertices = [self.get_vertex(vertex_id)]
for edge in self.get_vertex(edge.target_id).edges:
vertices.append(self.get_vertex(edge.target_id))
vertex = self.get_vertex(vertex_id)
vertices = [vertex]
for edge in vertex.edges:
if edge.target_id == vertex_id:
vertices.append(self.get_vertex(edge.source_id))
# Get the edges that are connected to the vertices
edges = []
for vertex in vertices:
edges.extend(self.get_vertex_edges(vertex.id))
source_vertex = self.get_vertex(edge.source_id)
target_vertex = self.get_vertex(edge.target_id)
if source_vertex not in vertices:
vertices.append(source_vertex)
if target_vertex not in vertices:
vertices.append(target_vertex)
edges = [edge for vertex in vertices for edge in vertex.edges]

View file

@ -55,11 +55,14 @@ class Vertex:
self.parent_node_id: Optional[str] = self._data.get("parent_node_id")
self.parent_is_top_level = False
self.layer = None
self.should_run = True
try:
self.is_interface_component = InterfaceComponentTypes(self.vertex_type)
except ValueError:
self.is_interface_component = False
self.use_result = False
# Build a result dict for each edge
# like so: {edge.target.id: {edge.target_param: self._built_object}}
async def get_result_dict(self, force: bool = False) -> Dict[str, Dict[str, Any]]:
@ -78,14 +81,18 @@ class Vertex:
):
if edge.target_id not in edge_results:
edge_results[edge.target_id] = {}
edge_results[edge.target_id][edge.target_param] = await edge.get_result(source=self, target=target)
edge_results[edge.target_id][edge.target_param] = await edge.get_result(
source=self, target=target
)
return edge_results
def get_built_result(self):
# If the Vertex.type is a power component
# then we need to return the built object
# instead of the result dict
if self.is_interface_component and not isinstance(self._built_object, UnbuiltObject):
if self.is_interface_component and not isinstance(
self._built_object, UnbuiltObject
):
result = self._built_object
# if it is not a dict or a string and hasattr model_dump then
# return the model_dump
@ -95,7 +102,11 @@ class Vertex:
if isinstance(self._built_result, UnbuiltResult):
return {}
return self._built_result if isinstance(self._built_result, dict) else {"result": self._built_result}
return (
self._built_result
if isinstance(self._built_result, dict)
else {"result": self._built_result}
)
def set_artifacts(self) -> None:
pass
@ -149,18 +160,29 @@ class Vertex:
self.data = self._data["data"]
self.output = self.data["node"]["base_classes"]
self.pinned = self.data["node"].get("pinned", False)
template_dicts = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
template_dicts = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
template_dicts = {
key: value
for key, value in self.data["node"]["template"].items()
if isinstance(value, dict)
}
self.required_inputs = [
template_dicts[key]["type"] for key, value in template_dicts.items() if value["required"]
template_dicts[key]["type"]
for key, value in template_dicts.items()
if value["required"]
]
self.optional_inputs = [
template_dicts[key]["type"] for key, value in template_dicts.items() if not value["required"]
template_dicts[key]["type"]
for key, value in template_dicts.items()
if not value["required"]
]
# Add the template_dicts[key]["input_types"] to the optional_inputs
self.optional_inputs.extend(
[input_type for value in template_dicts.values() for input_type in value.get("input_types", [])]
[
input_type
for value in template_dicts.values()
for input_type in value.get("input_types", [])
]
)
template_dict = self.data["node"]["template"]
@ -203,7 +225,11 @@ class Vertex:
if self.graph is None:
raise ValueError("Graph not found")
template_dict = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
template_dict = {
key: value
for key, value in self.data["node"]["template"].items()
if isinstance(value, dict)
}
params = self.params.copy() if self.params else {}
for edge in self.edges:
@ -255,7 +281,11 @@ class Vertex:
# list of dicts, so we need to convert it to a dict
# before passing it to the build method
if isinstance(val, list):
params[key] = {k: v for item in value.get("value", []) for k, v in item.items()}
params[key] = {
k: v
for item in value.get("value", [])
for k, v in item.items()
}
elif isinstance(val, dict):
params[key] = val
elif value.get("type") == "int" and val is not None:
@ -292,7 +322,12 @@ class Vertex:
self._built = True
async def _run(self, user_id: str, inputs: Optional[dict] = None, session_id: Optional[str] = None):
async def _run(
self,
user_id: str,
inputs: Optional[dict] = None,
session_id: Optional[str] = None,
):
# user_id is just for compatibility with the other build methods
inputs = inputs or {}
# inputs = {key: value or "" for key, value in inputs.items()}
@ -307,7 +342,9 @@ class Vertex:
if isinstance(self._built_object, str):
self._built_result = self._built_object
result = await generate_result(self._built_object, inputs, self.has_external_output, session_id)
result = await generate_result(
self._built_object, inputs, self.has_external_output, session_id
)
self._built_result = result
async def _build_each_node_in_params_dict(self, user_id=None):
@ -335,11 +372,13 @@ class Vertex:
"""
return all(self._is_node(node) for node in value)
async def get_result(self, requester: Optional["Vertex"] = None, user_id=None, timeout=None) -> Any:
async def get_result(
self, requester: Optional["Vertex"] = None, user_id=None, timeout=None
) -> Any:
# PLEASE REVIEW THIS IF STATEMENT
# Check if the Vertex was built already
if self._built:
return self._built_object
return self._built_object if not self.use_result else self._built_result
if self.is_task and self.task_id is not None:
task = self.get_task()
@ -369,7 +408,9 @@ class Vertex:
self._extend_params_list_with_result(key, result)
self.params[key] = result
async def _build_list_of_nodes_and_update_params(self, key, nodes: List["Vertex"], user_id=None):
async def _build_list_of_nodes_and_update_params(
self, key, nodes: List["Vertex"], user_id=None
):
"""
Iterates over a list of nodes, builds each and updates the params dictionary.
"""
@ -421,7 +462,9 @@ class Vertex:
self._update_built_object_and_artifacts(result)
except Exception as exc:
logger.exception(exc)
raise ValueError(f"Error building node {self.vertex_type}(ID:{self.id}): {str(exc)}") from exc
raise ValueError(
f"Error building node {self.vertex_type}(ID:{self.id}): {str(exc)}"
) from exc
def _update_built_object_and_artifacts(self, result):
"""
@ -458,7 +501,7 @@ class Vertex:
requester: Optional["Vertex"] = None,
**kwargs,
) -> Any:
if self.pinned:
if self.pinned and self._built:
return self.get_requester_result(requester)
self._reset()
@ -480,9 +523,15 @@ class Vertex:
return self._built_object
# Get the requester edge
requester_edge = next((edge for edge in self.edges if edge.target_id == requester.id), None)
requester_edge = next(
(edge for edge in self.edges if edge.target_id == requester.id), None
)
# Return the result of the requester edge
return None if requester_edge is None else await requester_edge.get_result(source=self, target=requester)
return (
None
if requester_edge is None
else await requester_edge.get_result(source=self, target=requester)
)
def add_edge(self, edge: "ContractEdge") -> None:
if edge not in self.edges:
@ -502,7 +551,11 @@ class Vertex:
def _built_object_repr(self):
# Add a message with an emoji, stars for sucess,
return "Built sucessfully ✨" if self._built_object is not None else "Failed to build 😵‍💫"
return (
"Built sucessfully ✨"
if self._built_object is not None
else "Failed to build 😵‍💫"
)
class StatefulVertex(Vertex):
@ -511,9 +564,3 @@ class StatefulVertex(Vertex):
class StatelessVertex(Vertex):
pass
pass
pass
pass
pass
pass
pass

View file

@ -3,10 +3,10 @@ import json
from typing import Callable, Dict, List, Optional, Union
from langchain_core.messages import AIMessage
from langflow.graph.utils import UnbuiltObject, flatten_list
from langflow.graph.vertex.base import StatefulVertex, StatelessVertex
from langflow.interface.utils import extract_input_variables_from_prompt
from langflow.schema import Record
from langflow.utils.schemas import ChatOutputResponse
@ -118,9 +118,11 @@ class DocumentLoaderVertex(StatefulVertex):
# show how many documents are in the list?
if not isinstance(self._built_object, UnbuiltObject):
avg_length = sum(len(doc.page_content) for doc in self._built_object if hasattr(doc, "page_content")) / len(
self._built_object
)
avg_length = sum(
len(doc.page_content)
for doc in self._built_object
if hasattr(doc, "page_content")
) / len(self._built_object)
return f"""{self.vertex_type}({len(self._built_object)} documents)
\nAvg. Document Length (characters): {int(avg_length)}
Documents: {self._built_object[:3]}..."""
@ -193,7 +195,9 @@ class TextSplitterVertex(StatefulVertex):
# show how many documents are in the list?
if not isinstance(self._built_object, UnbuiltObject):
avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(self._built_object)
avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(
self._built_object
)
return f"""{self.vertex_type}({len(self._built_object)} documents)
\nAvg. Document Length (characters): {int(avg_length)}
\nDocuments: {self._built_object[:3]}..."""
@ -240,18 +244,27 @@ class PromptVertex(StatelessVertex):
user_id = kwargs.get("user_id", None)
tools = kwargs.get("tools", [])
if not self._built or force:
if "input_variables" not in self.params or self.params["input_variables"] is None:
if (
"input_variables" not in self.params
or self.params["input_variables"] is None
):
self.params["input_variables"] = []
# Check if it is a ZeroShotPrompt and needs a tool
if "ShotPrompt" in self.vertex_type:
tools = [tool_node.build(user_id=user_id) for tool_node in tools] if tools is not None else []
tools = (
[tool_node.build(user_id=user_id) for tool_node in tools]
if tools is not None
else []
)
# flatten the list of tools if it is a list of lists
# first check if it is a list
if tools and isinstance(tools, list) and isinstance(tools[0], list):
tools = flatten_list(tools)
self.params["tools"] = tools
prompt_params = [
key for key, value in self.params.items() if isinstance(value, str) and key != "format_instructions"
key
for key, value in self.params.items()
if isinstance(value, str) and key != "format_instructions"
]
else:
prompt_params = ["template"]
@ -261,14 +274,20 @@ class PromptVertex(StatelessVertex):
prompt_text = self.params[param]
variables = extract_input_variables_from_prompt(prompt_text)
self.params["input_variables"].extend(variables)
self.params["input_variables"] = list(set(self.params["input_variables"]))
self.params["input_variables"] = list(
set(self.params["input_variables"])
)
elif isinstance(self.params, dict):
self.params.pop("input_variables", None)
await self._build(user_id=user_id)
def _built_object_repr(self):
if not self.artifacts or self._built_object is None or not hasattr(self._built_object, "format"):
if (
not self.artifacts
or self._built_object is None
or not hasattr(self._built_object, "format")
):
return super()._built_object_repr()
elif isinstance(self._built_object, UnbuiltObject):
return super()._built_object_repr()
@ -280,7 +299,9 @@ class PromptVertex(StatelessVertex):
# so the prompt format doesn't break
artifacts.pop("handle_keys", None)
try:
if not hasattr(self._built_object, "template") and hasattr(self._built_object, "prompt"):
if not hasattr(self._built_object, "template") and hasattr(
self._built_object, "prompt"
):
template = self._built_object.prompt.template
else:
template = self._built_object.template
@ -288,7 +309,11 @@ class PromptVertex(StatelessVertex):
if value:
replace_key = "{" + key + "}"
template = template.replace(replace_key, value)
return template if isinstance(template, str) else f"{self.vertex_type}({template})"
return (
template
if isinstance(template, str)
else f"{self.vertex_type}({template})"
)
except KeyError:
return str(self._built_object)
@ -337,12 +362,16 @@ class ChatVertex(StatelessVertex):
if isinstance(self._built_object, dict):
# Turn the dict into a pleasing to
# read JSON inside a code block
self._built_object = dict_to_codeblock(self._built_object)
message = dict_to_codeblock(self._built_object)
elif isinstance(self._built_object, Record):
message = self._built_object.text
elif not isinstance(self._built_object, str):
self._built_object = str(self._built_object)
message = str(self._built_object)
else:
message = self._built_object
artifacts = ChatOutputResponse(
message=self._built_object,
message=message,
sender=sender,
sender_name=sender_name,
)
@ -354,6 +383,52 @@ class ChatVertex(StatelessVertex):
await super()._run(*args, **kwargs)
class RoutingVertex(StatelessVertex):
def __init__(self, data: Dict, graph):
super().__init__(data, graph=graph, base_type="routing")
self.use_result = True
self.steps = [self._build, self._run]
def _built_object_repr(self):
if self.artifacts and "repr" in self.artifacts:
return self.artifacts["repr"] or super()._built_object_repr()
return super()._built_object_repr()
def _build(self, *args, **kwargs):
super()._build(*args, **kwargs)
# After building, the _built_object should be a dict with
# {"result": Any, "condition": bool}
# if true, we need to set should_run attr in the target of true edge
# to true and should_run attr in the target of false edge to false
# TODO: Add support for multiple conditions
def _run(self, *args, **kwargs):
if self._built_object:
condition = self._built_object.get("condition")
result = self._built_object.get("result")
if condition is not None:
for edge in self.edges:
if edge.source_id == self.id:
target_vertex = self.graph.get_vertex(edge.target_id)
# source_handle.channel and condition should be the same
channel_bool = edge.source_handle.channel == "true"
if condition == channel_bool:
target_vertex.should_run = True
else:
target_vertex.should_run = False
else:
raise ValueError(
f"RoutingVertex {self.id} must have a condition in the _built_object"
)
self._built_result = result
else:
raise ValueError(
f"RoutingVertex {self.id} must have a _built_object with a condition and a result"
)
def dict_to_codeblock(d: dict) -> str:
from langflow.api.utils import serialize_field

View file

@ -1,4 +1,4 @@
from typing import Optional
from typing import Any, Optional
from langchain_core.documents import Document
from pydantic import BaseModel
@ -38,6 +38,24 @@ class Record(BaseModel):
"""
return Document(page_content=self.text, metadata=self.data)
def __call__(self, *args: Any, **kwds: Any) -> Any:
"""
Returns the text of the record.
Returns:
Any: The text of the record.
"""
return self.text
def __str__(self) -> str:
"""
Returns the text of the record.
Returns:
str: The text of the record.
"""
return self.text
def docs_to_records(documents: list[Document]) -> list[Record]:
"""

View file

@ -59,7 +59,9 @@ class ChatService(Service):
"""Send the last chat message to the client."""
client_id = self.chat_cache.current_client_id
if client_id in self.active_connections:
chat_response = self.chat_history.get_history(client_id, filter_messages=False)[-1]
chat_response = self.chat_history.get_history(
client_id, filter_messages=False
)[-1]
if chat_response.is_bot:
# Process FileResponse
if isinstance(chat_response, FileResponse):
@ -86,7 +88,9 @@ class ChatService(Service):
data_type=self.last_cached_object_dict["type"],
)
self.chat_history.add_message(self.chat_cache.current_client_id, chat_response)
self.chat_history.add_message(
self.chat_cache.current_client_id, chat_response
)
async def connect(self, client_id: str, websocket: WebSocket):
self.active_connections[client_id] = websocket
@ -209,7 +213,9 @@ class ChatService(Service):
await self.process_message(client_id, payload, build_result)
else:
raise RuntimeError(f"Could not find a build result for client_id {client_id}")
raise RuntimeError(
f"Could not find a build result for client_id {client_id}"
)
except Exception as exc:
# Handle any exceptions that might occur
logger.exception(f"Error handling websocket: {exc}")
@ -241,6 +247,12 @@ class ChatService(Service):
"""
return self.cache_service.get(client_id)
def clear_cache(self, client_id: str):
"""
Clear the cache for a client.
"""
self.cache_service.delete(client_id)
def dict_to_markdown_table(my_dict):
markdown_table = "| Key | Value |\n|---|---|\n"

View file

@ -7,7 +7,9 @@ from pydantic import BaseModel, Field, field_serializer, validator
class TransactionModel(BaseModel):
id: Optional[int] = Field(default=None, alias="id")
timestamp: Optional[datetime] = Field(default_factory=datetime.now, alias="timestamp")
timestamp: Optional[datetime] = Field(
default_factory=datetime.now, alias="timestamp"
)
source: str
target: str
target_args: dict
@ -89,18 +91,13 @@ class VertexBuildModel(BaseModel):
return v
# create a function that turns dicts into a
# dict like this:
# my_map_dict = {
# "key": [
# 1, 2, 3
# ],
# "value": [
# "one", "two", "three"
# ]
# }
# so map has a "key" and a "value" list
# containing the keys and values of the dict
class VertexBuildResponseModel(VertexBuildModel):
@field_serializer("data", "artifacts")
def serialize_dict(v):
return v
def to_map(value: dict):
keys = list(value.keys())
values = list(value.values())
@ -108,13 +105,13 @@ def to_map(value: dict):
class VertexBuildMapModel(BaseModel):
vertex_builds: dict[str, list[VertexBuildModel]]
vertex_builds: dict[str, list[VertexBuildResponseModel]]
@classmethod
def from_list_of_dicts(cls, vertex_build_dicts):
vertex_build_map = {}
for vertex_build_dict in vertex_build_dicts:
vertex_build = VertexBuildModel(**vertex_build_dict)
vertex_build = VertexBuildResponseModel(**vertex_build_dict)
if vertex_build.id not in vertex_build_map:
vertex_build_map[vertex_build.id] = []
vertex_build_map[vertex_build.id].append(vertex_build)

View file

@ -1,9 +1,10 @@
import os
import yaml
from langflow.services.base import Service
from langflow.services.settings.auth import AuthSettings
from langflow.services.settings.base import Settings
from loguru import logger
import os
import yaml
class SettingsService(Service):
@ -11,8 +12,8 @@ class SettingsService(Service):
def __init__(self, settings: Settings, auth_settings: AuthSettings):
super().__init__()
self.settings = settings
self.auth_settings = auth_settings
self.settings: Settings = settings
self.auth_settings: AuthSettings = auth_settings
@classmethod
def load_settings_from_yaml(cls, file_path: str) -> "SettingsService":
@ -30,7 +31,9 @@ class SettingsService(Service):
for key in settings_dict:
if key not in Settings.model_fields.keys():
raise KeyError(f"Key {key} not found in settings")
logger.debug(f"Loading {len(settings_dict[key])} {key} from {file_path}")
logger.debug(
f"Loading {len(settings_dict[key])} {key} from {file_path}"
)
settings = Settings(**settings_dict)
if not settings.CONFIG_DIR:

View file

@ -406,13 +406,27 @@ export default function ParameterComponent({
data-testid={"textarea-" + data.node.template[name].name}
/>
) : (
<InputComponent
id={"input-" + index}
disabled={disabled}
password={data.node?.template[name].password ?? false}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
/>
<div className="mt-2 flex w-full items-center">
<div className="w-5/6 flex-grow">
<InputComponent
id={"input-" + index}
disabled={disabled}
password={data.node?.template[name].password ?? false}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
/>
</div>
{data.node?.template[name].refresh && (
<button
className="extra-side-bar-buttons ml-2 mt-1 w-1/6"
onClick={() => {
handleUpdateValues(name, data);
}}
>
<IconComponent name="RefreshCcw" />
</button>
)}
</div>
)}
</div>
) : left === true && type === "bool" ? (

View file

@ -86,6 +86,18 @@ export default function GenericNode({
// State for outline color
const isBuilding = useFlowStore((state) => state.isBuilding);
// should be empty string if no duration
// else should be `Duration: ${duration}`
const getDurationString = (duration: number | null): string => {
if (duration === null) {
return "";
} else {
return `Duration: ${duration}`;
}
};
const durationString = getDurationString(validationStatus?.data.duration);
useEffect(() => {
setNodeDescription(data.node!.description);
}, [data.node!.description]);
@ -400,19 +412,19 @@ export default function GenericNode({
Build{" "}
<IconComponent
name="Play"
className=" h-5 fill-build-trigger stroke-build-trigger stroke-1"
className=" h-5 stroke-build-trigger stroke-2"
/>{" "}
flow to validate status.
</span>
) : (
<div className="max-h-96 overflow-auto">
{typeof validationStatus.params === "string"
? `Duration: ${validationStatus.data.duration}\n${validationStatus.params}`
? `${durationString}\n${validationStatus.params}`
.split("\n")
.map((line, index) => (
<div key={index}>{line}</div>
))
: ""}
: durationString}
</div>
)
}

View file

@ -855,9 +855,16 @@ export async function requestLogout() {
}
export async function getVerticesOrder(
flowId: string
flowId: string,
nodeId?: string | null
): Promise<AxiosResponse<VerticesOrderTypeAPI>> {
return await api.get(`${BASE_URL_API}build/${flowId}/vertices`);
// nodeId is optional and is a query parameter
// if nodeId is not provided, the API will return all vertices
const config = {};
if (nodeId) {
config["params"] = { component_id: nodeId };
}
return await api.get(`${BASE_URL_API}build/${flowId}/vertices`, config);
}
export async function postBuildVertex(

View file

@ -378,6 +378,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
const currentFlow = useFlowsManagerStore.getState().currentFlow;
const setSuccessData = useAlertStore.getState().setSuccessData;
const setErrorData = useAlertStore.getState().setErrorData;
const setNoticeData = useAlertStore.getState().setNoticeData;
function handleBuildUpdate(data: any) {
get().addDataToFlowPool(data.data[data.id], data.id);
useFlowStore.getState().updateBuildStatus([data.id], BuildStatus.BUILDED);
@ -392,6 +393,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
name: currentFlow!.name,
description: currentFlow!.description,
});
setNoticeData({ title: "Running components" });
return buildVertices({
flowId: currentFlow!.id,
nodeId,

View file

@ -293,13 +293,13 @@
@apply opacity-100 animate-wiggle;
}
.green-status {
@apply generic-node-status text-status-green fill-status-green;
@apply generic-node-status text-status-green;
}
.red-status {
@apply generic-node-status text-status-red fill-status-red;
@apply generic-node-status text-status-red;
}
.yellow-status {
@apply generic-node-status text-status-yellow fill-status-yellow;
@apply generic-node-status text-status-yellow;
}
.status-build-animation {
@apply opacity-0;

View file

@ -23,7 +23,7 @@ export async function buildVertices({
onBuildError,
onBuildStart,
}: BuildVerticesParams) {
let orderResponse = await getVerticesOrder(flowId);
let orderResponse = await getVerticesOrder(flowId, nodeId);
let verticesOrder: Array<Array<string>> = orderResponse.data.ids;
let vertices: Array<Array<string>> = [];

View file

@ -110,6 +110,8 @@ import {
X,
XCircle,
Zap,
FlaskConical,
AlertCircle,
} from "lucide-react";
import { FaApple, FaGithub } from "react-icons/fa";
import { AWSIcon } from "../icons/AWS";
@ -400,4 +402,6 @@ export const nodeIconsLucide: iconsType = {
io: Cable,
ScreenShare,
Code,
FlaskConical,
AlertCircle,
};