Merge branch 'zustand/io/migration' of github.com:logspace-ai/langflow into zustand/io/migration
This commit is contained in:
commit
ba4d51a77f
18 changed files with 433 additions and 150 deletions
|
|
@ -1,6 +1,15 @@
|
||||||
import time
|
import time
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from fastapi import APIRouter, Body, Depends, HTTPException, WebSocket, WebSocketException, status
|
from fastapi import (
|
||||||
|
APIRouter,
|
||||||
|
Body,
|
||||||
|
Depends,
|
||||||
|
HTTPException,
|
||||||
|
WebSocket,
|
||||||
|
WebSocketException,
|
||||||
|
status,
|
||||||
|
)
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from langflow.api.utils import build_input_keys_response, format_elapsed_time
|
from langflow.api.utils import build_input_keys_response, format_elapsed_time
|
||||||
from langflow.api.v1.schemas import (
|
from langflow.api.v1.schemas import (
|
||||||
|
|
@ -15,7 +24,10 @@ from langflow.api.v1.schemas import (
|
||||||
from langflow.graph.graph.base import Graph
|
from langflow.graph.graph.base import Graph
|
||||||
from langflow.graph.vertex.base import StatelessVertex
|
from langflow.graph.vertex.base import StatelessVertex
|
||||||
from langflow.processing.process import process_tweaks_on_graph
|
from langflow.processing.process import process_tweaks_on_graph
|
||||||
from langflow.services.auth.utils import get_current_active_user, get_current_user_for_websocket
|
from langflow.services.auth.utils import (
|
||||||
|
get_current_active_user,
|
||||||
|
get_current_user_for_websocket,
|
||||||
|
)
|
||||||
from langflow.services.cache.service import BaseCacheService
|
from langflow.services.cache.service import BaseCacheService
|
||||||
from langflow.services.cache.utils import update_build_status
|
from langflow.services.cache.utils import update_build_status
|
||||||
from langflow.services.chat.service import ChatService
|
from langflow.services.chat.service import ChatService
|
||||||
|
|
@ -40,9 +52,13 @@ async def chat(
|
||||||
user = await get_current_user_for_websocket(websocket, db)
|
user = await get_current_user_for_websocket(websocket, db)
|
||||||
await websocket.accept()
|
await websocket.accept()
|
||||||
if not user:
|
if not user:
|
||||||
await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized")
|
await websocket.close(
|
||||||
|
code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized"
|
||||||
|
)
|
||||||
elif not user.is_active:
|
elif not user.is_active:
|
||||||
await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized")
|
await websocket.close(
|
||||||
|
code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized"
|
||||||
|
)
|
||||||
|
|
||||||
if client_id in chat_service.cache_service:
|
if client_id in chat_service.cache_service:
|
||||||
await chat_service.handle_websocket(client_id, websocket)
|
await chat_service.handle_websocket(client_id, websocket)
|
||||||
|
|
@ -58,7 +74,9 @@ async def chat(
|
||||||
logger.error(f"Error in chat websocket: {exc}")
|
logger.error(f"Error in chat websocket: {exc}")
|
||||||
messsage = exc.detail if isinstance(exc, HTTPException) else str(exc)
|
messsage = exc.detail if isinstance(exc, HTTPException) else str(exc)
|
||||||
if "Could not validate credentials" in str(exc):
|
if "Could not validate credentials" in str(exc):
|
||||||
await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized")
|
await websocket.close(
|
||||||
|
code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=messsage)
|
await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=messsage)
|
||||||
|
|
||||||
|
|
@ -100,10 +118,15 @@ async def init_build(
|
||||||
|
|
||||||
|
|
||||||
@router.get("/build/{flow_id}/status", response_model=BuiltResponse)
|
@router.get("/build/{flow_id}/status", response_model=BuiltResponse)
|
||||||
async def build_status(flow_id: str, cache_service: "BaseCacheService" = Depends(get_cache_service)):
|
async def build_status(
|
||||||
|
flow_id: str, cache_service: "BaseCacheService" = Depends(get_cache_service)
|
||||||
|
):
|
||||||
"""Check the flow_id is in the cache_service."""
|
"""Check the flow_id is in the cache_service."""
|
||||||
try:
|
try:
|
||||||
built = flow_id in cache_service and cache_service[flow_id]["status"] == BuildStatus.SUCCESS
|
built = (
|
||||||
|
flow_id in cache_service
|
||||||
|
and cache_service[flow_id]["status"] == BuildStatus.SUCCESS
|
||||||
|
)
|
||||||
|
|
||||||
return BuiltResponse(
|
return BuiltResponse(
|
||||||
built=built,
|
built=built,
|
||||||
|
|
@ -174,7 +197,9 @@ async def stream_build(
|
||||||
valid = True
|
valid = True
|
||||||
|
|
||||||
logger.debug(f"Building node {str(vertex.vertex_type)}")
|
logger.debug(f"Building node {str(vertex.vertex_type)}")
|
||||||
logger.debug(f"Output: {params[:100]}{'...' if len(params) > 100 else ''}")
|
logger.debug(
|
||||||
|
f"Output: {params[:100]}{'...' if len(params) > 100 else ''}"
|
||||||
|
)
|
||||||
if vertex.artifacts:
|
if vertex.artifacts:
|
||||||
# The artifacts will be prompt variables
|
# The artifacts will be prompt variables
|
||||||
# passed to build_input_keys_response
|
# passed to build_input_keys_response
|
||||||
|
|
@ -187,7 +212,9 @@ async def stream_build(
|
||||||
time_elapsed = format_elapsed_time(time.perf_counter() - start_time)
|
time_elapsed = format_elapsed_time(time.perf_counter() - start_time)
|
||||||
update_build_status(cache_service, flow_id, BuildStatus.FAILURE)
|
update_build_status(cache_service, flow_id, BuildStatus.FAILURE)
|
||||||
|
|
||||||
vertex_id = vertex.parent_node_id if vertex.parent_is_top_level else vertex.id
|
vertex_id = (
|
||||||
|
vertex.parent_node_id if vertex.parent_is_top_level else vertex.id
|
||||||
|
)
|
||||||
if vertex_id in graph.top_level_vertices:
|
if vertex_id in graph.top_level_vertices:
|
||||||
response = {
|
response = {
|
||||||
"valid": valid,
|
"valid": valid,
|
||||||
|
|
@ -202,7 +229,9 @@ async def stream_build(
|
||||||
langchain_object = await graph.build()
|
langchain_object = await graph.build()
|
||||||
# Now we need to check the input_keys to send them to the client
|
# Now we need to check the input_keys to send them to the client
|
||||||
if hasattr(langchain_object, "input_keys"):
|
if hasattr(langchain_object, "input_keys"):
|
||||||
input_keys_response = build_input_keys_response(langchain_object, artifacts)
|
input_keys_response = build_input_keys_response(
|
||||||
|
langchain_object, artifacts
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
input_keys_response = {
|
input_keys_response = {
|
||||||
"input_keys": None,
|
"input_keys": None,
|
||||||
|
|
@ -249,16 +278,30 @@ async def try_running_celery_task(vertex, user_id):
|
||||||
@router.get("/build/{flow_id}/vertices", response_model=VerticesOrderResponse)
|
@router.get("/build/{flow_id}/vertices", response_model=VerticesOrderResponse)
|
||||||
async def get_vertices(
|
async def get_vertices(
|
||||||
flow_id: str,
|
flow_id: str,
|
||||||
|
component_id: Optional[str] = None,
|
||||||
chat_service: "ChatService" = Depends(get_chat_service),
|
chat_service: "ChatService" = Depends(get_chat_service),
|
||||||
session=Depends(get_session),
|
session=Depends(get_session),
|
||||||
):
|
):
|
||||||
"""Check the flow_id is in the flow_data_store."""
|
"""Check the flow_id is in the flow_data_store."""
|
||||||
try:
|
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)
|
flow: Flow = session.get(Flow, flow_id)
|
||||||
if not flow or not flow.data:
|
if not flow or not flow.data:
|
||||||
raise ValueError("Invalid flow ID")
|
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)
|
chat_service.set_cache(flow_id, graph)
|
||||||
|
|
||||||
|
if component_id:
|
||||||
|
vertices = graph.sort_up_to_vertex(component_id)
|
||||||
|
else:
|
||||||
vertices = graph.layered_topological_sort()
|
vertices = graph.layered_topological_sort()
|
||||||
# Now vertices is a list of lists
|
# Now vertices is a list of lists
|
||||||
# We need to get the id of each vertex
|
# We need to get the id of each vertex
|
||||||
|
|
@ -276,7 +319,7 @@ async def build_vertex(
|
||||||
flow_id: str,
|
flow_id: str,
|
||||||
vertex_id: str,
|
vertex_id: str,
|
||||||
chat_service: "ChatService" = Depends(get_chat_service),
|
chat_service: "ChatService" = Depends(get_chat_service),
|
||||||
# current_user=Depends(get_current_active_user),
|
current_user=Depends(get_current_active_user),
|
||||||
tweaks: dict = Body(None),
|
tweaks: dict = Body(None),
|
||||||
inputs: dict = Body(None),
|
inputs: dict = Body(None),
|
||||||
):
|
):
|
||||||
|
|
@ -295,7 +338,7 @@ async def build_vertex(
|
||||||
raise ValueError("Invalid vertex")
|
raise ValueError("Invalid vertex")
|
||||||
try:
|
try:
|
||||||
if isinstance(vertex, StatelessVertex) or not vertex._built:
|
if isinstance(vertex, StatelessVertex) or not vertex._built:
|
||||||
await vertex.build(user_id=None)
|
await vertex.build(user_id=current_user.id)
|
||||||
params = vertex._built_object_repr()
|
params = vertex._built_object_repr()
|
||||||
valid = True
|
valid = True
|
||||||
result_dict = vertex.get_built_result()
|
result_dict = vertex.get_built_result()
|
||||||
|
|
@ -305,13 +348,21 @@ async def build_vertex(
|
||||||
artifacts = vertex.artifacts
|
artifacts = vertex.artifacts
|
||||||
timedelta = time.perf_counter() - start_time
|
timedelta = time.perf_counter() - start_time
|
||||||
duration = format_elapsed_time(timedelta)
|
duration = format_elapsed_time(timedelta)
|
||||||
result_dict = ResultDict(results=result_dict, artifacts=artifacts, duration=duration, timedelta=timedelta)
|
result_dict = ResultDict(
|
||||||
|
results=result_dict,
|
||||||
|
artifacts=artifacts,
|
||||||
|
duration=duration,
|
||||||
|
timedelta=timedelta,
|
||||||
|
)
|
||||||
|
chat_service.set_cache(flow_id, graph)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
params = str(exc)
|
params = str(exc)
|
||||||
valid = False
|
valid = False
|
||||||
result_dict = ResultDict(results={})
|
result_dict = ResultDict(results={})
|
||||||
artifacts = {}
|
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(
|
await log_vertex_build(
|
||||||
flow_id=flow_id,
|
flow_id=flow_id,
|
||||||
vertex_id=vertex_id,
|
vertex_id=vertex_id,
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,9 @@ class StreamData(BaseModel):
|
||||||
data: dict
|
data: dict
|
||||||
|
|
||||||
def __str__(self) -> str:
|
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):
|
class CustomComponentCode(BaseModel):
|
||||||
|
|
@ -246,4 +248,3 @@ class VertexBuildResponse(BaseModel):
|
||||||
|
|
||||||
class VerticesBuiltResponse(BaseModel):
|
class VerticesBuiltResponse(BaseModel):
|
||||||
vertices: List[VertexBuildResponse]
|
vertices: List[VertexBuildResponse]
|
||||||
vertices: List[VertexBuildResponse]
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ class CharacterTextSplitterComponent(CustomComponent):
|
||||||
chunk_size: int = 1000,
|
chunk_size: int = 1000,
|
||||||
separator: str = "\n",
|
separator: str = "\n",
|
||||||
) -> List[Document]:
|
) -> List[Document]:
|
||||||
|
# separator may come escaped from the frontend
|
||||||
|
separator = separator.encode().decode("unicode_escape")
|
||||||
docs = CharacterTextSplitter(
|
docs = CharacterTextSplitter(
|
||||||
chunk_overlap=chunk_overlap,
|
chunk_overlap=chunk_overlap,
|
||||||
chunk_size=chunk_size,
|
chunk_size=chunk_size,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ class ChromaComponent(CustomComponent):
|
||||||
description: str = "Implementation of Vector Store using Chroma"
|
description: str = "Implementation of Vector Store using Chroma"
|
||||||
documentation = "https://python.langchain.com/docs/integrations/vectorstores/chroma"
|
documentation = "https://python.langchain.com/docs/integrations/vectorstores/chroma"
|
||||||
beta: bool = True
|
beta: bool = True
|
||||||
|
icon = "Chroma"
|
||||||
|
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -28,7 +29,7 @@ class ChromaComponent(CustomComponent):
|
||||||
return {
|
return {
|
||||||
"collection_name": {"display_name": "Collection Name", "value": "langflow"},
|
"collection_name": {"display_name": "Collection Name", "value": "langflow"},
|
||||||
"persist": {"display_name": "Persist"},
|
"persist": {"display_name": "Persist"},
|
||||||
"persist_directory": {"display_name": "Persist Directory"},
|
"index_directory": {"display_name": "Persist Directory"},
|
||||||
"code": {"advanced": True, "display_name": "Code"},
|
"code": {"advanced": True, "display_name": "Code"},
|
||||||
"documents": {"display_name": "Documents", "is_list": True},
|
"documents": {"display_name": "Documents", "is_list": True},
|
||||||
"embedding": {"display_name": "Embedding"},
|
"embedding": {"display_name": "Embedding"},
|
||||||
|
|
@ -54,7 +55,7 @@ class ChromaComponent(CustomComponent):
|
||||||
persist: bool,
|
persist: bool,
|
||||||
embedding: Embeddings,
|
embedding: Embeddings,
|
||||||
chroma_server_ssl_enabled: bool,
|
chroma_server_ssl_enabled: bool,
|
||||||
persist_directory: Optional[str] = None,
|
index_directory: Optional[str] = None,
|
||||||
documents: Optional[List[Document]] = None,
|
documents: Optional[List[Document]] = None,
|
||||||
chroma_server_cors_allow_origins: Optional[str] = None,
|
chroma_server_cors_allow_origins: Optional[str] = None,
|
||||||
chroma_server_host: Optional[str] = None,
|
chroma_server_host: Optional[str] = None,
|
||||||
|
|
@ -66,7 +67,7 @@ class ChromaComponent(CustomComponent):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
- collection_name (str): The name of the collection.
|
- collection_name (str): The name of the collection.
|
||||||
- persist_directory (Optional[str]): The directory to persist the Vector Store to.
|
- index_directory (Optional[str]): The directory to persist the Vector Store to.
|
||||||
- chroma_server_ssl_enabled (bool): Whether to enable SSL for the Chroma server.
|
- chroma_server_ssl_enabled (bool): Whether to enable SSL for the Chroma server.
|
||||||
- persist (bool): Whether to persist the Vector Store or not.
|
- persist (bool): Whether to persist the Vector Store or not.
|
||||||
- embedding (Optional[Embeddings]): The embeddings to use for the Vector Store.
|
- embedding (Optional[Embeddings]): The embeddings to use for the Vector Store.
|
||||||
|
|
@ -85,7 +86,8 @@ 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 or None,
|
chroma_server_cors_allow_origins=chroma_server_cors_allow_origins
|
||||||
|
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,
|
||||||
|
|
@ -93,15 +95,25 @@ class ChromaComponent(CustomComponent):
|
||||||
)
|
)
|
||||||
|
|
||||||
# If documents, then we need to create a Chroma instance using .from_documents
|
# If documents, then we need to create a Chroma instance using .from_documents
|
||||||
|
|
||||||
|
# Check index_directory and expand it if it is a relative path
|
||||||
|
|
||||||
|
index_directory = self.resolve_path(index_directory)
|
||||||
|
|
||||||
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("If documents are provided, there must be at least one document.")
|
raise ValueError(
|
||||||
return Chroma.from_documents(
|
"If documents are provided, there must be at least one document."
|
||||||
|
)
|
||||||
|
chroma = Chroma.from_documents(
|
||||||
documents=documents, # type: ignore
|
documents=documents, # type: ignore
|
||||||
persist_directory=persist_directory if persist else None,
|
persist_directory=index_directory if persist else None,
|
||||||
collection_name=collection_name,
|
collection_name=collection_name,
|
||||||
embedding=embedding,
|
embedding=embedding,
|
||||||
client_settings=chroma_settings,
|
client_settings=chroma_settings,
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
return Chroma(persist_directory=persist_directory, client_settings=chroma_settings)
|
chroma = Chroma(
|
||||||
|
persist_directory=index_directory, client_settings=chroma_settings
|
||||||
|
)
|
||||||
|
return chroma
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ class ChromaSearchComponent(CustomComponent):
|
||||||
chroma_server_grpc_port=chroma_server_grpc_port or None,
|
chroma_server_grpc_port=chroma_server_grpc_port or None,
|
||||||
chroma_server_ssl_enabled=chroma_server_ssl_enabled,
|
chroma_server_ssl_enabled=chroma_server_ssl_enabled,
|
||||||
)
|
)
|
||||||
|
index_directory = self.resolve_path(index_directory)
|
||||||
chroma = Chroma(
|
chroma = Chroma(
|
||||||
embedding_function=embedding,
|
embedding_function=embedding,
|
||||||
collection_name=collection_name,
|
collection_name=collection_name,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
from collections import defaultdict, deque
|
from collections import defaultdict, deque
|
||||||
from typing import Dict, Generator, List, Type, Union
|
from typing import Dict, Generator, List, Optional, Type, Union
|
||||||
|
|
||||||
from langchain.chains.base import Chain
|
from langchain.chains.base import Chain
|
||||||
from langflow.graph.edge.base import ContractEdge
|
from langflow.graph.edge.base import ContractEdge
|
||||||
from langflow.graph.graph.constants import lazy_load_vertex_dict
|
from langflow.graph.graph.constants import lazy_load_vertex_dict
|
||||||
from langflow.graph.graph.utils import process_flow
|
from langflow.graph.graph.utils import process_flow
|
||||||
from langflow.graph.vertex.base import Vertex
|
from langflow.graph.vertex.base import Vertex
|
||||||
from langflow.graph.vertex.types import ChatVertex, FileToolVertex, LLMVertex, ToolkitVertex
|
from langflow.graph.vertex.types import (
|
||||||
|
ChatVertex,
|
||||||
|
FileToolVertex,
|
||||||
|
LLMVertex,
|
||||||
|
ToolkitVertex,
|
||||||
|
)
|
||||||
from langflow.interface.tools.constants import FILE_TOOLS
|
from langflow.interface.tools.constants import FILE_TOOLS
|
||||||
from langflow.utils import payload
|
from langflow.utils import payload
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
@ -71,6 +76,25 @@ class Graph:
|
||||||
return False
|
return False
|
||||||
return self.__repr__() == other.__repr__()
|
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:
|
||||||
|
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:
|
def _build_graph(self) -> None:
|
||||||
"""Builds the graph from the vertices and edges."""
|
"""Builds the graph from the vertices and edges."""
|
||||||
self.vertices = self._build_vertices()
|
self.vertices = self._build_vertices()
|
||||||
|
|
@ -127,7 +151,9 @@ 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(f"{vertex.vertex_type} is not connected to any other components")
|
raise ValueError(
|
||||||
|
f"{vertex.vertex_type} 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."""
|
||||||
|
|
@ -140,7 +166,11 @@ class Graph:
|
||||||
|
|
||||||
def get_vertex_edges(self, vertex_id: str) -> List[ContractEdge]:
|
def get_vertex_edges(self, vertex_id: str) -> List[ContractEdge]:
|
||||||
"""Returns a list of edges for a given vertex."""
|
"""Returns a list of edges for a given vertex."""
|
||||||
return [edge for edge in self.edges if edge.source_id == vertex_id or edge.target_id == vertex_id]
|
return [
|
||||||
|
edge
|
||||||
|
for edge in self.edges
|
||||||
|
if edge.source_id == vertex_id or edge.target_id == vertex_id
|
||||||
|
]
|
||||||
|
|
||||||
def get_vertices_with_target(self, vertex_id: str) -> List[Vertex]:
|
def get_vertices_with_target(self, vertex_id: str) -> List[Vertex]:
|
||||||
"""Returns the vertices connected to a vertex."""
|
"""Returns the vertices connected to a vertex."""
|
||||||
|
|
@ -178,7 +208,9 @@ 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("Graph contains a cycle, cannot perform topological sort")
|
raise ValueError(
|
||||||
|
"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:
|
||||||
|
|
@ -237,7 +269,9 @@ class Graph:
|
||||||
edges.append(ContractEdge(source, target, edge))
|
edges.append(ContractEdge(source, target, edge))
|
||||||
return edges
|
return edges
|
||||||
|
|
||||||
def _get_vertex_class(self, node_type: str, node_base_type: str, node_id: str) -> Type[Vertex]:
|
def _get_vertex_class(
|
||||||
|
self, node_type: str, node_base_type: str, node_id: str
|
||||||
|
) -> Type[Vertex]:
|
||||||
"""Returns the node class based on the node type."""
|
"""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]
|
||||||
|
|
@ -267,14 +301,18 @@ 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(vertex_type, vertex_base_type, vertex_data["id"])
|
VertexClass = self._get_vertex_class(
|
||||||
|
vertex_type, vertex_base_type, vertex_data["id"]
|
||||||
|
)
|
||||||
vertex_instance = VertexClass(vertex, graph=self)
|
vertex_instance = 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(self, vertex: Vertex, vertex_type: str) -> List[Vertex]:
|
def get_children_by_vertex_type(
|
||||||
|
self, vertex: Vertex, vertex_type: str
|
||||||
|
) -> List[Vertex]:
|
||||||
"""Returns the children of a vertex based on the vertex type."""
|
"""Returns the children of a vertex based on the vertex type."""
|
||||||
children = []
|
children = []
|
||||||
vertex_types = [vertex.data["type"]]
|
vertex_types = [vertex.data["type"]]
|
||||||
|
|
@ -286,20 +324,44 @@ 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([f"{edge.source_id} --> {edge.target_id}" for edge in self.edges])
|
edges_repr = "\n".join(
|
||||||
|
[f"{edge.source_id} --> {edge.target_id}" for edge in self.edges]
|
||||||
|
)
|
||||||
return f"Graph:\nNodes: {vertex_ids}\nConnections:\n{edges_repr}"
|
return f"Graph:\nNodes: {vertex_ids}\nConnections:\n{edges_repr}"
|
||||||
|
|
||||||
def layered_topological_sort(self):
|
def sort_up_to_vertex(self, vertex_id: str) -> "Graph":
|
||||||
in_degree = {vertex.id: 0 for vertex in self.vertices} # Initialize in-degrees
|
"""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))
|
||||||
|
|
||||||
|
edges = [edge for vertex in vertices for edge in vertex.edges]
|
||||||
|
|
||||||
|
return self.layered_topological_sort(vertices, edges)
|
||||||
|
|
||||||
|
def layered_topological_sort(
|
||||||
|
self,
|
||||||
|
vertices: Optional[List[Vertex]] = None,
|
||||||
|
edges: Optional[List[ContractEdge]] = None,
|
||||||
|
) -> List[List[str]]:
|
||||||
|
"""Performs a layered topological sort of the vertices in the graph."""
|
||||||
|
if vertices is None:
|
||||||
|
vertices = self.vertices
|
||||||
|
if edges is None:
|
||||||
|
edges = self.edges
|
||||||
|
|
||||||
|
in_degree = {vertex.id: 0 for vertex in vertices} # Initialize in-degrees
|
||||||
graph = defaultdict(list) # Adjacency list representation
|
graph = defaultdict(list) # Adjacency list representation
|
||||||
|
|
||||||
# Build graph and compute in-degrees
|
# Build graph and compute in-degrees
|
||||||
for edge in self.edges:
|
for edge in edges:
|
||||||
graph[edge.source_id].append(edge.target_id)
|
graph[edge.source_id].append(edge.target_id)
|
||||||
in_degree[edge.target_id] += 1
|
in_degree[edge.target_id] += 1
|
||||||
|
|
||||||
# Queue for vertices with no incoming edges
|
# Queue for vertices with no incoming edges
|
||||||
queue = deque(vertex.id for vertex in self.vertices if in_degree[vertex.id] == 0)
|
queue = deque(vertex.id for vertex in vertices if in_degree[vertex.id] == 0)
|
||||||
layers = []
|
layers = []
|
||||||
|
|
||||||
current_layer = 0
|
current_layer = 0
|
||||||
|
|
@ -314,9 +376,40 @@ class Graph:
|
||||||
if in_degree[neighbor] == 0:
|
if in_degree[neighbor] == 0:
|
||||||
queue.append(neighbor)
|
queue.append(neighbor)
|
||||||
current_layer += 1 # Next layer
|
current_layer += 1 # Next layer
|
||||||
|
new_layers = self.refine_layers(graph, layers)
|
||||||
|
return new_layers
|
||||||
|
|
||||||
return layers
|
def refine_layers(self, graph, initial_layers):
|
||||||
return layers
|
# Map each vertex to its current layer
|
||||||
return layers
|
vertex_to_layer = {}
|
||||||
return layers
|
for layer_index, layer in enumerate(initial_layers):
|
||||||
return layers
|
for vertex in layer:
|
||||||
|
vertex_to_layer[vertex] = layer_index
|
||||||
|
|
||||||
|
# Build the adjacency list for reverse lookup (dependencies)
|
||||||
|
|
||||||
|
refined_layers = [[] for _ in initial_layers] # Start with empty layers
|
||||||
|
new_layer_index_map = defaultdict(
|
||||||
|
int
|
||||||
|
) # Map each vertex to its highest dependency layer
|
||||||
|
|
||||||
|
for vertex_id, deps in graph.items():
|
||||||
|
for dep in deps:
|
||||||
|
new_layer_index_map[vertex_id] = (
|
||||||
|
max(new_layer_index_map[vertex_id], vertex_to_layer[dep]) - 1
|
||||||
|
)
|
||||||
|
|
||||||
|
for layer_index, layer in enumerate(initial_layers):
|
||||||
|
for vertex_id in layer:
|
||||||
|
# Place the vertex in the highest possible layer where its dependencies are met
|
||||||
|
new_layer_index = new_layer_index_map[vertex_id]
|
||||||
|
if new_layer_index > layer_index:
|
||||||
|
refined_layers[new_layer_index].append(vertex_id)
|
||||||
|
vertex_to_layer[vertex_id] = new_layer_index
|
||||||
|
else:
|
||||||
|
refined_layers[layer_index].append(vertex_id)
|
||||||
|
|
||||||
|
# Remove empty layers if any
|
||||||
|
refined_layers = [layer for layer in refined_layers if layer]
|
||||||
|
|
||||||
|
return refined_layers
|
||||||
|
|
|
||||||
|
|
@ -78,14 +78,18 @@ class Vertex:
|
||||||
):
|
):
|
||||||
if edge.target_id not in edge_results:
|
if edge.target_id not in edge_results:
|
||||||
edge_results[edge.target_id] = {}
|
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
|
return edge_results
|
||||||
|
|
||||||
def get_built_result(self):
|
def get_built_result(self):
|
||||||
# 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(self._built_object, UnbuiltObject):
|
if self.is_interface_component and not isinstance(
|
||||||
|
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
|
||||||
|
|
@ -95,7 +99,11 @@ class Vertex:
|
||||||
|
|
||||||
if isinstance(self._built_result, UnbuiltResult):
|
if isinstance(self._built_result, UnbuiltResult):
|
||||||
return {}
|
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:
|
def set_artifacts(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
@ -149,18 +157,29 @@ class Vertex:
|
||||||
self.data = self._data["data"]
|
self.data = self._data["data"]
|
||||||
self.output = self.data["node"]["base_classes"]
|
self.output = self.data["node"]["base_classes"]
|
||||||
self.pinned = self.data["node"].get("pinned", False)
|
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 = {
|
||||||
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.required_inputs = [
|
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 = [
|
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
|
# 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"]
|
||||||
|
|
@ -203,7 +222,11 @@ class Vertex:
|
||||||
if self.graph is None:
|
if self.graph is None:
|
||||||
raise ValueError("Graph not found")
|
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 {}
|
params = self.params.copy() if self.params else {}
|
||||||
|
|
||||||
for edge in self.edges:
|
for edge in self.edges:
|
||||||
|
|
@ -255,7 +278,11 @@ 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] = {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):
|
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:
|
||||||
|
|
@ -292,7 +319,12 @@ class Vertex:
|
||||||
|
|
||||||
self._built = True
|
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
|
# user_id is just for compatibility with the other build methods
|
||||||
inputs = inputs or {}
|
inputs = inputs or {}
|
||||||
# inputs = {key: value or "" for key, value in inputs.items()}
|
# inputs = {key: value or "" for key, value in inputs.items()}
|
||||||
|
|
@ -307,7 +339,9 @@ 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(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
|
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):
|
||||||
|
|
@ -335,7 +369,9 @@ 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(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
|
# 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:
|
||||||
|
|
@ -369,7 +405,9 @@ 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(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.
|
Iterates over a list of nodes, builds each and updates the params dictionary.
|
||||||
"""
|
"""
|
||||||
|
|
@ -421,7 +459,9 @@ class Vertex:
|
||||||
self._update_built_object_and_artifacts(result)
|
self._update_built_object_and_artifacts(result)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.exception(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):
|
def _update_built_object_and_artifacts(self, result):
|
||||||
"""
|
"""
|
||||||
|
|
@ -458,7 +498,7 @@ class Vertex:
|
||||||
requester: Optional["Vertex"] = None,
|
requester: Optional["Vertex"] = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
if self.pinned:
|
if self.pinned and self._built:
|
||||||
return self.get_requester_result(requester)
|
return self.get_requester_result(requester)
|
||||||
self._reset()
|
self._reset()
|
||||||
|
|
||||||
|
|
@ -480,9 +520,15 @@ class Vertex:
|
||||||
return self._built_object
|
return self._built_object
|
||||||
|
|
||||||
# Get the requester edge
|
# 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 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:
|
def add_edge(self, edge: "ContractEdge") -> None:
|
||||||
if edge not in self.edges:
|
if edge not in self.edges:
|
||||||
|
|
@ -502,7 +548,11 @@ 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 "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):
|
class StatefulVertex(Vertex):
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ from typing import Any, ClassVar, Optional
|
||||||
import emoji
|
import emoji
|
||||||
from cachetools import TTLCache, cachedmethod
|
from cachetools import TTLCache, cachedmethod
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
|
|
||||||
from langflow.interface.custom.code_parser import CodeParser
|
from langflow.interface.custom.code_parser import CodeParser
|
||||||
from langflow.interface.custom.eval import eval_custom_component_code
|
from langflow.interface.custom.eval import eval_custom_component_code
|
||||||
from langflow.utils import validate
|
from langflow.utils import validate
|
||||||
|
|
@ -21,7 +20,9 @@ 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] = "The name of the entrypoint function must be provided."
|
ERROR_FUNCTION_ENTRYPOINT_NAME_NULL: ClassVar[str] = (
|
||||||
|
"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"
|
||||||
|
|
@ -36,10 +37,6 @@ class Component:
|
||||||
else:
|
else:
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
# Validate the emoji at the icon field
|
|
||||||
if hasattr(self, "icon") and self.icon:
|
|
||||||
self.icon = self.validate_icon(self.icon)
|
|
||||||
|
|
||||||
def __setattr__(self, key, value):
|
def __setattr__(self, key, value):
|
||||||
if key == "_user_id" and hasattr(self, "_user_id"):
|
if key == "_user_id" and hasattr(self, "_user_id"):
|
||||||
warnings.warn("user_id is immutable and cannot be changed.")
|
warnings.warn("user_id is immutable and cannot be changed.")
|
||||||
|
|
@ -68,8 +65,8 @@ class Component:
|
||||||
|
|
||||||
return validate.create_function(self.code, self._function_entrypoint_name)
|
return validate.create_function(self.code, self._function_entrypoint_name)
|
||||||
|
|
||||||
def getattr_return_str(self, component, value):
|
def getattr_return_str(self, value):
|
||||||
value = getattr(component, value)
|
|
||||||
return str(value) if value else ""
|
return str(value) if value else ""
|
||||||
|
|
||||||
def build_template_config(self) -> dict:
|
def build_template_config(self) -> dict:
|
||||||
|
|
@ -89,7 +86,9 @@ class Component:
|
||||||
|
|
||||||
for attribute, func in attributes_func_mapping.items():
|
for attribute, func in attributes_func_mapping.items():
|
||||||
if hasattr(component_instance, attribute):
|
if hasattr(component_instance, attribute):
|
||||||
template_config[attribute] = func(component=component_instance, value=attribute)
|
value = getattr(component_instance, attribute)
|
||||||
|
if value is not None:
|
||||||
|
template_config[attribute] = func(value=value)
|
||||||
|
|
||||||
return template_config
|
return template_config
|
||||||
|
|
||||||
|
|
@ -97,11 +96,12 @@ class Component:
|
||||||
# we are going to use the emoji library to validate the emoji
|
# we are going to use the emoji library to validate the emoji
|
||||||
# emojis can be defined using the :emoji_name: syntax
|
# emojis can be defined using the :emoji_name: syntax
|
||||||
if not value.startswith(":") or not value.endswith(":"):
|
if not value.startswith(":") or not value.endswith(":"):
|
||||||
raise ValueError("Invalid emoji. Please use the :emoji_name: syntax.")
|
warnings.warn("Invalid emoji. Please use the :emoji_name: syntax.")
|
||||||
|
return value
|
||||||
emoji_value = emoji.emojize(value, variant="emoji_type")
|
emoji_value = emoji.emojize(value, variant="emoji_type")
|
||||||
if value == emoji_value:
|
if value == emoji_value:
|
||||||
raise ValueError(f"Invalid emoji. {value} is not a valid emoji.")
|
warnings.warn(f"Invalid emoji. {value} is not a valid emoji.")
|
||||||
|
return value
|
||||||
return emoji_value
|
return emoji_value
|
||||||
|
|
||||||
def build(self, *args: Any, **kwargs: Any) -> Any:
|
def build(self, *args: Any, **kwargs: Any) -> Any:
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import operator
|
import operator
|
||||||
|
from pathlib import Path
|
||||||
from typing import Any, Callable, ClassVar, List, Optional, Union
|
from typing import Any, Callable, ClassVar, List, Optional, Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from cachetools import TTLCache, cachedmethod
|
from cachetools import TTLCache, cachedmethod
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
|
|
||||||
from langflow.interface.custom.code_parser.utils import (
|
from langflow.interface.custom.code_parser.utils import (
|
||||||
extract_inner_type_from_generic_alias,
|
extract_inner_type_from_generic_alias,
|
||||||
extract_union_types_from_generic_alias,
|
extract_union_types_from_generic_alias,
|
||||||
|
|
@ -13,7 +13,11 @@ from langflow.interface.custom.code_parser.utils import (
|
||||||
from langflow.interface.custom.custom_component.component import Component
|
from langflow.interface.custom.custom_component.component import Component
|
||||||
from langflow.services.database.models.flow import Flow
|
from langflow.services.database.models.flow import Flow
|
||||||
from langflow.services.database.utils import session_getter
|
from langflow.services.database.utils import session_getter
|
||||||
from langflow.services.deps import get_credential_service, get_db_service, get_storage_service
|
from langflow.services.deps import (
|
||||||
|
get_credential_service,
|
||||||
|
get_db_service,
|
||||||
|
get_storage_service,
|
||||||
|
)
|
||||||
from langflow.services.storage.service import StorageService
|
from langflow.services.storage.service import StorageService
|
||||||
from langflow.utils import validate
|
from langflow.utils import validate
|
||||||
|
|
||||||
|
|
@ -42,6 +46,16 @@ class CustomComponent(Component):
|
||||||
self.cache = TTLCache(maxsize=1024, ttl=60)
|
self.cache = TTLCache(maxsize=1024, ttl=60)
|
||||||
super().__init__(**data)
|
super().__init__(**data)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def resolve_path(path: str) -> str:
|
||||||
|
"""Resolves the path to an absolute path."""
|
||||||
|
path_object = Path(path)
|
||||||
|
if path_object.parts[0] == "~":
|
||||||
|
path_object = path_object.expanduser()
|
||||||
|
elif path_object.is_relative_to("."):
|
||||||
|
path_object = path_object.resolve()
|
||||||
|
return str(path_object)
|
||||||
|
|
||||||
def get_full_path(self, path: str) -> str:
|
def get_full_path(self, path: str) -> str:
|
||||||
storage_svc: "StorageService" = get_storage_service()
|
storage_svc: "StorageService" = get_storage_service()
|
||||||
|
|
||||||
|
|
@ -78,7 +92,8 @@ class CustomComponent(Component):
|
||||||
detail={
|
detail={
|
||||||
"error": "Type hint Error",
|
"error": "Type hint Error",
|
||||||
"traceback": (
|
"traceback": (
|
||||||
"Prompt type is not supported in the build method." " Try using PromptTemplate instead."
|
"Prompt type is not supported in the build method."
|
||||||
|
" Try using PromptTemplate instead."
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -92,14 +107,20 @@ class CustomComponent(Component):
|
||||||
if not self.code:
|
if not self.code:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
component_classes = [cls for cls in self.tree["classes"] if self.code_class_base_inheritance in cls["bases"]]
|
component_classes = [
|
||||||
|
cls
|
||||||
|
for cls in self.tree["classes"]
|
||||||
|
if self.code_class_base_inheritance in cls["bases"]
|
||||||
|
]
|
||||||
if not component_classes:
|
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 for method in component_class["methods"] if method["name"] == self.function_entrypoint_name
|
method
|
||||||
|
for method in component_class["methods"]
|
||||||
|
if method["name"] == self.function_entrypoint_name
|
||||||
]
|
]
|
||||||
|
|
||||||
return build_methods[0] if build_methods else {}
|
return build_methods[0] if build_methods else {}
|
||||||
|
|
@ -112,7 +133,10 @@ class CustomComponent(Component):
|
||||||
return_type = build_method["return_type"]
|
return_type = build_method["return_type"]
|
||||||
|
|
||||||
# If list or List is in the return type, then we remove it and return the inner type
|
# If list or List is in the return type, then we remove it and return the inner type
|
||||||
if hasattr(return_type, "__origin__") and return_type.__origin__ in [list, List]:
|
if hasattr(return_type, "__origin__") and return_type.__origin__ in [
|
||||||
|
list,
|
||||||
|
List,
|
||||||
|
]:
|
||||||
return_type = extract_inner_type_from_generic_alias(return_type)
|
return_type = extract_inner_type_from_generic_alias(return_type)
|
||||||
|
|
||||||
# If the return type is not a Union, then we just return it as a list
|
# If the return type is not a Union, then we just return it as a list
|
||||||
|
|
@ -153,7 +177,9 @@ 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(user_id=self._user_id or "", name=name, session=session)
|
return credential_service.get_credential(
|
||||||
|
user_id=self._user_id or "", name=name, session=session
|
||||||
|
)
|
||||||
|
|
||||||
return get_credential
|
return get_credential
|
||||||
|
|
||||||
|
|
@ -163,7 +189,9 @@ 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(user_id=self._user_id, session=session)
|
return credential_service.list_credentials(
|
||||||
|
user_id=self._user_id, session=session
|
||||||
|
)
|
||||||
|
|
||||||
def index(self, value: int = 0):
|
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."""
|
||||||
|
|
@ -214,7 +242,11 @@ class CustomComponent(Component):
|
||||||
if flow_id:
|
if flow_id:
|
||||||
flow = session.query(Flow).get(flow_id)
|
flow = session.query(Flow).get(flow_id)
|
||||||
elif flow_name:
|
elif flow_name:
|
||||||
flow = (session.query(Flow).filter(Flow.name == flow_name).filter(Flow.user_id == self.user_id)).first()
|
flow = (
|
||||||
|
session.query(Flow)
|
||||||
|
.filter(Flow.name == flow_name)
|
||||||
|
.filter(Flow.user_id == self.user_id)
|
||||||
|
).first()
|
||||||
else:
|
else:
|
||||||
raise ValueError("Either flow_name or flow_id must be provided")
|
raise ValueError("Either flow_name or flow_id must be provided")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,9 @@ class ChatService(Service):
|
||||||
"""Send the last chat message to the client."""
|
"""Send the last chat message to the client."""
|
||||||
client_id = self.chat_cache.current_client_id
|
client_id = self.chat_cache.current_client_id
|
||||||
if client_id in self.active_connections:
|
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:
|
if chat_response.is_bot:
|
||||||
# Process FileResponse
|
# Process FileResponse
|
||||||
if isinstance(chat_response, FileResponse):
|
if isinstance(chat_response, FileResponse):
|
||||||
|
|
@ -86,7 +88,9 @@ class ChatService(Service):
|
||||||
data_type=self.last_cached_object_dict["type"],
|
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):
|
async def connect(self, client_id: str, websocket: WebSocket):
|
||||||
self.active_connections[client_id] = websocket
|
self.active_connections[client_id] = websocket
|
||||||
|
|
@ -209,7 +213,9 @@ class ChatService(Service):
|
||||||
await self.process_message(client_id, payload, build_result)
|
await self.process_message(client_id, payload, build_result)
|
||||||
|
|
||||||
else:
|
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:
|
except Exception as exc:
|
||||||
# Handle any exceptions that might occur
|
# Handle any exceptions that might occur
|
||||||
logger.exception(f"Error handling websocket: {exc}")
|
logger.exception(f"Error handling websocket: {exc}")
|
||||||
|
|
@ -241,6 +247,12 @@ class ChatService(Service):
|
||||||
"""
|
"""
|
||||||
return self.cache_service.get(client_id)
|
return self.cache_service.get(client_id)
|
||||||
|
|
||||||
|
def clear_cache(self, client_id: str):
|
||||||
|
"""
|
||||||
|
Clear the cache for a client.
|
||||||
|
"""
|
||||||
|
self.cache_service.clear(client_id)
|
||||||
|
|
||||||
|
|
||||||
def dict_to_markdown_table(my_dict):
|
def dict_to_markdown_table(my_dict):
|
||||||
markdown_table = "| Key | Value |\n|---|---|\n"
|
markdown_table = "| Key | Value |\n|---|---|\n"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ from pydantic import BaseModel, Field, field_serializer, validator
|
||||||
|
|
||||||
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(default_factory=datetime.now, alias="timestamp")
|
timestamp: Optional[datetime] = Field(
|
||||||
|
default_factory=datetime.now, alias="timestamp"
|
||||||
|
)
|
||||||
source: str
|
source: str
|
||||||
target: str
|
target: str
|
||||||
target_args: dict
|
target_args: dict
|
||||||
|
|
@ -89,18 +91,13 @@ class VertexBuildModel(BaseModel):
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
# create a function that turns dicts into a
|
class VertexBuildResponseModel(VertexBuildModel):
|
||||||
# dict like this:
|
|
||||||
# my_map_dict = {
|
@field_serializer("data", "artifacts")
|
||||||
# "key": [
|
def serialize_dict(v):
|
||||||
# 1, 2, 3
|
return v
|
||||||
# ],
|
|
||||||
# "value": [
|
|
||||||
# "one", "two", "three"
|
|
||||||
# ]
|
|
||||||
# }
|
|
||||||
# so map has a "key" and a "value" list
|
|
||||||
# containing the keys and values of the dict
|
|
||||||
def to_map(value: dict):
|
def to_map(value: dict):
|
||||||
keys = list(value.keys())
|
keys = list(value.keys())
|
||||||
values = list(value.values())
|
values = list(value.values())
|
||||||
|
|
@ -108,13 +105,13 @@ def to_map(value: dict):
|
||||||
|
|
||||||
|
|
||||||
class VertexBuildMapModel(BaseModel):
|
class VertexBuildMapModel(BaseModel):
|
||||||
vertex_builds: dict[str, list[VertexBuildModel]]
|
vertex_builds: dict[str, list[VertexBuildResponseModel]]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_list_of_dicts(cls, vertex_build_dicts):
|
def from_list_of_dicts(cls, vertex_build_dicts):
|
||||||
vertex_build_map = {}
|
vertex_build_map = {}
|
||||||
for vertex_build_dict in vertex_build_dicts:
|
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:
|
if vertex_build.id not in vertex_build_map:
|
||||||
vertex_build_map[vertex_build.id] = []
|
vertex_build_map[vertex_build.id] = []
|
||||||
vertex_build_map[vertex_build.id].append(vertex_build)
|
vertex_build_map[vertex_build.id].append(vertex_build)
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import aiofiles
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from .service import StorageService
|
from .service import StorageService
|
||||||
|
|
||||||
|
|
||||||
class LocalStorageService(StorageService):
|
class LocalStorageService(StorageService):
|
||||||
"""A service class for handling local storage operations."""
|
"""A service class for handling local storage operations without aiofiles."""
|
||||||
|
|
||||||
def __init__(self, session_service, settings_service):
|
def __init__(self, session_service, settings_service):
|
||||||
"""Initialize the local storage service with session and settings services."""
|
"""Initialize the local storage service with session and settings services."""
|
||||||
|
|
@ -35,8 +34,8 @@ class LocalStorageService(StorageService):
|
||||||
file_path = folder_path / file_name
|
file_path = folder_path / file_name
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with aiofiles.open(file_path, "wb") as f:
|
with open(file_path, "wb") as f:
|
||||||
await f.write(data)
|
f.write(data)
|
||||||
logger.info(f"File {file_name} saved successfully in flow {flow_id}.")
|
logger.info(f"File {file_name} saved successfully in flow {flow_id}.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error saving file {file_name} in flow {flow_id}: {e}")
|
logger.error(f"Error saving file {file_name} in flow {flow_id}: {e}")
|
||||||
|
|
@ -56,9 +55,9 @@ class LocalStorageService(StorageService):
|
||||||
logger.warning(f"File {file_name} not found in flow {flow_id}.")
|
logger.warning(f"File {file_name} not found in flow {flow_id}.")
|
||||||
raise FileNotFoundError(f"File {file_name} not found in flow {flow_id}")
|
raise FileNotFoundError(f"File {file_name} not found in flow {flow_id}")
|
||||||
|
|
||||||
async with aiofiles.open(file_path, "rb") as f:
|
with open(file_path, "rb") as f:
|
||||||
logger.info(f"File {file_name} retrieved successfully from flow {flow_id}.")
|
logger.info(f"File {file_name} retrieved successfully from flow {flow_id}.")
|
||||||
return await f.read()
|
return f.read()
|
||||||
|
|
||||||
async def list_files(self, flow_id: str):
|
async def list_files(self, flow_id: str):
|
||||||
"""
|
"""
|
||||||
|
|
@ -89,8 +88,10 @@ class LocalStorageService(StorageService):
|
||||||
file_path.unlink()
|
file_path.unlink()
|
||||||
logger.info(f"File {file_name} deleted successfully from flow {flow_id}.")
|
logger.info(f"File {file_name} deleted successfully from flow {flow_id}.")
|
||||||
else:
|
else:
|
||||||
logger.warning(f"Attempted to delete non-existent file {file_name} in flow {flow_id}.")
|
logger.warning(
|
||||||
|
f"Attempted to delete non-existent file {file_name} in flow {flow_id}."
|
||||||
|
)
|
||||||
|
|
||||||
def teardown(self):
|
def teardown(self):
|
||||||
"""Perform any cleanup operations when the service is being torn down."""
|
"""Perform any cleanup operations when the service is being torn down."""
|
||||||
pass # No specific teardown actions required for local storage at the moment.
|
pass # No specific teardown actions required for local
|
||||||
|
|
|
||||||
|
|
@ -406,6 +406,8 @@ export default function ParameterComponent({
|
||||||
data-testid={"textarea-" + data.node.template[name].name}
|
data-testid={"textarea-" + data.node.template[name].name}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
<div className="mt-2 flex w-full items-center">
|
||||||
|
<div className="w-5/6 flex-grow">
|
||||||
<InputComponent
|
<InputComponent
|
||||||
id={"input-" + index}
|
id={"input-" + index}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
@ -413,6 +415,18 @@ export default function ParameterComponent({
|
||||||
value={data.node?.template[name].value ?? ""}
|
value={data.node?.template[name].value ?? ""}
|
||||||
onChange={handleOnNewValue}
|
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>
|
</div>
|
||||||
) : left === true && type === "bool" ? (
|
) : left === true && type === "bool" ? (
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { NodeToolbar } from "reactflow";
|
import { NodeToolbar } from "reactflow";
|
||||||
import ShadTooltip from "../../components/ShadTooltipComponent";
|
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||||
import Tooltip from "../../components/TooltipComponent";
|
import Tooltip from "../../components/TooltipComponent";
|
||||||
|
|
@ -112,6 +112,39 @@ export default function GenericNode({
|
||||||
|
|
||||||
const nameEditable = data.node?.flow || data.type === "CustomComponent";
|
const nameEditable = data.node?.flow || data.type === "CustomComponent";
|
||||||
|
|
||||||
|
const emojiRegex = /\p{Emoji}/u;
|
||||||
|
const isEmoji = emojiRegex.test(data?.node?.icon!);
|
||||||
|
|
||||||
|
const iconNodeRender = useCallback(() => {
|
||||||
|
const iconElement = data?.node?.icon;
|
||||||
|
const iconColor = nodeColors[types[data.type]];
|
||||||
|
const iconName =
|
||||||
|
iconElement || (data.node?.flow ? "group_components" : name);
|
||||||
|
const iconClassName = `generic-node-icon ${
|
||||||
|
!showNode ? "absolute inset-x-6 h-12 w-12" : ""
|
||||||
|
}`;
|
||||||
|
|
||||||
|
if (iconElement && isEmoji) {
|
||||||
|
return nodeIconFragment(iconElement);
|
||||||
|
} else {
|
||||||
|
return checkNodeIconFragment(iconColor, iconName, iconClassName);
|
||||||
|
}
|
||||||
|
}, [data, isEmoji, name, showNode]);
|
||||||
|
|
||||||
|
const nodeIconFragment = (icon) => {
|
||||||
|
return <span className="text-lg">{icon}</span>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkNodeIconFragment = (iconColor, iconName, iconClassName) => {
|
||||||
|
return (
|
||||||
|
<IconComponent
|
||||||
|
name={iconName}
|
||||||
|
className={iconClassName}
|
||||||
|
iconColor={iconColor}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<NodeToolbar>
|
<NodeToolbar>
|
||||||
|
|
@ -164,19 +197,7 @@ export default function GenericNode({
|
||||||
(!showNode && "justify-center")
|
(!showNode && "justify-center")
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{data?.node?.icon ? (
|
{iconNodeRender()}
|
||||||
<span className="text-lg">{data?.node?.icon}</span>
|
|
||||||
) : (
|
|
||||||
<IconComponent
|
|
||||||
name={data.node?.flow ? "group_components" : name}
|
|
||||||
className={
|
|
||||||
"generic-node-icon " +
|
|
||||||
(!showNode ? "absolute inset-x-6 h-12 w-12" : "")
|
|
||||||
}
|
|
||||||
iconColor={`${nodeColors[types[data.type]]}`}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{showNode && (
|
{showNode && (
|
||||||
<div className="generic-node-tooltip-div">
|
<div className="generic-node-tooltip-div">
|
||||||
{nameEditable && inputName ? (
|
{nameEditable && inputName ? (
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ export default function ChatInput({
|
||||||
: "bg-chat-send text-background"
|
: "bg-chat-send text-background"
|
||||||
)}
|
)}
|
||||||
disabled={lockChat}
|
disabled={lockChat}
|
||||||
onClick={(): void => sendMessage()}
|
onClick={(): void => sendMessage(repeat)}
|
||||||
>
|
>
|
||||||
{lockChat ? (
|
{lockChat ? (
|
||||||
<IconComponent
|
<IconComponent
|
||||||
|
|
|
||||||
|
|
@ -680,5 +680,5 @@ export const LANGFLOW_SUPPORTED_TYPES = new Set([
|
||||||
|
|
||||||
export const priorityFields = new Set(["code", "template"]);
|
export const priorityFields = new Set(["code", "template"]);
|
||||||
|
|
||||||
export const INPUT_TYPES = new Set(["ChatInput", "TextInput", "FileLoader"]);
|
export const INPUT_TYPES = new Set(["ChatInput", "TextInput"]);
|
||||||
export const OUTPUT_TYPES = new Set(["ChatOutput"]);
|
export const OUTPUT_TYPES = new Set(["ChatOutput"]);
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,11 @@ export async function buildVertices({
|
||||||
for (let i = 0; i < verticesOrder.length; i += 1) {
|
for (let i = 0; i < verticesOrder.length; i += 1) {
|
||||||
const innerArray = verticesOrder[i];
|
const innerArray = verticesOrder[i];
|
||||||
const idIndex = innerArray.indexOf(nodeId);
|
const idIndex = innerArray.indexOf(nodeId);
|
||||||
|
|
||||||
if (idIndex !== -1) {
|
if (idIndex !== -1) {
|
||||||
// If the targetId is found in the inner array, cut the array before the id
|
// If there's a nodeId, we want to run just that component and not the entire layer
|
||||||
vertices.push(innerArray.slice(0, idIndex + 1));
|
// because a layer contains dependencies for the next layer
|
||||||
|
// and we are stopping at the layer that contains the nodeId
|
||||||
|
vertices.push([innerArray[idIndex]]);
|
||||||
break; // Stop searching after finding the first occurrence
|
break; // Stop searching after finding the first occurrence
|
||||||
}
|
}
|
||||||
// If the targetId is not found, include the entire inner array
|
// If the targetId is not found, include the entire inner array
|
||||||
|
|
@ -39,11 +40,14 @@ export async function buildVertices({
|
||||||
vertices = verticesOrder;
|
vertices = verticesOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set each vertex state to building
|
||||||
|
|
||||||
const buildResults: Array<boolean> = [];
|
const buildResults: Array<boolean> = [];
|
||||||
for (let i = 0; i < vertices.length; i += 1) {
|
for (let i = 0; i < vertices.length; i += 1) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
vertices[i].map(async (id) => {
|
vertices[i].map(async (id) => {
|
||||||
try {
|
try {
|
||||||
|
// Set vertex state to building
|
||||||
const buildRes = await postBuildVertex(flowId, id);
|
const buildRes = await postBuildVertex(flowId, id);
|
||||||
const buildData: VertexBuildTypeAPI = buildRes.data;
|
const buildData: VertexBuildTypeAPI = buildRes.data;
|
||||||
if (onBuildUpdate) {
|
if (onBuildUpdate) {
|
||||||
|
|
|
||||||
|
|
@ -119,13 +119,7 @@ export function groupByFamily(
|
||||||
display_name?: string;
|
display_name?: string;
|
||||||
}> = [];
|
}> = [];
|
||||||
let checkedNodes = new Map();
|
let checkedNodes = new Map();
|
||||||
const excludeTypes = new Set([
|
const excludeTypes = new Set(["bool", "float", "code", "file", "int"]);
|
||||||
"bool",
|
|
||||||
"float",
|
|
||||||
"code",
|
|
||||||
"file",
|
|
||||||
"int",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const checkBaseClass = (template: TemplateVariableType) => {
|
const checkBaseClass = (template: TemplateVariableType) => {
|
||||||
return (
|
return (
|
||||||
|
|
@ -134,12 +128,11 @@ export function groupByFamily(
|
||||||
((!excludeTypes.has(template.type) &&
|
((!excludeTypes.has(template.type) &&
|
||||||
baseClassesSet.has(template.type)) ||
|
baseClassesSet.has(template.type)) ||
|
||||||
(template.input_types &&
|
(template.input_types &&
|
||||||
template.input_types.some((inputType) => {
|
template.input_types.some((inputType) =>
|
||||||
baseClassesSet.has(inputType);
|
baseClassesSet.has(inputType)
|
||||||
})))
|
)))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
console.log(flow);
|
|
||||||
|
|
||||||
if (flow) {
|
if (flow) {
|
||||||
// se existir o flow
|
// se existir o flow
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue