Remove console.log

This commit is contained in:
igorrCarvalho 2024-02-28 18:10:48 -03:00
commit 25d3b96600
88 changed files with 1621 additions and 1797 deletions

View file

@ -3,7 +3,15 @@ name: lint
on:
push:
branches: [main]
paths:
- "poetry.lock"
- "pyproject.toml"
- "src/backend/**"
pull_request:
paths:
- "poetry.lock"
- "pyproject.toml"
- "src/backend/**"
env:
POETRY_VERSION: "1.7.0"

View file

@ -3,8 +3,16 @@ name: test
on:
push:
branches: [main]
paths:
- "poetry.lock"
- "pyproject.toml"
- "src/backend/**"
pull_request:
branches: [dev]
paths:
- "poetry.lock"
- "pyproject.toml"
- "src/backend/**"
env:
POETRY_VERSION: "1.5.0"

View file

@ -1,10 +1,11 @@
import time
import uuid
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING, Annotated, Optional
from fastapi import (
APIRouter,
BackgroundTasks,
Body,
Depends,
HTTPException,
WebSocket,
@ -21,6 +22,7 @@ from langflow.api.utils import (
format_exception_message,
)
from langflow.api.v1.schemas import (
InputValueRequest,
ResultDataResponse,
StreamData,
VertexBuildResponse,
@ -32,8 +34,9 @@ from langflow.services.auth.utils import (
get_current_user_for_websocket,
)
from langflow.services.chat.service import ChatService
from langflow.services.deps import get_chat_service, get_session
from langflow.services.deps import get_chat_service, get_session, get_session_service
from langflow.services.monitor.utils import log_vertex_build
from langflow.services.session.service import SessionService
if TYPE_CHECKING:
from langflow.graph.vertex.types import ChatVertex
@ -138,10 +141,12 @@ async def build_vertex(
flow_id: str,
vertex_id: str,
background_tasks: BackgroundTasks,
inputs: Annotated[InputValueRequest, Body(embed=True)] = None,
chat_service: "ChatService" = Depends(get_chat_service),
current_user=Depends(get_current_active_user),
):
"""Build a vertex instead of the entire graph."""
{"inputs": {"input_value": "some value"}}
start_time = time.perf_counter()
try:
start_time = time.perf_counter()
@ -162,7 +167,8 @@ async def build_vertex(
vertex = graph.get_vertex(vertex_id)
try:
if not vertex.pinned or not vertex._built:
await vertex.build(user_id=current_user.id)
inputs_dict = inputs.model_dump() if inputs else {}
await vertex.build(user_id=current_user.id, inputs=inputs_dict)
if vertex.result is not None:
params = vertex._built_object_repr()
@ -175,7 +181,7 @@ async def build_vertex(
result_data_response = ResultDataResponse(**result_dict.model_dump())
except Exception as exc:
logger.error(f"Error building vertex: {exc}")
logger.exception(f"Error building vertex: {exc}")
params = format_exception_message(exc)
valid = False
result_data_response = ResultDataResponse(results={})
@ -185,15 +191,16 @@ async def build_vertex(
chat_service.clear_cache(flow_id)
# Log the vertex build
background_tasks.add_task(
log_vertex_build,
flow_id=flow_id,
vertex_id=vertex_id,
valid=valid,
params=params,
data=result_data_response,
artifacts=artifacts,
)
if not vertex.will_stream:
background_tasks.add_task(
log_vertex_build,
flow_id=flow_id,
vertex_id=vertex_id,
valid=valid,
params=params,
data=result_data_response,
artifacts=artifacts,
)
timedelta = time.perf_counter() - start_time
duration = format_elapsed_time(timedelta)
@ -226,39 +233,56 @@ async def build_vertex(
async def build_vertex_stream(
flow_id: str,
vertex_id: str,
session_id: Optional[str] = None,
chat_service: "ChatService" = Depends(get_chat_service),
session_service: "SessionService" = Depends(get_session_service),
):
"""Build a vertex instead of the entire graph."""
try:
async def stream_vertex():
try:
cache = chat_service.get_cache(flow_id)
if not cache:
# If there's no cache
raise ValueError(f"No cache found for {flow_id}.")
if not session_id:
cache = chat_service.get_cache(flow_id)
if not cache:
# If there's no cache
raise ValueError(f"No cache found for {flow_id}.")
else:
graph = cache.get("result")
else:
graph = cache.get("result")
session_data = await session_service.load_session(session_id)
graph, artifacts = session_data if session_data else (None, None)
if not graph:
raise ValueError(f"No graph found for {flow_id}.")
vertex: "ChatVertex" = graph.get_vertex(vertex_id)
if not hasattr(vertex, "stream"):
raise ValueError(f"Vertex {vertex_id} does not support streaming")
if not vertex.pinned or not vertex._built:
if isinstance(vertex._built_result, str) and vertex._built_result:
stream_data = StreamData(
event="message",
data={"message": f"Streaming vertex {vertex_id}"},
)
yield str(stream_data)
stream_data = StreamData(
event="message",
data={"chunk": vertex._built_result},
)
yield str(stream_data)
elif not vertex.pinned or not vertex._built:
logger.debug(f"Streaming vertex {vertex_id}")
stream_data = StreamData(
event="message",
data={"message": f"Streaming vertex {vertex_id}"},
)
yield str(stream_data)
number_of_chunks = 0
async for chunk in vertex.stream():
stream_data = StreamData(
event="message",
data={"chunk": chunk},
)
number_of_chunks += 1
yield str(stream_data)
logger.debug(f"Number of chunks: {number_of_chunks}")
elif vertex.result is not None:
stream_data = StreamData(
event="message",

View file

@ -228,6 +228,7 @@ async def run_flow_with_caching(
flow_id: str,
inputs: Optional[Union[List[dict], dict]] = None,
tweaks: Optional[dict] = None,
stream: Annotated[bool, Body(embed=True)] = False, # noqa: F821
session_id: Annotated[Union[None, str], Body(embed=True)] = None, # noqa: F821
api_key_user: User = Depends(api_key_security),
session_service: SessionService = Depends(get_session_service),
@ -239,13 +240,14 @@ async def run_flow_with_caching(
task_result: Any = None
if not graph:
raise ValueError("Graph not found in the session")
task_result = await run_graph(
task_result, session_id = await run_graph(
graph=graph,
flow_id=flow_id,
session_id=session_id,
inputs=inputs,
artifacts=artifacts,
session_service=session_service,
stream=stream,
)
else:
@ -263,13 +265,14 @@ async def run_flow_with_caching(
raise ValueError(f"Flow {flow_id} has no data")
graph_data = flow.data
graph_data = process_tweaks(graph_data, tweaks)
task_result = await run_graph(
task_result, session_id = await run_graph(
graph=graph_data,
flow_id=flow_id,
session_id=session_id,
inputs=inputs,
artifacts={},
session_service=session_service,
stream=stream,
)
return RunResponse(outputs=task_result, session_id=session_id)

View file

@ -261,3 +261,7 @@ class VertexBuildResponse(BaseModel):
class VerticesBuiltResponse(BaseModel):
vertices: List[VertexBuildResponse]
class InputValueRequest(BaseModel):
input_value: str

View file

@ -16,7 +16,8 @@ from langflow.field_typing.range_spec import RangeSpec
class ConversationalAgent(CustomComponent):
display_name: str = "OpenAI Conversational Agent"
description: str = "Conversational Agent that can use OpenAI's function calling API"
icon = "OpenAI"
def build_config(self):
openai_function_models = [
"gpt-4-turbo-preview",

View file

@ -20,7 +20,10 @@ class RetrievalQAComponent(CustomComponent):
"input_key": {"display_name": "Input Key", "advanced": True},
"output_key": {"display_name": "Output Key", "advanced": True},
"return_source_documents": {"display_name": "Return Source Documents"},
"inputs": {"display_name": "Input", "input_types": ["Text", "Document"]},
"input_value": {
"display_name": "Input",
"input_types": ["Text", "Document"],
},
}
def build(

View file

@ -1,6 +1,6 @@
from concurrent import futures
from pathlib import Path
from typing import Any, Dict, List
from typing import Any, Dict, List, Optional
from langflow import CustomComponent
from langflow.schema import Record
@ -12,21 +12,30 @@ class GatherRecordsComponent(CustomComponent):
def build_config(self) -> Dict[str, Any]:
return {
"path": {"display_name": "Path"},
"types": {
"display_name": "Types",
"info": "File types to load. Leave empty to load all types.",
},
"depth": {"display_name": "Depth", "info": "Depth to search for files."},
"max_concurrency": {"display_name": "Max Concurrency", "advanced": True},
"load_hidden": {
"display_name": "Load Hidden Files",
"value": False,
"display_name": "Load Hidden",
"advanced": True,
"info": "If true, hidden files will be loaded.",
},
"max_concurrency": {
"display_name": "Max Concurrency",
"value": 10,
"recursive": {
"display_name": "Recursive",
"advanced": True,
"info": "If true, the search will be recursive.",
},
"silent_errors": {
"display_name": "Silent Errors",
"advanced": True,
"info": "If true, errors will not raise an exception.",
},
"path": {"display_name": "Local Directory"},
"recursive": {"display_name": "Recursive", "value": True, "advanced": True},
"use_multithreading": {
"display_name": "Use Multithreading",
"value": True,
"advanced": True,
},
}
@ -61,7 +70,9 @@ class GatherRecordsComponent(CustomComponent):
glob = "**/*" if recursive else "*"
paths = walk_level(path_obj, depth) if depth else path_obj.glob(glob)
file_paths = [str(p) for p in paths if p.is_file() and match_types(p) and is_not_hidden(p)]
file_paths = [
str(p) for p in paths if p.is_file() and match_types(p) and is_not_hidden(p)
]
return file_paths
@ -91,13 +102,20 @@ class GatherRecordsComponent(CustomComponent):
use_multithreading: bool,
) -> List[Record]:
if use_multithreading:
records = self.parallel_load_records(file_paths, silent_errors, max_concurrency)
records = self.parallel_load_records(
file_paths, silent_errors, max_concurrency
)
else:
records = [self.parse_file_to_record(file_path, silent_errors) for file_path in file_paths]
records = [
self.parse_file_to_record(file_path, silent_errors)
for file_path in file_paths
]
records = list(filter(None, records))
return records
def parallel_load_records(self, file_paths: List[str], silent_errors: bool, max_concurrency: int) -> List[Record]:
def parallel_load_records(
self, file_paths: List[str], silent_errors: bool, max_concurrency: int
) -> List[Record]:
with futures.ThreadPoolExecutor(max_workers=max_concurrency) as executor:
loaded_files = executor.map(
lambda file_path: self.parse_file_to_record(file_path, silent_errors),
@ -108,7 +126,7 @@ class GatherRecordsComponent(CustomComponent):
def build(
self,
path: str,
types: List[str] = None,
types: Optional[List[str]] = None,
depth: int = 0,
max_concurrency: int = 2,
load_hidden: bool = False,
@ -116,14 +134,23 @@ class GatherRecordsComponent(CustomComponent):
silent_errors: bool = False,
use_multithreading: bool = True,
) -> List[Record]:
if types is None:
types = []
resolved_path = self.resolve_path(path)
file_paths = self.retrieve_file_paths(resolved_path, types, load_hidden, recursive, depth)
file_paths = self.retrieve_file_paths(
resolved_path, types, load_hidden, recursive, depth
)
loaded_records = []
if use_multithreading:
loaded_records = self.parallel_load_records(file_paths, silent_errors, max_concurrency)
loaded_records = self.parallel_load_records(
file_paths, silent_errors, max_concurrency
)
else:
loaded_records = [self.parse_file_to_record(file_path, silent_errors) for file_path in file_paths]
loaded_records = [
self.parse_file_to_record(file_path, silent_errors)
for file_path in file_paths
]
loaded_records = list(filter(None, loaded_records))
self.status = loaded_records
return loaded_records

View file

@ -9,6 +9,7 @@ class HuggingFaceEmbeddingsComponent(CustomComponent):
documentation = (
"https://python.langchain.com/docs/modules/data_connection/text_embedding/integrations/sentence_transformers"
)
icon="HuggingFace"
def build_config(self):
return {

View file

@ -9,6 +9,8 @@ class HuggingFaceInferenceAPIEmbeddingsComponent(CustomComponent):
display_name = "HuggingFaceInferenceAPIEmbeddings"
description = "HuggingFace sentence_transformers embedding models, API version."
documentation = "https://github.com/huggingface/text-embeddings-inference"
icon="HuggingFace"
def build_config(self):
return {

View file

@ -1,7 +1,7 @@
from typing import Optional, Union
from langflow.components.io.base.chat import ChatComponent
from langflow.field_typing import Text
from langflow.io.schema import ChatComponent
from langflow.schema import Record

View file

@ -1,7 +1,7 @@
from typing import Optional, Union
from langflow.components.io.base.chat import ChatComponent
from langflow.field_typing import Text
from langflow.io.schema import ChatComponent
from langflow.schema import Record

View file

@ -12,7 +12,7 @@ class MessageHistoryComponent(CustomComponent):
def build_config(self):
return {
"sender": {
"options": ["Machine", "User"],
"options": ["Machine", "User", "Machine and User"],
"display_name": "Sender Type",
},
"sender_name": {"display_name": "Sender Name"},
@ -38,6 +38,8 @@ class MessageHistoryComponent(CustomComponent):
session_id: Optional[str] = None,
n_messages: int = 5,
) -> List[Record]:
if sender == "Machine and User":
sender = None
messages = get_messages(
sender=sender,
sender_name=sender_name,

View file

@ -9,11 +9,11 @@ class TextInput(CustomComponent):
description = "Used to pass text input to the next component."
field_config = {
"value": {"display_name": "Value", "multiline": True},
"input_value": {"display_name": "Value", "multiline": True},
}
def build(self, value: Optional[str] = "") -> Text:
self.status = value
if not value:
value = ""
return value
def build(self, input_value: Optional[str] = "") -> Text:
self.status = input_value
if not input_value:
input_value = ""
return input_value

View file

@ -10,6 +10,8 @@ from langflow import CustomComponent
class AmazonBedrockComponent(CustomComponent):
display_name: str = "Amazon Bedrock"
description: str = "LLM model from Amazon Bedrock."
icon = "Amazon"
def build_config(self):
return {

View file

@ -10,6 +10,7 @@ from langflow import CustomComponent
class AnthropicLLM(CustomComponent):
display_name: str = "AnthropicLLM"
description: str = "Anthropic Chat&Completion large language models."
icon ="Anthropic"
def build_config(self):
return {

View file

@ -10,6 +10,8 @@ from langflow.field_typing import BaseLanguageModel, NestedDict
class AnthropicComponent(CustomComponent):
display_name = "Anthropic"
description = "Anthropic large language models."
icon ="Anthropic"
def build_config(self):
return {

View file

@ -9,6 +9,7 @@ class ChatAnthropicComponent(CustomComponent):
display_name = "ChatAnthropic"
description = "`Anthropic` chat large language models."
documentation = "https://python.langchain.com/docs/modules/model_io/models/chat/integrations/anthropic"
icon ="Anthropic"
def build_config(self):
return {

View file

@ -9,6 +9,7 @@ from langflow.field_typing import BaseLanguageModel, NestedDict
class ChatOpenAIComponent(CustomComponent):
display_name = "ChatOpenAI"
description = "`OpenAI` Chat large language models API."
icon = "OpenAI"
def build_config(self):
return {

View file

@ -10,6 +10,8 @@ from langflow.field_typing import BaseLanguageModel
class ChatVertexAIComponent(CustomComponent):
display_name = "ChatVertexAI"
description = "`Vertex AI` Chat large language models API."
icon="VertexAI"
def build_config(self):
return {

View file

@ -7,6 +7,7 @@ class CohereComponent(CustomComponent):
display_name = "Cohere"
description = "Cohere large language models."
documentation = "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/cohere"
icon = "Cohere"
def build_config(self):
return {

View file

@ -10,6 +10,7 @@ class GoogleGenerativeAIComponent(CustomComponent):
display_name: str = "Google Generative AI"
description: str = "A component that uses Google Generative AI to generate text."
documentation: str = "http://docs.langflow.org/components/custom"
icon = "Google"
def build_config(self):
return {

View file

@ -8,6 +8,8 @@ from langflow import CustomComponent
class HuggingFaceEndpointsComponent(CustomComponent):
display_name: str = "Hugging Face Inference API"
description: str = "LLM model from Hugging Face Inference API."
icon="HuggingFace"
def build_config(self):
return {

View file

@ -7,6 +7,7 @@ from langchain_community.llms.vertexai import VertexAI
class VertexAIComponent(CustomComponent):
display_name = "VertexAI"
description = "Google Vertex AI large language models"
icon="VertexAI"
def build_config(self):
return {

View file

@ -2,13 +2,14 @@ from typing import Optional
from langchain_community.chat_models.bedrock import BedrockChat
from langflow import CustomComponent
from langflow.components.models.base.model import LCModelComponent
from langflow.field_typing import Text
class AmazonBedrockComponent(CustomComponent):
class AmazonBedrockComponent(LCModelComponent):
display_name: str = "Amazon Bedrock Model"
description: str = "Generate text using LLM model from Amazon Bedrock."
icon = "Amazon"
def build_config(self):
return {
@ -34,7 +35,11 @@ class AmazonBedrockComponent(CustomComponent):
"model_kwargs": {"display_name": "Model Kwargs"},
"cache": {"display_name": "Cache"},
"code": {"advanced": True},
"inputs": {"display_name": "Input"},
"input_value": {"display_name": "Input"},
"stream": {
"display_name": "Stream",
"info": "Stream the response from the model.",
},
}
def build(
@ -47,6 +52,7 @@ class AmazonBedrockComponent(CustomComponent):
endpoint_url: Optional[str] = None,
streaming: bool = False,
cache: Optional[bool] = None,
stream: bool = False,
) -> Text:
try:
output = BedrockChat(
@ -60,7 +66,5 @@ class AmazonBedrockComponent(CustomComponent):
) # type: ignore
except Exception as e:
raise ValueError("Could not connect to AmazonBedrock API.") from e
message = output.invoke(input_value)
result = message.content if hasattr(message, "content") else message
self.status = result
return result
return self.get_result(output=output, stream=stream, input_value=input_value)

View file

@ -3,15 +3,16 @@ from typing import Optional
from langchain_community.chat_models.anthropic import ChatAnthropic
from pydantic.v1 import SecretStr
from langflow import CustomComponent
from langflow.components.models.base.model import LCModelComponent
from langflow.field_typing import Text
class AnthropicLLM(CustomComponent):
class AnthropicLLM(LCModelComponent):
display_name: str = "AnthropicModel"
description: str = (
"Generate text using Anthropic Chat&Completion large language models."
)
icon = "Anthropic"
def build_config(self):
return {
@ -49,7 +50,11 @@ class AnthropicLLM(CustomComponent):
"info": "Endpoint of the Anthropic API. Defaults to 'https://api.anthropic.com' if not specified.",
},
"code": {"show": False},
"inputs": {"display_name": "Input"},
"input_value": {"display_name": "Input"},
"stream": {
"display_name": "Stream",
"info": "Stream the response from the model.",
},
}
def build(
@ -60,6 +65,7 @@ class AnthropicLLM(CustomComponent):
max_tokens: Optional[int] = None,
temperature: Optional[float] = None,
api_endpoint: Optional[str] = None,
stream: bool = False,
) -> Text:
# Set default API endpoint if not provided
if not api_endpoint:
@ -77,7 +83,5 @@ class AnthropicLLM(CustomComponent):
)
except Exception as e:
raise ValueError("Could not connect to Anthropic API.") from e
message = output.invoke(input_value)
result = message.content if hasattr(message, "content") else message
self.status = result
return result
return self.get_result(output=output, stream=stream, input_value=input_value)

View file

@ -3,16 +3,17 @@ from typing import Optional
from langchain.llms.base import BaseLanguageModel
from langchain_openai import AzureChatOpenAI
from langflow import CustomComponent
from langflow.components.models.base.model import LCModelComponent
class AzureChatOpenAIComponent(CustomComponent):
class AzureChatOpenAIComponent(LCModelComponent):
display_name: str = "AzureOpenAI Model"
description: str = "Generate text using LLM model from Azure OpenAI."
documentation: str = (
"https://python.langchain.com/docs/integrations/llms/azure_openai"
)
beta = False
icon = "Azure"
AZURE_OPENAI_MODELS = [
"gpt-35-turbo",
@ -73,7 +74,11 @@ class AzureChatOpenAIComponent(CustomComponent):
"info": "Maximum number of tokens to generate.",
},
"code": {"show": False},
"inputs": {"display_name": "Input"},
"input_value": {"display_name": "Input"},
"stream": {
"display_name": "Stream",
"info": "Stream the response from the model.",
},
}
def build(
@ -86,6 +91,7 @@ class AzureChatOpenAIComponent(CustomComponent):
api_version: str,
temperature: float = 0.7,
max_tokens: Optional[int] = 1000,
stream: bool = False,
) -> BaseLanguageModel:
try:
output = AzureChatOpenAI(
@ -99,7 +105,5 @@ class AzureChatOpenAIComponent(CustomComponent):
)
except Exception as e:
raise ValueError("Could not connect to AzureOpenAI API.") from e
message = output.invoke(input_value)
result = message.content if hasattr(message, "content") else message
self.status = result
return result
return self.get_result(output=output, stream=stream, input_value=input_value)

View file

@ -3,16 +3,17 @@ from typing import Optional
from langchain_community.chat_models.baidu_qianfan_endpoint import QianfanChatEndpoint
from pydantic.v1 import SecretStr
from langflow import CustomComponent
from langflow.components.models.base.model import LCModelComponent
from langflow.field_typing import Text
class QianfanChatEndpointComponent(CustomComponent):
class QianfanChatEndpointComponent(LCModelComponent):
display_name: str = "QianfanChat Model"
description: str = (
"Generate text using Baidu Qianfan chat models. Get more detail from "
"https://python.langchain.com/docs/integrations/chat/baidu_qianfan_endpoint."
)
icon = "BaiduQianfan"
def build_config(self):
return {
@ -68,7 +69,11 @@ class QianfanChatEndpointComponent(CustomComponent):
"info": "Endpoint of the Qianfan LLM, required if custom model used.",
},
"code": {"show": False},
"inputs": {"display_name": "Input"},
"input_value": {"display_name": "Input"},
"stream": {
"display_name": "Stream",
"info": "Stream the response from the model.",
},
}
def build(
@ -81,6 +86,7 @@ class QianfanChatEndpointComponent(CustomComponent):
temperature: Optional[float] = None,
penalty_score: Optional[float] = None,
endpoint: Optional[str] = None,
stream: bool = False,
) -> Text:
try:
output = QianfanChatEndpoint( # type: ignore
@ -94,7 +100,5 @@ class QianfanChatEndpointComponent(CustomComponent):
)
except Exception as e:
raise ValueError("Could not connect to Baidu Qianfan API.") from e
message = output.invoke(input_value)
result = message.content if hasattr(message, "content") else message
self.status = result
return result
return self.get_result(output=output, stream=stream, input_value=input_value)

View file

@ -2,11 +2,11 @@ from typing import Dict, Optional
from langchain_community.llms.ctransformers import CTransformers
from langflow import CustomComponent
from langflow.components.models.base.model import LCModelComponent
from langflow.field_typing import Text
class CTransformersComponent(CustomComponent):
class CTransformersComponent(LCModelComponent):
display_name = "CTransformersModel"
description = "Generate text using CTransformers LLM models"
documentation = "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/ctransformers"
@ -28,7 +28,11 @@ class CTransformersComponent(CustomComponent):
"field_type": "dict",
"value": '{"top_k":40,"top_p":0.95,"temperature":0.8,"repetition_penalty":1.1,"last_n_tokens":64,"seed":-1,"max_new_tokens":256,"stop":"","stream":"False","reset":"True","batch_size":8,"threads":-1,"context_length":-1,"gpu_layers":0}',
},
"inputs": {"display_name": "Input"},
"input_value": {"display_name": "Input"},
"stream": {
"display_name": "Stream",
"info": "Stream the response from the model.",
},
}
def build(
@ -38,11 +42,10 @@ class CTransformersComponent(CustomComponent):
input_value: str,
model_type: str,
config: Optional[Dict] = None,
stream: Optional[bool] = False,
) -> Text:
output = CTransformers(
model=model, model_file=model_file, model_type=model_type, config=config
)
message = output.invoke(input_value)
result = message.content if hasattr(message, "content") else message
self.status = result
return result
return self.get_result(output=output, stream=stream, input_value=input_value)

View file

@ -1,14 +1,16 @@
from langchain_community.chat_models.cohere import ChatCohere
from langflow import CustomComponent
from langflow.components.models.base.model import LCModelComponent
from langflow.field_typing import Text
class CohereComponent(CustomComponent):
class CohereComponent(LCModelComponent):
display_name = "CohereModel"
description = "Generate text using Cohere large language models."
documentation = "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/cohere"
icon = "Cohere"
def build_config(self):
return {
"cohere_api_key": {
@ -28,7 +30,11 @@ class CohereComponent(CustomComponent):
"type": "float",
"show": True,
},
"inputs": {"display_name": "Input"},
"input_value": {"display_name": "Input"},
"stream": {
"display_name": "Stream",
"info": "Stream the response from the model.",
},
}
def build(
@ -37,14 +43,11 @@ class CohereComponent(CustomComponent):
input_value: str,
max_tokens: int = 256,
temperature: float = 0.75,
stream: bool = False,
) -> Text:
output = ChatCohere(
cohere_api_key=cohere_api_key,
max_tokens=max_tokens,
temperature=temperature,
)
message = output.invoke(input_value)
result = message.content if hasattr(message, "content") else message
self.status = result
return result
return result
return self.get_result(output=output, stream=stream, input_value=input_value)

View file

@ -1,16 +1,17 @@
from typing import Optional
from langchain_google_genai import ChatGoogleGenerativeAI # type: ignore
from pydantic.v1.types import SecretStr
from langchain_google_genai import ChatGoogleGenerativeAI
from pydantic.v1 import SecretStr
from langflow import CustomComponent
from langflow.components.models.base.model import LCModelComponent
from langflow.field_typing import RangeSpec, Text
class GoogleGenerativeAIComponent(CustomComponent):
class GoogleGenerativeAIComponent(LCModelComponent):
display_name: str = "Google Generative AIModel"
description: str = "Generate text using Google Generative AI to generate text."
documentation: str = "http://docs.langflow.org/components/custom"
icon = "GoogleGenerativeAI"
icon = "Google"
def build_config(self):
return {
@ -50,7 +51,11 @@ class GoogleGenerativeAIComponent(CustomComponent):
"code": {
"advanced": True,
},
"inputs": {"display_name": "Input"},
"input_value": {"display_name": "Input", "info": "The input to the model."},
"stream": {
"display_name": "Stream",
"info": "Stream the response from the model.",
},
}
def build(
@ -63,6 +68,7 @@ class GoogleGenerativeAIComponent(CustomComponent):
top_k: Optional[int] = None,
top_p: Optional[float] = None,
n: Optional[int] = 1,
stream: bool = False,
) -> Text:
output = ChatGoogleGenerativeAI(
model=model,
@ -73,7 +79,4 @@ class GoogleGenerativeAIComponent(CustomComponent):
n=n or 1,
google_api_key=SecretStr(google_api_key),
)
message = output.invoke(input_value)
result = message.content if hasattr(message, "content") else message
self.status = result
return result
return self.get_result(output=output, stream=stream, input_value=input_value)

View file

@ -3,13 +3,14 @@ from typing import Optional
from langchain_community.chat_models.huggingface import ChatHuggingFace
from langchain_community.llms.huggingface_endpoint import HuggingFaceEndpoint
from langflow import CustomComponent
from langflow.components.models.base.model import LCModelComponent
from langflow.field_typing import Text
class HuggingFaceEndpointsComponent(CustomComponent):
class HuggingFaceEndpointsComponent(LCModelComponent):
display_name: str = "Hugging Face Inference API models"
description: str = "Generate text using LLM model from Hugging Face Inference API."
icon = "HuggingFace"
def build_config(self):
return {
@ -24,7 +25,11 @@ class HuggingFaceEndpointsComponent(CustomComponent):
"field_type": "code",
},
"code": {"show": False},
"inputs": {"display_name": "Input"},
"input_value": {"display_name": "Input"},
"stream": {
"display_name": "Stream",
"info": "Stream the response from the model.",
},
}
def build(
@ -34,6 +39,7 @@ class HuggingFaceEndpointsComponent(CustomComponent):
task: str = "text2text-generation",
huggingfacehub_api_token: Optional[str] = None,
model_kwargs: Optional[dict] = None,
stream: bool = False,
) -> Text:
try:
llm = HuggingFaceEndpoint(
@ -45,7 +51,4 @@ class HuggingFaceEndpointsComponent(CustomComponent):
except Exception as e:
raise ValueError("Could not connect to HuggingFace Endpoints API.") from e
output = ChatHuggingFace(llm=llm)
message = output.invoke(input_value)alue)
result = message.content if hasattr(message, "content") else message
self.status = result
return result
return self.get_result(output=output, stream=stream, input_value=input_value)

View file

@ -2,11 +2,11 @@ from typing import Any, Dict, List, Optional
from langchain_community.llms.llamacpp import LlamaCpp
from langflow import CustomComponent
from langflow.components.models.base.model import LCModelComponent
from langflow.field_typing import Text
class LlamaCppComponent(CustomComponent):
class LlamaCppComponent(LCModelComponent):
display_name = "LlamaCppModel"
description = "Generate text using llama.cpp model."
documentation = "https://python.langchain.com/docs/modules/model_io/models/llms/integrations/llamacpp"
@ -56,7 +56,11 @@ class LlamaCppComponent(CustomComponent):
"use_mmap": {"display_name": "Use Mmap", "advanced": True},
"verbose": {"display_name": "Verbose", "advanced": True},
"vocab_only": {"display_name": "Vocab Only", "advanced": True},
"inputs": {"display_name": "Input"},
"input_value": {"display_name": "Input"},
"stream": {
"display_name": "Stream",
"info": "Stream the response from the model.",
},
}
def build(
@ -97,6 +101,7 @@ class LlamaCppComponent(CustomComponent):
use_mmap: Optional[bool] = True,
verbose: bool = True,
vocab_only: bool = False,
stream: bool = False,
) -> Text:
output = LlamaCpp(
model_path=model_path,
@ -135,9 +140,5 @@ class LlamaCppComponent(CustomComponent):
verbose=verbose,
vocab_only=vocab_only,
)
message = output.invoke(input_value)
result = message.content if hasattr(message, "content") else message
self.status = result
return result
self.status = result
return result
return self.get_result(output=output, stream=stream, input_value=input_value)

View file

@ -3,17 +3,19 @@ from typing import Any, Dict, List, Optional
# from langchain_community.chat_models import ChatOllama
from langchain_community.chat_models import ChatOllama
from langflow.components.models.base.model import LCModelComponent
# from langchain.chat_models import ChatOllama
from langflow import CustomComponent
from langflow.field_typing import Text
# whe When a callback component is added to Langflow, the comment must be uncommented.
# from langchain.callbacks.manager import CallbackManager
class ChatOllamaComponent(CustomComponent):
class ChatOllamaComponent(LCModelComponent):
display_name = "ChatOllamaModel"
description = "Generate text using Local LLM for chat with Ollama."
icon = "Ollama"
def build_config(self) -> dict:
return {
@ -164,7 +166,11 @@ class ChatOllamaComponent(CustomComponent):
"info": "Template to use for generating text.",
"advanced": True,
},
"inputs": {"display_name": "Input"},
"input_value": {"display_name": "Input"},
"stream": {
"display_name": "Stream",
"info": "Stream the response from the model.",
},
}
def build(
@ -197,6 +203,7 @@ class ChatOllamaComponent(CustomComponent):
timeout: Optional[int] = None,
top_k: Optional[int] = None,
top_p: Optional[int] = None,
stream: Optional[bool] = False,
) -> Text:
if not base_url:
base_url = "http://localhost:11434"
@ -250,7 +257,5 @@ class ChatOllamaComponent(CustomComponent):
output = ChatOllama(**llm_params) # type: ignore
except Exception as e:
raise ValueError("Could not initialize Ollama LLM.") from e
message = output.invoke(input_value)
result = message.content if hasattr(message, "content") else message
self.status = result
return result
return self.get_result(output=output, stream=stream, input_value=input_value)

View file

@ -2,17 +2,18 @@ from typing import Optional
from langchain_openai import ChatOpenAI
from langflow import CustomComponent
from langflow.components.models.base.model import LCModelComponent
from langflow.field_typing import NestedDict, Text
class OpenAIModelComponent(CustomComponent):
class OpenAIModelComponent(LCModelComponent):
display_name = "OpenAI Model"
description = "Generates text using OpenAI's models."
icon = "OpenAI"
def build_config(self):
return {
"inputs": {"display_name": "Input"},
"input_value": {"display_name": "Input"},
"max_tokens": {
"display_name": "Max Tokens",
"advanced": False,
@ -57,6 +58,10 @@ class OpenAIModelComponent(CustomComponent):
"required": False,
"value": 0.7,
},
"stream": {
"display_name": "Stream",
"info": "Stream the response from the model.",
},
}
def build(
@ -68,10 +73,11 @@ class OpenAIModelComponent(CustomComponent):
openai_api_base: Optional[str] = None,
openai_api_key: Optional[str] = None,
temperature: float = 0.7,
stream: Optional[bool] = False,
) -> Text:
if not openai_api_base:
openai_api_base = "https://api.openai.com/v1"
model = ChatOpenAI(
output = ChatOpenAI(
max_tokens=max_tokens,
model_kwargs=model_kwargs,
model=model_name,
@ -80,7 +86,4 @@ class OpenAIModelComponent(CustomComponent):
temperature=temperature,
)
message = model.invoke(input_value)
result = message.content if hasattr(message, "content") else message
self.status = result
return result
return self.get_result(output=output, stream=stream, input_value=input_value)

View file

@ -2,13 +2,15 @@ from typing import List, Optional
from langchain_core.messages.base import BaseMessage
from langflow import CustomComponent
from langflow.components.models.base.model import LCModelComponent
from langflow.field_typing import Text
class ChatVertexAIComponent(CustomComponent):
class ChatVertexAIComponent(LCModelComponent):
display_name = "ChatVertexAIModel"
description = "Generate text using Vertex AI Chat large language models API."
icon="VertexAI"
def build_config(self):
return {
@ -57,7 +59,11 @@ class ChatVertexAIComponent(CustomComponent):
"value": False,
"advanced": True,
},
"inputs": {"display_name": "Input"},
"input_value": {"display_name": "Input"},
"stream": {
"display_name": "Stream",
"info": "Stream the response from the model.",
},
}
def build(
@ -73,6 +79,7 @@ class ChatVertexAIComponent(CustomComponent):
top_k: int = 40,
top_p: float = 0.95,
verbose: bool = False,
stream: bool = False,
) -> Text:
try:
from langchain_google_vertexai import ChatVertexAI
@ -92,7 +99,5 @@ class ChatVertexAIComponent(CustomComponent):
top_p=top_p,
verbose=verbose,
)
message = output.invoke(input_value)
result = message.content if hasattr(message, "content") else message
self.status = result
return result
return self.get_result(output=output, stream=stream, input_value=input_value)

View file

@ -0,0 +1,28 @@
from langchain_core.runnables import Runnable
from langflow import CustomComponent
class LCModelComponent(CustomComponent):
display_name: str = "Model Name"
description: str = "Model Description"
def get_result(self, output: Runnable, stream: bool, input_value: str):
"""
Retrieves the result from the output of a Runnable object.
Args:
output (Runnable): The output object to retrieve the result from.
stream (bool): Indicates whether to use streaming or invocation mode.
input_value (str): The input value to pass to the output object.
Returns:
The result obtained from the output object.
"""
if stream:
result = output.stream(input_value)
else:
message = output.invoke(input_value)
result = message.content if hasattr(message, "content") else message
self.status = result
return result

View file

@ -9,6 +9,7 @@ from langflow import CustomComponent
class AmazonKendraRetrieverComponent(CustomComponent):
display_name: str = "Amazon Kendra Retriever"
description: str = "Retriever that uses the Amazon Kendra API."
icon = "Amazon"
def build_config(self):
return {

View file

@ -17,6 +17,8 @@ class VectaraSelfQueryRetriverComponent(CustomComponent):
description: str = "Implementation of Vectara Self Query Retriever"
documentation = "https://python.langchain.com/docs/integrations/retrievers/self_query/vectara_self_query"
beta = True
icon="Vectara"
field_config = {
"code": {"show": True},

View file

@ -15,7 +15,7 @@ class RunnableExecComponent(CustomComponent):
"display_name": "Input Key",
"info": "The key to use for the input.",
},
"inputs": {
"input_value": {
"display_name": "Inputs",
"info": "The inputs to pass to the runnable.",
},

View file

@ -3,12 +3,12 @@ from typing import List, Optional
import chromadb # type: ignore
from langchain_community.vectorstores.chroma import Chroma
from langflow import CustomComponent
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
from langflow.field_typing import Embeddings, Text
from langflow.schema import Record, docs_to_records
from langflow.schema import Record
class ChromaSearchComponent(CustomComponent):
class ChromaSearchComponent(LCVectorStoreComponent):
"""
A custom component for implementing a Vector Store using Chroma.
"""
@ -26,7 +26,7 @@ class ChromaSearchComponent(CustomComponent):
- dict: A dictionary containing the configuration options for the component.
"""
return {
"inputs": {"display_name": "Input"},
"input_value": {"display_name": "Input"},
"search_type": {
"display_name": "Search Type",
"options": ["Similarity", "MMR"],
@ -101,17 +101,11 @@ class ChromaSearchComponent(CustomComponent):
chroma_server_ssl_enabled=chroma_server_ssl_enabled,
)
index_directory = self.resolve_path(index_directory)
chroma = Chroma(
vector_store = Chroma(
embedding_function=embedding,
collection_name=collection_name,
persist_directory=index_directory,
client_settings=chroma_settings,
)
# Validate the inputs
docs = []
if inputs and isinstance(inputs, str):
docs = chroma.search(query=inputs, search_type=search_type.lower())
else:
raise ValueError("Invalid inputs provided.")
return docs_to_records(docs)
return self.search_with_vector_store(input_value, search_type, vector_store)

View file

@ -3,24 +3,36 @@ from typing import List, Union
from langchain.schema import BaseRetriever
from langchain_community.vectorstores import VectorStore
from langchain_community.vectorstores.faiss import FAISS
from langflow import CustomComponent
from langflow.field_typing import Document, Embeddings
class FAISSComponent(CustomComponent):
display_name = "FAISS"
description = "Construct FAISS wrapper from raw documents."
description = "Ingest documents into FAISS Vector Store."
documentation = "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/faiss"
def build_config(self):
return {
"documents": {"display_name": "Documents"},
"embedding": {"display_name": "Embedding"},
"folder_path": {
"display_name": "Folder Path",
"info": "Path to save the FAISS index. It will be relative to where Langflow is running.",
},
"index_name": {"display_name": "Index Name"},
}
def build(
self,
embedding: Embeddings,
documents: List[Document],
folder_path: str,
index_name: str = "langflow_index",
) -> Union[VectorStore, FAISS, BaseRetriever]:
return FAISS.from_documents(documents=documents, embedding=embedding)
vector_store = FAISS.from_documents(documents=documents, embedding=embedding)
if not folder_path:
raise ValueError("Folder path is required to save the FAISS index.")
path = self.resolve_path(folder_path)
vector_store.save_local(str(path), index_name)

View file

@ -0,0 +1,45 @@
from typing import List
from langchain_community.vectorstores.faiss import FAISS
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
from langflow.field_typing import Embeddings
from langflow.schema import Record
class FAISSSearchComponent(LCVectorStoreComponent):
display_name = "FAISS Search"
description = "Search a FAISS Vector Store for similar documents."
documentation = "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/faiss"
def build_config(self):
return {
"documents": {"display_name": "Documents"},
"embedding": {"display_name": "Embedding"},
"folder_path": {
"display_name": "Folder Path",
"info": "Path to save the FAISS index. It will be relative to where Langflow is running.",
},
"input_value": {"display_name": "Input"},
"index_name": {"display_name": "Index Name"},
}
def build(
self,
input_value: str,
embedding: Embeddings,
folder_path: str,
index_name: str = "langflow_index",
) -> List[Record]:
if not folder_path:
raise ValueError("Folder path is required to save the FAISS index.")
path = self.resolve_path(folder_path)
vector_store = FAISS.load_local(
folder_path=str(path), embeddings=embedding, index_name=index_name
)
if not vector_store:
raise ValueError("Failed to load the FAISS index.")
return self.search_with_vector_store(
vector_store=vector_store, input_value=input_value, search_type="similarity"
)

View file

@ -0,0 +1,57 @@
from typing import List, Optional
from langchain_community.vectorstores.mongodb_atlas import MongoDBAtlasVectorSearch
from langflow import CustomComponent
from langflow.field_typing import Document, Embeddings, NestedDict
class MongoDBAtlasComponent(CustomComponent):
display_name = "MongoDB Atlas"
description = (
"Construct a `MongoDB Atlas Vector Search` vector store from raw documents."
)
icon="MongoDB"
def build_config(self):
return {
"documents": {"display_name": "Documents"},
"embedding": {"display_name": "Embedding"},
"collection_name": {"display_name": "Collection Name"},
"db_name": {"display_name": "Database Name"},
"index_name": {"display_name": "Index Name"},
"mongodb_atlas_cluster_uri": {"display_name": "MongoDB Atlas Cluster URI"},
"search_kwargs": {"display_name": "Search Kwargs", "advanced": True},
}
def build(
self,
embedding: Embeddings,
documents: List[Document] = None,
collection_name: str = "",
db_name: str = "",
index_name: str = "",
mongodb_atlas_cluster_uri: str = "",
search_kwargs: Optional[NestedDict] = None,
) -> MongoDBAtlasVectorSearch:
search_kwargs = search_kwargs or {}
if documents:
vector_store = MongoDBAtlasVectorSearch.from_documents(
documents=documents,
embedding=embedding,
collection_name=collection_name,
db_name=db_name,
index_name=index_name,
mongodb_atlas_cluster_uri=mongodb_atlas_cluster_uri,
search_kwargs=search_kwargs,
)
else:
vector_store = MongoDBAtlasVectorSearch(
embedding=embedding,
collection_name=collection_name,
db_name=db_name,
index_name=index_name,
mongodb_atlas_cluster_uri=mongodb_atlas_cluster_uri,
search_kwargs=search_kwargs,
)
return vector_store

View file

@ -1,22 +1,22 @@
from typing import List, Optional
from langchain_community.vectorstores import MongoDBAtlasVectorSearch
from langflow import CustomComponent
from langflow.field_typing import (
Document,
Embeddings,
NestedDict,
)
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
from langflow.components.vectorstores.MongoDBAtlasVector import MongoDBAtlasComponent
from langflow.field_typing import Embeddings, NestedDict
from langflow.schema import Record
class MongoDBAtlasComponent(CustomComponent):
display_name = "MongoDB Atlas"
description = "Construct a `MongoDB Atlas Vector Search` vector store from raw documents."
class MongoDBAtlasSearchComponent(MongoDBAtlasComponent, LCVectorStoreComponent):
display_name = "MongoDB Atlas Search"
description = "Search a MongoDB Atlas Vector Store for similar documents."
def build_config(self):
return {
"documents": {"display_name": "Documents"},
"search_type": {
"display_name": "Search Type",
"options": ["Similarity", "MMR"],
},
"input_value": {"display_name": "Input"},
"embedding": {"display_name": "Embedding"},
"collection_name": {"display_name": "Collection Name"},
"db_name": {"display_name": "Database Name"},
@ -27,17 +27,16 @@ class MongoDBAtlasComponent(CustomComponent):
def build(
self,
documents: List[Document],
input_value: str,
search_type: str,
embedding: Embeddings,
collection_name: str = "",
db_name: str = "",
index_name: str = "",
mongodb_atlas_cluster_uri: str = "",
search_kwargs: Optional[NestedDict] = None,
) -> MongoDBAtlasVectorSearch:
search_kwargs = search_kwargs or {}
return MongoDBAtlasVectorSearch(
documents=documents,
) -> List[Record]:
vector_store = super().build(
embedding=embedding,
collection_name=collection_name,
db_name=db_name,
@ -45,3 +44,8 @@ class MongoDBAtlasComponent(CustomComponent):
mongodb_atlas_cluster_uri=mongodb_atlas_cluster_uri,
search_kwargs=search_kwargs,
)
if not vector_store:
raise ValueError("Failed to create MongoDB Atlas Vector Store")
return self.search_with_vector_store(
vector_store=vector_store, input_value=input_value, search_type=search_type
)

View file

@ -5,6 +5,7 @@ import pinecone # type: ignore
from langchain.schema import BaseRetriever
from langchain_community.vectorstores import VectorStore
from langchain_community.vectorstores.pinecone import Pinecone
from langflow import CustomComponent
from langflow.field_typing import Document, Embeddings
@ -12,6 +13,7 @@ from langflow.field_typing import Document, Embeddings
class PineconeComponent(CustomComponent):
display_name = "Pinecone"
description = "Construct Pinecone wrapper from raw documents."
icon = "Pinecone"
def build_config(self):
return {
@ -19,10 +21,23 @@ class PineconeComponent(CustomComponent):
"embedding": {"display_name": "Embedding"},
"index_name": {"display_name": "Index Name"},
"namespace": {"display_name": "Namespace"},
"pinecone_api_key": {"display_name": "Pinecone API Key", "default": "", "password": True, "required": True},
"pinecone_env": {"display_name": "Pinecone Environment", "default": "", "required": True},
"pinecone_api_key": {
"display_name": "Pinecone API Key",
"default": "",
"password": True,
"required": True,
},
"pinecone_env": {
"display_name": "Pinecone Environment",
"default": "",
"required": True,
},
"search_kwargs": {"display_name": "Search Kwargs", "default": "{}"},
"pool_threads": {"display_name": "Pool Threads", "default": 1, "advanced": True},
"pool_threads": {
"display_name": "Pool Threads",
"default": 1,
"advanced": True,
},
}
def build(

View file

@ -0,0 +1,70 @@
from typing import List, Optional
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
from langflow.components.vectorstores.Pinecone import PineconeComponent
from langflow.field_typing import Embeddings
from langflow.schema import Record
class PineconeSearchComponent(PineconeComponent, LCVectorStoreComponent):
display_name = "Pinecone Search"
description = "Search a Pinecone Vector Store for similar documents."
icon = "Pinecone"
def build_config(self):
return {
"search_type": {
"display_name": "Search Type",
"options": ["Similarity", "MMR"],
},
"input_value": {"display_name": "Input"},
"embedding": {"display_name": "Embedding"},
"index_name": {"display_name": "Index Name"},
"namespace": {"display_name": "Namespace"},
"pinecone_api_key": {
"display_name": "Pinecone API Key",
"default": "",
"password": True,
"required": True,
},
"pinecone_env": {
"display_name": "Pinecone Environment",
"default": "",
"required": True,
},
"search_kwargs": {"display_name": "Search Kwargs", "default": "{}"},
"pool_threads": {
"display_name": "Pool Threads",
"default": 1,
"advanced": True,
},
}
def build(
self,
input_value: str,
embedding: Embeddings,
pinecone_env: str,
text_key: str = "text",
pool_threads: int = 4,
index_name: Optional[str] = None,
pinecone_api_key: Optional[str] = None,
namespace: Optional[str] = "default",
search_type: str = "similarity",
) -> List[Record]:
vector_store = super().build(
embedding=embedding,
pinecone_env=pinecone_env,
documents=[],
text_key=text_key,
pool_threads=pool_threads,
index_name=index_name,
pinecone_api_key=pinecone_api_key,
namespace=namespace,
)
if not vector_store:
raise ValueError("Failed to load the Pinecone index.")
return self.search_with_vector_store(
vector_store=vector_store, input_value=input_value, search_type=search_type
)

View file

@ -10,6 +10,7 @@ from langflow.field_typing import Document, Embeddings, NestedDict
class QdrantComponent(CustomComponent):
display_name = "Qdrant"
description = "Construct Qdrant wrapper from a list of texts."
icon="Qdrant"
def build_config(self):
return {

View file

@ -0,0 +1,93 @@
from typing import List, Optional
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
from langflow.components.vectorstores.Qdrant import QdrantComponent
from langflow.field_typing import Embeddings, NestedDict
from langflow.schema import Record
class QdrantSearchComponent(QdrantComponent, LCVectorStoreComponent):
display_name = "Qdrant"
description = "Construct Qdrant wrapper from a list of texts."
icon="Qdrant"
def build_config(self):
return {
"search_type": {
"display_name": "Search Type",
"options": ["Similarity", "MMR"],
},
"input_value": {"display_name": "Input"},
"embedding": {"display_name": "Embedding"},
"api_key": {"display_name": "API Key", "password": True, "advanced": True},
"collection_name": {"display_name": "Collection Name"},
"content_payload_key": {
"display_name": "Content Payload Key",
"advanced": True,
},
"distance_func": {"display_name": "Distance Function", "advanced": True},
"grpc_port": {"display_name": "gRPC Port", "advanced": True},
"host": {"display_name": "Host", "advanced": True},
"https": {"display_name": "HTTPS", "advanced": True},
"location": {"display_name": "Location", "advanced": True},
"metadata_payload_key": {
"display_name": "Metadata Payload Key",
"advanced": True,
},
"path": {"display_name": "Path", "advanced": True},
"port": {"display_name": "Port", "advanced": True},
"prefer_grpc": {"display_name": "Prefer gRPC", "advanced": True},
"prefix": {"display_name": "Prefix", "advanced": True},
"search_kwargs": {"display_name": "Search Kwargs", "advanced": True},
"timeout": {"display_name": "Timeout", "advanced": True},
"url": {"display_name": "URL", "advanced": True},
}
def build(
self,
input_value: str,
embedding: Embeddings,
collection_name: str,
search_type: str = "similarity",
api_key: Optional[str] = None,
content_payload_key: str = "page_content",
distance_func: str = "Cosine",
grpc_port: int = 6334,
https: bool = False,
host: Optional[str] = None,
location: Optional[str] = None,
metadata_payload_key: str = "metadata",
path: Optional[str] = None,
port: Optional[int] = 6333,
prefer_grpc: bool = False,
prefix: Optional[str] = None,
search_kwargs: Optional[NestedDict] = None,
timeout: Optional[int] = None,
url: Optional[str] = None,
) -> List[Record]:
vector_store = super().build(
embedding=embedding,
collection_name=collection_name,
api_key=api_key,
content_payload_key=content_payload_key,
distance_func=distance_func,
grpc_port=grpc_port,
https=https,
host=host,
location=location,
metadata_payload_key=metadata_payload_key,
path=path,
port=port,
prefer_grpc=prefer_grpc,
prefix=prefix,
search_kwargs=search_kwargs,
timeout=timeout,
url=url,
)
if not vector_store:
raise ValueError("Failed to load the Qdrant index.")
return self.search_with_vector_store(
vector_store=vector_store, input_value=input_value, search_type=search_type
)

View file

@ -0,0 +1,77 @@
from typing import List, Optional
from langchain.embeddings.base import Embeddings
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
from langflow.components.vectorstores.Redis import RedisComponent
from langflow.schema import Record
class RedisSearchComponent(RedisComponent, LCVectorStoreComponent):
"""
A custom component for implementing a Vector Store using Redis.
"""
display_name: str = "Redis Search"
description: str = "Search a Redis Vector Store for similar documents."
documentation = "https://python.langchain.com/docs/integrations/vectorstores/redis"
beta = True
def build_config(self):
"""
Builds the configuration for the component.
Returns:
- dict: A dictionary containing the configuration options for the component.
"""
return {
"search_type": {
"display_name": "Search Type",
"options": ["Similarity", "MMR"],
},
"input_value": {"display_name": "Input"},
"index_name": {"display_name": "Index Name", "value": "your_index"},
"code": {"show": False, "display_name": "Code"},
"documents": {"display_name": "Documents", "is_list": True},
"embedding": {"display_name": "Embedding"},
"schema": {"display_name": "Schema", "file_types": [".yaml"]},
"redis_server_url": {
"display_name": "Redis Server Connection String",
"advanced": False,
},
"redis_index_name": {"display_name": "Redis Index", "advanced": False},
}
def build(
self,
input_value: str,
search_type: str,
embedding: Embeddings,
redis_server_url: str,
redis_index_name: str,
schema: Optional[str] = None,
) -> List[Record]:
"""
Builds the Vector Store or BaseRetriever object.
Args:
- embedding (Embeddings): The embeddings to use for the Vector Store.
- documents (Optional[Document]): The documents to use for the Vector Store.
- redis_index_name (str): The name of the Redis index.
- redis_server_url (str): The URL for the Redis server.
Returns:
- VectorStore: The Vector Store object.
"""
vector_store = super().build(
embedding=embedding,
redis_server_url=redis_server_url,
redis_index_name=redis_index_name,
schema=schema,
)
if not vector_store:
raise ValueError("Failed to load the Redis index.")
return self.search_with_vector_store(
input_value=input_value, search_type=search_type, vector_store=vector_store
)

View file

@ -0,0 +1,50 @@
from typing import List
from langchain_community.vectorstores.supabase import SupabaseVectorStore
from supabase.client import Client, create_client
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
from langflow.field_typing import Embeddings
from langflow.schema import Record
class SupabaseSearchComponent(LCVectorStoreComponent):
display_name = "Supabase Search"
description = "Search a Supabase Vector Store for similar documents."
icon="Supabase"
def build_config(self):
return {
"search_type": {
"display_name": "Search Type",
"options": ["Similarity", "MMR"],
},
"input_value": {"display_name": "Input"},
"embedding": {"display_name": "Embedding"},
"query_name": {"display_name": "Query Name"},
"search_kwargs": {"display_name": "Search Kwargs", "advanced": True},
"supabase_service_key": {"display_name": "Supabase Service Key"},
"supabase_url": {"display_name": "Supabase URL"},
"table_name": {"display_name": "Table Name", "advanced": True},
}
def build(
self,
input_value: str,
search_type: str,
embedding: Embeddings,
query_name: str = "",
supabase_service_key: str = "",
supabase_url: str = "",
table_name: str = "",
) -> List[Record]:
supabase: Client = create_client(
supabase_url, supabase_key=supabase_service_key
)
vector_store = SupabaseVectorStore(
client=supabase,
embedding=embedding,
table_name=table_name,
query_name=query_name,
)
return self.search_with_vector_store(input_value, search_type, vector_store)

View file

@ -8,13 +8,17 @@ from langchain_community.vectorstores.vectara import Vectara
from langchain_core.vectorstores import VectorStore
from langflow import CustomComponent
from langflow.field_typing import BaseRetriever, Document
from langchain_community.vectorstores.vectara import Vectara
class VectaraComponent(CustomComponent):
display_name: str = "Vectara"
description: str = "Implementation of Vector Store using Vectara"
documentation = "https://python.langchain.com/docs/integrations/vectorstores/vectara"
documentation = (
"https://python.langchain.com/docs/integrations/vectorstores/vectara"
)
beta = True
icon="Vectara"
field_config = {
"vectara_customer_id": {
"display_name": "Vectara Customer ID",
@ -26,7 +30,10 @@ class VectaraComponent(CustomComponent):
"display_name": "Vectara API Key",
"password": True,
},
"documents": {"display_name": "Documents", "info": "If provided, will be upserted to corpus (optional)"},
"documents": {
"display_name": "Documents",
"info": "If provided, will be upserted to corpus (optional)",
},
"files_url": {
"display_name": "Files Url",
"info": "Make vectara object using url of files (optional)",

View file

@ -0,0 +1,66 @@
from typing import List
from langchain_community.vectorstores.vectara import Vectara
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
from langflow.components.vectorstores.Vectara import VectaraComponent
from langflow.schema import Record
class VectaraSearchComponent(VectaraComponent, LCVectorStoreComponent):
display_name: str = "Vectara Search"
description: str = "Search a Vectara Vector Store for similar documents."
documentation = (
"https://python.langchain.com/docs/integrations/vectorstores/vectara"
)
beta = True
icon="Vectara"
field_config = {
"search_type": {
"display_name": "Search Type",
"options": ["Similarity", "MMR"],
},
"input_value": {"display_name": "Input"},
"vectara_customer_id": {
"display_name": "Vectara Customer ID",
},
"vectara_corpus_id": {
"display_name": "Vectara Corpus ID",
},
"vectara_api_key": {
"display_name": "Vectara API Key",
"password": True,
},
"documents": {
"display_name": "Documents",
"info": "If provided, will be upserted to corpus (optional)",
},
"files_url": {
"display_name": "Files Url",
"info": "Make vectara object using url of files (optional)",
},
}
def build(
self,
input_value: str,
search_type: str,
vectara_customer_id: str,
vectara_corpus_id: str,
vectara_api_key: str,
) -> List[Record]:
source = "Langflow"
vector_store = Vectara(
vectara_customer_id=vectara_customer_id,
vectara_corpus_id=vectara_corpus_id,
vectara_api_key=vectara_api_key,
source=source,
)
if not vector_store:
raise ValueError("Failed to create Vectara Vector Store")
return self.search_with_vector_store(
vector_store=vector_store, input_value=input_value, search_type=search_type
)

View file

@ -8,10 +8,12 @@ from langchain_community.vectorstores import VectorStore, Weaviate
from langflow import CustomComponent
class WeaviateVectorStore(CustomComponent):
class WeaviateVectorStoreComponent(CustomComponent):
display_name: str = "Weaviate"
description: str = "Implementation of Vector Store using Weaviate"
documentation = "https://python.langchain.com/docs/integrations/vectorstores/weaviate"
documentation = (
"https://python.langchain.com/docs/integrations/vectorstores/weaviate"
)
beta = True
field_config = {
"url": {"display_name": "Weaviate URL", "value": "http://localhost:8080"},
@ -24,7 +26,12 @@ class WeaviateVectorStore(CustomComponent):
"display_name": "Index name",
"required": False,
},
"text_key": {"display_name": "Text Key", "required": False, "advanced": True, "value": "text"},
"text_key": {
"display_name": "Text Key",
"required": False,
"advanced": True,
"value": "text",
},
"documents": {"display_name": "Documents", "is_list": True},
"embedding": {"display_name": "Embedding"},
"attributes": {
@ -34,7 +41,11 @@ class WeaviateVectorStore(CustomComponent):
"field_type": "str",
"advanced": True,
},
"search_by_text": {"display_name": "Search By Text", "field_type": "bool", "advanced": True},
"search_by_text": {
"display_name": "Search By Text",
"field_type": "bool",
"advanced": True,
},
"code": {"show": False},
}

View file

@ -0,0 +1,84 @@
from typing import List, Optional
from langchain.embeddings.base import Embeddings
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
from langflow.components.vectorstores.Weaviate import WeaviateVectorStoreComponent
from langflow.schema import Record
class WeaviateSearchVectorStore(WeaviateVectorStoreComponent, LCVectorStoreComponent):
display_name: str = "Weaviate Search"
description: str = "Search a Weaviate Vector Store for similar documents."
documentation = (
"https://python.langchain.com/docs/integrations/vectorstores/weaviate"
)
beta = True
icon="Weaviate"
field_config = {
"search_type": {
"display_name": "Search Type",
"options": ["Similarity", "MMR"],
},
"input_value": {"display_name": "Input"},
"url": {"display_name": "Weaviate URL", "value": "http://localhost:8080"},
"api_key": {
"display_name": "API Key",
"password": True,
"required": False,
},
"index_name": {
"display_name": "Index name",
"required": False,
},
"text_key": {
"display_name": "Text Key",
"required": False,
"advanced": True,
"value": "text",
},
"documents": {"display_name": "Documents", "is_list": True},
"embedding": {"display_name": "Embedding"},
"attributes": {
"display_name": "Attributes",
"required": False,
"is_list": True,
"field_type": "str",
"advanced": True,
},
"search_by_text": {
"display_name": "Search By Text",
"field_type": "bool",
"advanced": True,
},
"code": {"show": False},
}
def build(
self,
input_value: str,
search_type: str,
url: str,
search_by_text: bool = False,
api_key: Optional[str] = None,
index_name: Optional[str] = None,
text_key: str = "text",
embedding: Optional[Embeddings] = None,
attributes: Optional[list] = None,
) -> List[Record]:
vector_store = super().build(
url=url,
api_key=api_key,
index_name=index_name,
text_key=text_key,
embedding=embedding,
attributes=attributes,
search_by_text=search_by_text,
)
if not vector_store:
raise ValueError("Failed to load the Weaviate index.")
return self.search_with_vector_store(
vector_store=vector_store, input_value=input_value, search_type=search_type
)

View file

@ -0,0 +1,41 @@
from typing import List
from langchain_core.vectorstores import VectorStore
from langflow import CustomComponent
from langflow.field_typing import Text
from langflow.schema import Record, docs_to_records
class LCVectorStoreComponent(CustomComponent):
display_name: str = "LC Vector Store"
description: str = "Search a LC Vector Store for similar documents."
beta: bool = True
def search_with_vector_store(
self, input_value: Text, search_type: str, vector_store: VectorStore
) -> List[Record]:
"""
Search for records in the vector store based on the input value and search type.
Args:
input_value (Text): The input value to search for.
search_type (str): The type of search to perform.
vector_store (VectorStore): The vector store to search in.
Returns:
List[Record]: A list of records matching the search criteria.
Raises:
ValueError: If invalid inputs are provided.
"""
docs = []
if input_value and isinstance(input_value, str):
docs = vector_store.search(
query=input_value, search_type=search_type.lower()
)
else:
raise ValueError("Invalid inputs provided.")
return docs_to_records(docs)

View file

@ -0,0 +1,73 @@
from typing import List, Optional
from langchain.embeddings.base import Embeddings
from langflow.components.vectorstores.base.model import LCVectorStoreComponent
from langflow.components.vectorstores.pgvector import PGVectorComponent
from langflow.schema import Record
class PGVectorSearchComponent(PGVectorComponent, LCVectorStoreComponent):
"""
A custom component for implementing a Vector Store using PostgreSQL.
"""
display_name: str = "PGVector Search"
description: str = "Search a PGVector Store for similar documents."
documentation = (
"https://python.langchain.com/docs/integrations/vectorstores/pgvector"
)
def build_config(self):
"""
Builds the configuration for the component.
Returns:
- dict: A dictionary containing the configuration options for the component.
"""
return {
"code": {"show": False},
"embedding": {"display_name": "Embedding"},
"search_type": {
"display_name": "Search Type",
"options": ["Similarity", "MMR"],
},
"pg_server_url": {
"display_name": "PostgreSQL Server Connection String",
"advanced": False,
},
"collection_name": {"display_name": "Table", "advanced": False},
"input_value": {"display_name": "Input"},
}
def build(
self,
input_value: str,
embedding: Embeddings,
pg_server_url: str,
collection_name: str,
search_type: Optional[str] = None,
) -> List[Record]:
"""
Builds the Vector Store or BaseRetriever object.
Args:
- input_value (str): The input value to search for.
- embedding (Embeddings): The embeddings to use for the Vector Store.
- collection_name (str): The name of the PG table.
- pg_server_url (str): The URL for the PG server.
Returns:
- VectorStore: The Vector Store object.
"""
try:
vector_store = super().build(
embedding=embedding,
pg_server_url=pg_server_url,
collection_name=collection_name,
)
except Exception as e:
raise RuntimeError(f"Failed to build PGVector: {e}")
return self.search_with_vector_store(
input_value=input_value, search_type=search_type, vector_store=vector_store
)

View file

@ -218,24 +218,7 @@ retrievers:
# https://github.com/supabase-community/supabase-py/issues/482
# ZepRetriever:
# documentation: "https://python.langchain.com/docs/modules/data_connection/retrievers/integrations/zep_memorystore"
vectorstores:
# Chroma:
# documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/chroma"
Qdrant:
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/qdrant"
FAISS:
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/faiss"
Pinecone:
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/pinecone"
ElasticsearchStore:
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/elasticsearch"
SupabaseVectorStore:
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/supabase"
MongoDBAtlasVectorSearch:
documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/mongodb_atlas"
# Requires docarray >=0.32.0 but langchain-serve requires jina 3.15.2 which doesn't support docarray >=0.32.0
# DocArrayInMemorySearch:
# documentation: "https://python.langchain.com/docs/modules/data_connection/vectorstores/integrations/docarray_in_memory"
wrappers:
RequestsWrapper:
documentation: ""

View file

@ -73,7 +73,7 @@ class Graph:
if getattr(vertex, attribute):
getattr(self, f"_{attribute}_vertices").append(vertex.id)
async def _run(self, inputs: Dict[str, str]) -> List["ResultData"]:
async def _run(self, inputs: Dict[str, str], stream: bool) -> List["ResultData"]:
"""Runs the graph with the given inputs."""
for vertex_id in self._is_input_vertices:
vertex = self.get_vertex(vertex_id)
@ -91,10 +91,14 @@ class Graph:
vertex = self.get_vertex(vertex_id)
if vertex is None:
raise ValueError(f"Vertex {vertex_id} not found")
if not stream and hasattr(vertex, "consume_async_generator"):
await vertex.consume_async_generator()
outputs.append(vertex.result)
return outputs
async def run(self, inputs: Dict[str, Union[str, list[str]]]) -> List["ResultData"]:
async def run(
self, inputs: Dict[str, Union[str, list[str]]], stream: bool
) -> List["ResultData"]:
"""Runs the graph with the given inputs."""
# inputs is {"message": "Hello, world!"}
@ -106,7 +110,9 @@ class Graph:
if not isinstance(inputs_values, list):
inputs_values = [inputs_values]
for input_value in inputs_values:
run_outputs = await self._run({INPUT_FIELD_NAME: input_value})
run_outputs = await self._run(
{INPUT_FIELD_NAME: input_value}, stream=stream
)
logger.debug(f"Run outputs: {run_outputs}")
outputs.extend(run_outputs)
return outputs

View file

@ -44,6 +44,7 @@ class Vertex:
) -> None:
# is_external means that the Vertex send or receives data from
# an external source (e.g the chat)
self.will_stream = False
self.updated_raw_params = False
self.id: str = data["id"]
self.is_input = any(
@ -391,6 +392,8 @@ class Vertex:
ValueError: If any key in new_params is not found in self._raw_params.
"""
# First check if the input_value in _raw_params is not a vertex
if not new_params:
return
if any(isinstance(self._raw_params.get(key), Vertex) for key in new_params):
return
self._raw_params.update(new_params)
@ -456,7 +459,7 @@ class Vertex:
await self._build_node_and_update_params(key, value, user_id)
elif isinstance(value, list) and self._is_list_of_nodes(value):
await self._build_list_of_nodes_and_update_params(key, value, user_id)
elif key not in self.params:
elif key not in self.params or self.updated_raw_params:
self.params[key] = value
def _is_node(self, value):
@ -610,6 +613,7 @@ class Vertex:
async def build(
self,
user_id=None,
inputs: Optional[Dict[str, Any]] = None,
requester: Optional["Vertex"] = None,
**kwargs,
) -> Any:
@ -622,6 +626,9 @@ class Vertex:
return self.get_requester_result(requester)
self._reset()
if self.is_input:
self.update_raw_params(inputs)
# Run steps
for step in self.steps:
if step not in self.steps_ran:

View file

@ -11,7 +11,7 @@ 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.services.monitor.utils import log_message
from langflow.services.monitor.utils import log_vertex_build
from langflow.utils.schemas import ChatOutputResponse
@ -394,6 +394,8 @@ class ChatVertex(StatelessVertex):
sender_name=sender_name,
stream_url=stream_url,
)
self.will_stream = stream_url is not None
if artifacts:
self.artifacts = artifacts.model_dump()
if isinstance(self._built_object, (AsyncIterator, Iterator)):
@ -434,19 +436,25 @@ class ChatVertex(StatelessVertex):
self._built_result = complete_message
# Update artifacts with the message
# and remove the stream_url
self._finalize_build()
logger.debug(f"Streamed message: {complete_message}")
await log_message(
sender=self.params.get("sender", ""),
sender_name=self.params.get("sender_name", ""),
message=complete_message,
session_id=self.params.get("session_id", ""),
await log_vertex_build(
flow_id=self.graph.flow_id,
vertex_id=self.id,
valid=True,
params=self._built_object_repr(),
data=self.result,
artifacts=self.artifacts,
)
self._validate_built_object()
self._built = True
async def consume_async_generator(self):
async for _ in self.stream():
pass
class RoutingVertex(StatelessVertex):
def __init__(self, data: Dict, graph):

View file

@ -1,6 +1,7 @@
import ast
import os
import zlib
from pathlib import Path
from loguru import logger
@ -79,9 +80,13 @@ class DirectoryReader:
except Exception as e:
logger.error(f"Error while loading component: {e}")
continue
items.append({"name": menu["name"], "path": menu["path"], "components": components})
items.append(
{"name": menu["name"], "path": menu["path"], "components": components}
)
filtered = [menu for menu in items if menu["components"]]
logger.debug(f'Filtered components {"with errors" if with_errors else ""}: {len(filtered)}')
logger.debug(
f'Filtered components {"with errors" if with_errors else ""}: {len(filtered)}'
)
return {"menu": filtered}
def validate_code(self, file_content):
@ -114,15 +119,24 @@ class DirectoryReader:
Walk through the directory path and return a list of all .py files.
"""
if not (safe_path := self.get_safe_path()):
raise CustomComponentPathValueError(f"The path needs to start with '{self.base_path}'.")
raise CustomComponentPathValueError(
f"The path needs to start with '{self.base_path}'."
)
file_list = []
for root, _, files in os.walk(safe_path):
file_list.extend(
os.path.join(root, filename)
for filename in files
if filename.endswith(".py") and not filename.startswith("__")
)
safe_path_obj = Path(safe_path)
for file_path in safe_path_obj.rglob("*.py"):
# The other condtion is that it should be
# in the safe_path/[folder]/[file].py format
# any folders below [folder] will be ignored
# basically the parent folder of the file should be a
# folder in the safe_path
if (
file_path.is_file()
and file_path.parent.parent == safe_path_obj
and not file_path.name.startswith("__")
):
file_list.append(str(file_path))
return file_list
def find_menu(self, response, menu_name):
@ -159,7 +173,9 @@ class DirectoryReader:
for node in ast.walk(module):
if isinstance(node, ast.FunctionDef):
for arg in node.args.args:
if self._is_type_hint_in_arg_annotation(arg.annotation, type_hint_name):
if self._is_type_hint_in_arg_annotation(
arg.annotation, type_hint_name
):
return True
except SyntaxError:
# Returns False if the code is not valid Python
@ -177,14 +193,16 @@ class DirectoryReader:
and annotation.value.id == type_hint_name
)
def is_type_hint_used_but_not_imported(self, type_hint_name: str, code: str) -> bool:
def is_type_hint_used_but_not_imported(
self, type_hint_name: str, code: str
) -> bool:
"""
Check if a type hint is used but not imported in the given code.
"""
try:
return self._is_type_hint_used_in_args(type_hint_name, code) and not self._is_type_hint_imported(
return self._is_type_hint_used_in_args(
type_hint_name, code
)
) and not self._is_type_hint_imported(type_hint_name, code)
except SyntaxError:
# Returns True if there's something wrong with the code
# TODO : Find a better way to handle this
@ -205,9 +223,9 @@ class DirectoryReader:
return False, "Syntax error"
elif not self.validate_build(file_content):
return False, "Missing build function"
elif self._is_type_hint_used_in_args("Optional", file_content) and not self._is_type_hint_imported(
elif self._is_type_hint_used_in_args(
"Optional", file_content
):
) and not self._is_type_hint_imported("Optional", file_content):
return (
False,
"Type hint 'Optional' is used but not imported in the code.",
@ -223,7 +241,9 @@ class DirectoryReader:
from the .py files in the directory.
"""
response = {"menu": []}
logger.debug("-------------------- Building component menu list --------------------")
logger.debug(
"-------------------- Building component menu list --------------------"
)
for file_path in file_paths:
menu_name = os.path.basename(os.path.dirname(file_path))
@ -243,7 +263,9 @@ class DirectoryReader:
# first check if it's already CamelCase
if "_" in component_name:
component_name_camelcase = " ".join(word.title() for word in component_name.split("_"))
component_name_camelcase = " ".join(
word.title() for word in component_name.split("_")
)
else:
component_name_camelcase = component_name
@ -251,7 +273,9 @@ class DirectoryReader:
try:
output_types = self.get_output_types_from_code(result_content)
except Exception as exc:
logger.exception(f"Error while getting output types from code: {str(exc)}")
logger.exception(
f"Error while getting output types from code: {str(exc)}"
)
output_types = [component_name_camelcase]
else:
output_types = [component_name_camelcase]
@ -267,7 +291,9 @@ class DirectoryReader:
if menu_result not in response["menu"]:
response["menu"].append(menu_result)
logger.debug("-------------------- Component menu list built --------------------")
logger.debug(
"-------------------- Component menu list built --------------------"
)
return response
@staticmethod

View file

@ -1,11 +1,18 @@
from langflow.interface.custom.directory_reader import DirectoryReader
from langflow.template.frontend_node.custom_components import CustomComponentFrontendNode
from loguru import logger
from langflow.interface.custom.directory_reader import DirectoryReader
from langflow.template.frontend_node.custom_components import (
CustomComponentFrontendNode,
)
def merge_nested_dicts_with_renaming(dict1, dict2):
for key, value in dict2.items():
if key in dict1 and isinstance(value, dict) and isinstance(dict1.get(key), dict):
if (
key in dict1
and isinstance(value, dict)
and isinstance(dict1.get(key), dict)
):
for sub_key, sub_value in value.items():
# if sub_key in dict1[key]:
# new_key = get_new_key(dict1[key], sub_key)
@ -62,7 +69,9 @@ def build_custom_component_list_from_path(path: str):
file_list = load_files_from_path(path)
reader = DirectoryReader(path, False)
valid_components, invalid_components = build_and_validate_all_files(reader, file_list)
valid_components, invalid_components = build_and_validate_all_files(
reader, file_list
)
valid_menu = build_valid_menu(valid_components)
invalid_menu = build_invalid_menu(invalid_components)
@ -109,7 +118,9 @@ def build_invalid_menu_items(menu_item):
menu_items[component_name] = component_template
logger.debug(f"Added {component_name} to invalid menu.")
except Exception as exc:
logger.exception(f"Error while creating custom component [{component_name}]: {str(exc)}")
logger.exception(
f"Error while creating custom component [{component_name}]: {str(exc)}"
)
return menu_items
@ -136,12 +147,14 @@ def determine_component_name(component):
def build_menu_items(menu_item):
"""Build menu items for a given menu."""
menu_items = {}
logger.debug(f"Building menu items for {menu_item['name']}")
logger.debug(f"Loading {len(menu_item['components'])} components")
for component_name, component_template, component in menu_item["components"]:
try:
menu_items[component_name] = component_template
logger.debug(f"Added {component_name} to valid menu.")
except Exception as exc:
logger.error(f"Error loading Component: {component['output_types']}")
logger.exception(f"Error while building custom component {component['output_types']}: {exc}")
return menu_items
logger.exception(
f"Error while building custom component {component['output_types']}: {exc}"
)
return menu_items

View file

@ -271,18 +271,26 @@ async def run_graph(
graph: Union["Graph", dict],
flow_id: str,
session_id: str,
stream: bool,
inputs: Optional[Union[dict, List[dict]]] = None,
artifacts: Optional[Dict[str, Any]] = None,
session_service: Optional[SessionService] = None,
):
"""Run the graph and generate the result"""
if isinstance(graph, dict):
graph_data = graph
graph = Graph.from_payload(graph, flow_id=flow_id)
else:
graph_data = graph._graph_data
if not session_id:
session_id = session_service.generate_key(
session_id=flow_id, data_graph=graph_data
)
outputs = await graph.run(inputs)
outputs = await graph.run(inputs, stream=stream)
if session_id and session_service:
session_service.update_session(session_id, (graph, artifacts))
return outputs
return outputs, session_id
def validate_input(

View file

@ -5,16 +5,17 @@ from typing import TYPE_CHECKING
import sqlalchemy as sa
from alembic import command, util
from alembic.config import Config
from loguru import logger
from sqlalchemy import inspect
from sqlalchemy.exc import OperationalError
from sqlmodel import Session, SQLModel, create_engine, select, text
from langflow.services.base import Service
from langflow.services.database import models # noqa
from langflow.services.database.models.user.crud import get_user_by_username
from langflow.services.database.utils import Result, TableResults
from langflow.services.deps import get_settings_service
from langflow.services.utils import teardown_superuser
from loguru import logger
from sqlalchemy import inspect
from sqlalchemy.exc import OperationalError
from sqlmodel import Session, SQLModel, create_engine, select, text
if TYPE_CHECKING:
from sqlalchemy.engine import Engine
@ -39,7 +40,7 @@ class DatabaseService(Service):
connect_args = {"check_same_thread": False}
else:
connect_args = {}
return create_engine(self.database_url, connect_args=connect_args, max_overflow=-1)
return create_engine(self.database_url, connect_args=connect_args)
def __enter__(self):
self._session = Session(self.engine)

View file

@ -8,7 +8,11 @@ import Checkmark from "../../components/ui/checkmark";
import Loading from "../../components/ui/loading";
import { Textarea } from "../../components/ui/textarea";
import Xmark from "../../components/ui/xmark";
import { priorityFields, statusBuild, statusBuilding } from "../../constants/constants";
import {
priorityFields,
statusBuild,
statusBuilding,
} from "../../constants/constants";
import { BuildStatus } from "../../constants/enums";
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
import { useDarkStore } from "../../stores/darkStore";
@ -211,9 +215,7 @@ export default function GenericNode({
return "inactive-status";
}
if (buildStatus === BuildStatus.BUILT && isInvalid) {
return isDark
? "built-invalid-status-dark"
: "built-invalid-status";
return isDark ? "built-invalid-status-dark" : "built-invalid-status";
} else if (buildStatus === BuildStatus.BUILDING) {
return "building-status";
} else {
@ -296,7 +298,7 @@ export default function GenericNode({
<div
className={
"generic-node-title-arrangement rounded-full" +
(!showNode && " justify-center")
(!showNode && " justify-center ")
}
>
{iconNodeRender()}
@ -332,21 +334,20 @@ export default function GenericNode({
) : (
<ShadTooltip content={data.node?.display_name}>
<div className="group flex items-center gap-2.5">
<div
onDoubleClick={(event) => {
if (nameEditable) {
setInputName(true);
}
takeSnapshot();
event.stopPropagation();
event.preventDefault();
}}
data-testid={"title-" + data.node?.display_name}
className="generic-node-tooltip-div text-primary"
>
{data.node?.display_name}
</div>
<div
onDoubleClick={(event) => {
if (nameEditable) {
setInputName(true);
}
takeSnapshot();
event.stopPropagation();
event.preventDefault();
}}
data-testid={"title-" + data.node?.display_name}
className="generic-node-tooltip-div text-primary"
>
{data.node?.display_name}
</div>
{nameEditable && (
<div
@ -465,7 +466,7 @@ export default function GenericNode({
if (buildStatus === BuildStatus.BUILDING || isBuilding)
return;
setValidationStatus(null);
buildFlow(data.id);
buildFlow({nodeId: data.id});
}}
>
<div>
@ -478,11 +479,11 @@ export default function GenericNode({
) : (
<div className="max-h-96 overflow-auto">
{typeof validationStatus.params === "string"
? (`${durationString}\n${validationStatus.params}`
? `${durationString}\n${validationStatus.params}`
.split("\n")
.map((line, index) => (
<div key={index}>{line}</div>
)))
))
: durationString}
</div>
)

View file

@ -1,3 +1,4 @@
import { Cross2Icon } from "@radix-ui/react-icons";
import { useState } from "react";
import IconComponent from "../../components/genericIconComponent";
import {
@ -46,15 +47,15 @@ export default function AlertDropdown({
setTimeout(clearNotificationList, 100);
}}
>
<IconComponent name="Trash2" className="h-[1.1rem] w-[1.1rem]" />
<IconComponent name="Trash2" className="h-4 w-4" />
</button>
<button
className="text-foreground hover:text-status-red"
className="text-foreground opacity-70 hover:opacity-100"
onClick={() => {
setOpen(false);
}}
>
<IconComponent name="X" className="h-5 w-5" />
<Cross2Icon className="h-4 w-4" />
</button>
</div>
</div>

View file

@ -19,12 +19,12 @@ export default function IOInputField({
<Textarea
className="w-full"
placeholder={"Enter text..."}
value={node.data.node!.template["value"].value}
value={node.data.node!.template["input_value"].value}
onChange={(e) => {
e.target.value;
if (node) {
let newNode = cloneDeep(node);
newNode.data.node!.template["value"].value = e.target.value;
newNode.data.node!.template["input_value"].value = e.target.value;
setNode(node.id, newNode);
}
}}
@ -49,12 +49,12 @@ export default function IOInputField({
<Textarea
className="w-full custom-scroll"
placeholder={"Enter text..."}
value={node.data.node!.template["value"]}
value={node.data.node!.template["input_value"]}
onChange={(e) => {
e.target.value;
if (node) {
let newNode = cloneDeep(node);
newNode.data.node!.template["value"].value = e.target.value;
newNode.data.node!.template["input_value"].value = e.target.value;
setNode(node.id, newNode);
}
}}

View file

@ -30,12 +30,12 @@ export default function IOOutputView({
<Textarea
className="w-full custom-scroll"
placeholder={"Enter text..."}
value={node.data.node!.template["value"]}
value={node.data.node!.template["input_value"]}
onChange={(e) => {
e.target.value;
if (node) {
let newNode = cloneDeep(node);
newNode.data.node!.template["value"].value = e.target.value;
newNode.data.node!.template["input_value"].value = e.target.value;
setNode(node.id, newNode);
}
}}

View file

@ -1,9 +1,13 @@
import { cloneDeep } from "lodash";
import { useEffect, useState } from "react";
import { CHAT_FORM_DIALOG_SUBTITLE, outputsModalTitle, textInputModalTitle } from "../../constants/constants";
import {
CHAT_FORM_DIALOG_SUBTITLE,
outputsModalTitle,
textInputModalTitle,
} from "../../constants/constants";
import BaseModal from "../../modals/baseModal";
import useAlertStore from "../../stores/alertStore";
import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { updateVerticesOrder } from "../../utils/buildUtils";
import { cn } from "../../utils/utils";
import AccordionComponent from "../AccordionComponent";
import IOInputField from "../IOInputField";
@ -40,27 +44,30 @@ export default function IOView({ children, open, setOpen }): JSX.Element {
{ type: string; id: string } | undefined
>(undefined);
const { getNode, setNode, buildFlow, getFlow } = useFlowStore();
const { setErrorData } = useAlertStore();
const buildFlow = useFlowStore((state) => state.buildFlow);
const setIsBuilding = useFlowStore((state) => state.setIsBuilding);
const [lockChat, setLockChat] = useState(false);
const [chatValue, setChatValue] = useState("");
const isBuilding = useFlowStore((state) => state.isBuilding);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
async function updateVertices() {
return updateVerticesOrder(currentFlow!.id, null);
}
useEffect(() => {
if (open) {
updateVertices();
}
}, [open, currentFlow]);
async function sendMessage(count = 1): Promise<void> {
if (isBuilding) return;
const { nodes, edges } = getFlow();
setIsBuilding(true);
setLockChat(true);
setChatValue("");
const chatInputNode = nodes.find((node) => node.id === chatInput?.id);
if (chatInputNode) {
let newNode = cloneDeep(chatInputNode);
newNode.data.node!.template["message"].value = chatValue;
setNode(chatInput!.id, newNode);
}
for (let i = 0; i < count; i++) {
await buildFlow().catch((err) => {
await buildFlow({ input_value: chatValue }).catch((err) => {
console.error(err);
setLockChat(false);
});
@ -104,7 +111,7 @@ export default function IOView({ children, open, setOpen }): JSX.Element {
<Tabs
value={selectedTab.toString()}
className={
"flex h-full flex-col overflow-y-auto custom-scroll rounded-md border bg-muted text-center"
"flex h-full flex-col overflow-y-auto rounded-md border bg-muted text-center custom-scroll"
}
onValueChange={(value) => {
setSelectedTab(Number(value));
@ -266,24 +273,27 @@ export default function IOView({ children, open, setOpen }): JSX.Element {
{selectedViewField.type}
</div>
<div className="h-full">
{inputs.some(
(input) => input.id === selectedViewField.id
) ? (
<IOInputField
inputType={selectedViewField.type!}
inputId={selectedViewField.id!}
/>
) : (
<IOOutputView
outputType={selectedViewField.type!}
outputId={selectedViewField.id!}
/>
)}
{inputs.some(
(input) => input.id === selectedViewField.id
) ? (
<IOInputField
inputType={selectedViewField.type!}
inputId={selectedViewField.id!}
/>
) : (
<IOOutputView
outputType={selectedViewField.type!}
outputId={selectedViewField.id!}
/>
)}
</div>
</div>
)}
<div
className={cn("flex w-full h-full",selectedViewField ? "hidden" : "")}
className={cn(
"flex h-full w-full",
selectedViewField ? "hidden" : ""
)}
>
<NewChatView
sendMessage={sendMessage}

View file

@ -23,8 +23,6 @@ export default function BuildTrigger({
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const setErrorData = useAlertStore((state) => state.setErrorData);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setFlowState = useFlowStore((state) => state.setFlowState);
const eventClick = isBuilding ? "pointer-events-none" : "";
const [progress, setProgress] = useState(0);
@ -47,7 +45,7 @@ export default function BuildTrigger({
setIsBuilding(true);
await enforceMinimumLoadingTime(startTime, minimumLoadingTime);
await buildFlow();
await buildFlow({});
} catch (error) {
console.error("Error:", error);
} finally {

View file

@ -4,6 +4,7 @@ import { Textarea } from "../../../components/ui/textarea";
import { chatInputType } from "../../../types/components";
import { classNames } from "../../../utils/utils";
import { chatInputPlaceholder, chatInputPlaceholderSend } from "../../../constants/constants";
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
export default function ChatInput({
lockChat,
@ -14,20 +15,21 @@ export default function ChatInput({
noInput,
}: chatInputType): JSX.Element {
const [repeat, setRepeat] = useState(1);
const saveLoading = useFlowsManagerStore((state) => state.saveLoading);
useEffect(() => {
if (!lockChat && inputRef.current) {
inputRef.current.focus();
}
}, [lockChat, inputRef]);
function handleChange(value: number) {
/* function handleChange(value: number) {
console.log(value);
if (value > 0) {
setRepeat(value);
} else {
setRepeat(1);
}
}
} */
useEffect(() => {
if (inputRef.current) {
@ -41,13 +43,13 @@ export default function ChatInput({
<div className="relative w-full">
<Textarea
onKeyDown={(event) => {
if (event.key === "Enter" && !lockChat && !event.shiftKey) {
if (event.key === "Enter" && !lockChat && !saveLoading && !event.shiftKey) {
sendMessage(repeat);
}
}}
rows={1}
ref={inputRef}
disabled={lockChat || noInput}
disabled={lockChat || noInput || saveLoading}
style={{
resize: "none",
bottom: `${inputRef?.current?.scrollHeight}px`,
@ -58,12 +60,12 @@ export default function ChatInput({
: "hidden"
}`,
}}
value={lockChat ? "Thinking..." : chatValue}
value={lockChat ? "Thinking..." : (saveLoading ? "Saving..." : chatValue)}
onChange={(event): void => {
setChatValue(event.target.value);
}}
className={classNames(
lockChat
(lockChat || saveLoading)
? " form-modal-lock-true bg-input"
: noInput
? "form-modal-no-input bg-input"
@ -87,10 +89,10 @@ export default function ChatInput({
? "text-primary"
: "bg-chat-send text-background"
)}
disabled={lockChat}
disabled={lockChat || saveLoading}
onClick={(): void => sendMessage(repeat)}
>
{lockChat ? (
{lockChat || saveLoading ? (
<IconComponent
name="Lock"
className="form-modal-lock-icon"

View file

@ -1,5 +1,5 @@
import Convert from "ansi-to-html";
import { useEffect, useMemo, useState, useRef } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import Markdown from "react-markdown";
import rehypeMathjax from "rehype-mathjax";
import remarkGfm from "remark-gfm";
@ -9,17 +9,17 @@ import Robot from "../../../assets/robot.png";
import SanitizedHTMLWrapper from "../../../components/SanitizedHTMLWrapper";
import CodeTabsComponent from "../../../components/codeTabsComponent";
import IconComponent from "../../../components/genericIconComponent";
import useFlowStore from "../../../stores/flowStore";
import { chatMessagePropsType } from "../../../types/components";
import { classNames } from "../../../utils/utils";
import FileCard from "../fileComponent";
import useFlowStore from "../../../stores/flowStore";
export default function ChatMessage({
chat,
lockChat,
lastMessage,
updateChat,
setLockChat
setLockChat,
}: chatMessagePropsType): JSX.Element {
const convert = new Convert({ newline: true });
const [hidden, setHidden] = useState(true);
@ -40,8 +40,6 @@ export default function ChatMessage({
chatMessageRef.current = chatMessage;
}, [chatMessage]);
// The idea now is that chat.stream_url MAY be a URL if we should stream the output of the chat
// probably the message is empty when we have a stream_url
// what we need is to update the chat_message with the SSE data
@ -70,9 +68,7 @@ export default function ChatMessage({
});
};
useEffect(() => {
console.log("chatMessage", chatMessage);
if (streamUrl && !isStreaming) {
setLockChat(true);
streamChunks(streamUrl)
@ -92,8 +88,8 @@ export default function ChatMessage({
useEffect(() => {
return () => {
eventSource.current?.close();
}
}, [])
};
}, []);
useEffect(() => {
const element = document.getElementById("last-chat-message");
@ -222,7 +218,7 @@ dark:prose-invert"
},
]}
activeTab={"0"}
setActiveTab={() => { }}
setActiveTab={() => {}}
/>
) : (
<code className={className} {...props}>
@ -279,33 +275,33 @@ dark:prose-invert"
<span className="prose text-primary word-break-break-word dark:prose-invert">
{promptOpen
? template?.split("\n")?.map((line, index) => {
const regex = /{([^}]+)}/g;
let match;
let parts: Array<JSX.Element | string> = [];
let lastIndex = 0;
while ((match = regex.exec(line)) !== null) {
// Push text up to the match
if (match.index !== lastIndex) {
parts.push(line.substring(lastIndex, match.index));
}
// Push div with matched text
if (chat.message[match[1]]) {
parts.push(
<span className="chat-message-highlight">
{chat.message[match[1]]}
</span>
);
}
const regex = /{([^}]+)}/g;
let match;
let parts: Array<JSX.Element | string> = [];
let lastIndex = 0;
while ((match = regex.exec(line)) !== null) {
// Push text up to the match
if (match.index !== lastIndex) {
parts.push(line.substring(lastIndex, match.index));
}
// Push div with matched text
if (chat.message[match[1]]) {
parts.push(
<span className="chat-message-highlight">
{chat.message[match[1]]}
</span>
);
}
// Update last index
lastIndex = regex.lastIndex;
}
// Push text after the last match
if (lastIndex !== line.length) {
parts.push(line.substring(lastIndex));
}
return <p>{parts}</p>;
})
// Update last index
lastIndex = regex.lastIndex;
}
// Push text after the last match
if (lastIndex !== line.length) {
parts.push(line.substring(lastIndex));
}
return <p>{parts}</p>;
})
: chatMessage}
</span>
</>

View file

@ -1,6 +1,10 @@
import _ from "lodash";
import { useEffect, useRef, useState } from "react";
import IconComponent from "../../components/genericIconComponent";
import { NOCHATOUTPUT_NOTICE_ALERT } from "../../constants/alerts_constants";
import {
chatFirstInitialText,
chatSecondInitialText,
} from "../../constants/constants";
import { deleteFlowPool } from "../../controllers/API";
import useAlertStore from "../../stores/alertStore";
import useFlowStore from "../../stores/flowStore";
@ -14,8 +18,6 @@ import {
import { classNames } from "../../utils/utils";
import ChatInput from "./chatInput";
import ChatMessage from "./chatMessage";
import { INFO_MISSING_ALERT, NOCHATOUTPUT_NOTICE_ALERT } from "../../constants/alerts_constants";
import { chatFirstInitialText, chatSecondInitialText } from "../../constants/constants";
export default function NewChatView({
sendMessage,
@ -34,7 +36,7 @@ export default function NewChatView({
const inputIds = inputs.map((obj) => obj.id);
const outputIds = outputs.map((obj) => obj.id);
const outputTypes = outputs.map((obj) => obj.type);
const updateFlowPool = useFlowStore((state)=>state.updateFlowPool)
const updateFlowPool = useFlowStore((state) => state.updateFlowPool);
useEffect(() => {
if (!outputTypes.includes("ChatOutput")) {
@ -73,7 +75,7 @@ export default function NewChatView({
isSend: !is_ai,
message: message,
sender_name,
componentId: output.id,
componentId: output.id,
stream_url: stream_url,
};
} catch (e) {
@ -120,22 +122,26 @@ export default function NewChatView({
chat: ChatMessageType,
message: string,
stream_url?: string
) {
if (message === "") return;
chat.message = message;
) {
if (message === "") return;
chat.message = message;
// chat is one of the chatHistory
updateFlowPool(chat.componentId,{message,sender_name:chat.sender_name??"Bot",sender:"Machine"})
updateFlowPool(chat.componentId, {
message,
sender_name: chat.sender_name ?? "Bot",
sender: chat.isSend ? "User" : "Machine",
});
// setChatHistory((oldChatHistory) => {
// const index = oldChatHistory.findIndex((ch) => ch.id === chat.id);
// if (index === -1) return oldChatHistory;
// let newChatHistory = _.cloneDeep(oldChatHistory);
// newChatHistory = [
// ...newChatHistory.slice(0, index),
// chat,
// ...newChatHistory.slice(index + 1),
// ];
// console.log("newChatHistory:", newChatHistory);
// return newChatHistory;
// const index = oldChatHistory.findIndex((ch) => ch.id === chat.id);
// if (index === -1) return oldChatHistory;
// let newChatHistory = _.cloneDeep(oldChatHistory);
// newChatHistory = [
// ...newChatHistory.slice(0, index),
// chat,
// ...newChatHistory.slice(index + 1),
// ];
// console.log("newChatHistory:", newChatHistory);
// return newChatHistory;
// });
}
@ -160,7 +166,7 @@ export default function NewChatView({
{chatHistory?.length > 0 ? (
chatHistory.map((chat, index) => (
<ChatMessage
setLockChat={setLockChat}
setLockChat={setLockChat}
lockChat={lockChat}
chat={chat}
lastMessage={chatHistory.length - 1 === index ? true : false}

View file

@ -123,7 +123,7 @@ function ApiInterceptor() {
async function clearBuildVerticesState(error) {
if (error?.response?.status === 500) {
const vertices = useFlowStore.getState().verticesBuild;
useFlowStore.getState().updateBuildStatus(vertices, BuildStatus.BUILT);
useFlowStore.getState().updateBuildStatus(vertices?.verticesIds ?? [], BuildStatus.BUILT);
useFlowStore.getState().setIsBuilding(false);
}
}

View file

@ -869,9 +869,10 @@ export async function getVerticesOrder(
export async function postBuildVertex(
flowId: string,
vertexId: string
vertexId: string,
input_value: string,
): Promise<AxiosResponse<VertexBuildTypeAPI>> {
return await api.post(`${BASE_URL_API}build/${flowId}/vertices/${vertexId}`);
return await api.post(`${BASE_URL_API}build/${flowId}/vertices/${vertexId}`, input_value ? {inputs: {input_value: input_value}} : undefined);
}
export async function downloadImage({ flowId, fileName }): Promise<any> {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 406 KiB

Before After
Before After

View file

@ -1,5 +1,6 @@
import _, { cloneDeep } from "lodash";
import { useEffect, useState } from "react";
import { useUpdateNodeInternals } from "reactflow";
import ShadTooltip from "../../../../components/ShadTooltipComponent";
import CodeAreaComponent from "../../../../components/codeAreaComponent";
import IconComponent from "../../../../components/genericIconComponent";
@ -26,7 +27,6 @@ import {
updateFlowPosition,
} from "../../../../utils/reactflowUtils";
import { classNames, cn } from "../../../../utils/utils";
import { useUpdateNodeInternals } from "reactflow";
export default function NodeToolbarComponent({
data,
@ -94,11 +94,12 @@ export default function NodeToolbarComponent({
const handleModalWShortcut = useFlowStore(state => state.handleModalWShortcut);
useEffect(() => {
console.log(openCodeModalWShortcut)
setOpenModal(openCodeModalWShortcut)
}, [openCodeModalWShortcut, handleModalWShortcut])
const setLastCopiedSelection = useFlowStore(state => state.setLastCopiedSelection);
const setLastCopiedSelection = useFlowStore(
(state) => state.setLastCopiedSelection
);
useEffect(() => {
setFlowComponent(createFlowComponent(cloneDeep(data), version));
}, [
@ -153,8 +154,8 @@ export default function NodeToolbarComponent({
deleteNode(data.id);
break;
case "copy":
const node = nodes.filter(node => node.id === data.id)
setLastCopiedSelection({ nodes: _.cloneDeep(node), edges: [] })
const node = nodes.filter((node) => node.id === data.id);
setLastCopiedSelection({ nodes: _.cloneDeep(node), edges: [] });
}
};
@ -242,7 +243,7 @@ export default function NodeToolbarComponent({
id={"code-input-node-toolbar-" + name}
/>
</div>
<IconComponent name="Code" className="h-4 w-4" />
<IconComponent name="TerminalSquare" className="h-4 w-4" />
</button>
</ShadTooltip>
) : (
@ -380,13 +381,11 @@ export default function NodeToolbarComponent({
className="relative top-0.5 mr-2 h-4 w-4 "
/>{" "}
<span className="">Copy</span>{" "}
<IconComponent
name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
></IconComponent>
<span className="absolute right-2 top-[0.5em]">C</span>
<IconComponent
name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
></IconComponent>
<span className="absolute right-2 top-[0.5em]">C</span>
</div>
</SelectItem>
{hasStore && (
@ -459,7 +458,7 @@ export default function NodeToolbarComponent({
<span>
<IconComponent
name="Delete"
className="absolute right-2 top-2 h-4 w-4 text-red-400 stroke-2"
className="absolute right-2 top-2 h-4 w-4 stroke-2 text-red-400"
></IconComponent>
</span>
</div>

View file

@ -9,9 +9,12 @@ import {
applyNodeChanges,
} from "reactflow";
import { create } from "zustand";
import { FLOW_BUILD_SUCCESS_ALERT, MISSED_ERROR_ALERT } from "../constants/alerts_constants";
import {
FLOW_BUILD_SUCCESS_ALERT,
MISSED_ERROR_ALERT,
} from "../constants/alerts_constants";
import { BuildStatus } from "../constants/enums";
import { getFlowPool, updateFlowInDatabase } from "../controllers/API";
import { getFlowPool } from "../controllers/API";
import { VertexBuildTypeAPI } from "../types/api";
import {
NodeDataType,
@ -19,7 +22,12 @@ import {
sourceHandleType,
targetHandleType,
} from "../types/flow";
import { ChatOutputType, FlowPoolObjectType, FlowStoreType, chatInputType } from "../types/zustand/flow";
import {
ChatOutputType,
FlowPoolObjectType,
FlowStoreType,
chatInputType,
} from "../types/zustand/flow";
import { buildVertices } from "../utils/buildUtils";
import {
cleanEdges,
@ -69,23 +77,25 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
}
get().setFlowPool(newFlowPool);
},
updateFlowPool:(nodeId:string,data:FlowPoolObjectType| ChatOutputType | chatInputType,buildId?:string)=>{
updateFlowPool: (
nodeId: string,
data: FlowPoolObjectType | ChatOutputType | chatInputType,
buildId?: string
) => {
let newFlowPool = cloneDeep({ ...get().flowPool });
if (!newFlowPool[nodeId]){
if (!newFlowPool[nodeId]) {
return;
}
else {
let index = newFlowPool[nodeId].length-1;
if(buildId){
index = newFlowPool[nodeId].findIndex((flow)=>flow.id===buildId);
} else {
let index = newFlowPool[nodeId].length - 1;
if (buildId) {
index = newFlowPool[nodeId].findIndex((flow) => flow.id === buildId);
}
//check if the data is a flowpool object
if((data as FlowPoolObjectType).data?.artifacts!==undefined){
newFlowPool[nodeId][index] = (data as FlowPoolObjectType);
if ((data as FlowPoolObjectType).data?.artifacts !== undefined) {
newFlowPool[nodeId][index] = data as FlowPoolObjectType;
}
//update data artifact
else
{
else {
newFlowPool[nodeId][index].data.artifacts = data;
}
}
@ -404,7 +414,13 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
});
});
},
buildFlow: async (nodeId?: string) => {
buildFlow: async ({
nodeId,
input_value,
}: {
nodeId?: string;
input_value?: string;
}) => {
get().setIsBuilding(true);
const currentFlow = useFlowsManagerStore.getState().currentFlow;
const setSuccessData = useAlertStore.getState().setSuccessData;
@ -427,25 +443,19 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
function handleBuildUpdate(
vertexBuildData: VertexBuildTypeAPI,
status: BuildStatus,
buildId:string
buildId: string
) {
if (vertexBuildData && vertexBuildData.inactive_vertices) {
get().removeFromVerticesBuild(vertexBuildData.inactive_vertices);
}
get().addDataToFlowPool({...vertexBuildData,buildId}, vertexBuildData.id);
get().addDataToFlowPool(
{ ...vertexBuildData, buildId },
vertexBuildData.id
);
useFlowStore.getState().updateBuildStatus([vertexBuildData.id], status);
}
await updateFlowInDatabase({
data: {
nodes: get().nodes,
edges: get().edges,
viewport: get().reactFlowInstance?.getViewport()!,
},
id: currentFlow!.id,
name: currentFlow!.name,
description: currentFlow!.description,
});
await buildVertices({
input_value,
flowId: currentFlow!.id,
nodeId,
onGetOrderSuccess: () => {
@ -483,16 +493,22 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
viewport: get().reactFlowInstance?.getViewport()!,
};
},
updateVerticesBuild: (vertices: string[]) => {
updateVerticesBuild: (
vertices: { verticesIds: string[], verticesOrder: string[][], verticesLayers: string[][], runId: string } | null
) => {
set({ verticesBuild: vertices });
},
verticesBuild: [],
verticesBuild: null,
removeFromVerticesBuild: (vertices: string[]) => {
const verticesBuild = get().verticesBuild;
if (!verticesBuild) return;
set({
verticesBuild: get().verticesBuild.filter(
(vertex) => !vertices.includes(vertex)
),
verticesBuild: {
...verticesBuild,
verticesIds: get().verticesBuild!.verticesIds.filter(
(vertex) => !vertices.includes(vertex)
),
},
});
},
updateBuildStatus: (nodeIdList: string[], status: BuildStatus) => {

View file

@ -83,6 +83,7 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
if (saveTimeoutId) {
clearTimeout(saveTimeoutId);
}
set({ saveLoading: true });
// Set up a new timeout.
saveTimeoutId = setTimeout(() => {
if (get().currentFlow) {
@ -92,7 +93,7 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
);
}
set({ saveLoading: true });
}, 1000); // Delay of 1000ms.
}, 500); // Delay of 500ms because chat message depends on it.
},
saveFlow: (flow: FlowType, silent?: boolean) => {
set({ saveLoading: true });

View file

@ -88,11 +88,11 @@ export type FlowStoreType = {
getFilterEdge: any[];
onConnect: (connection: Connection) => void;
unselectAll: () => void;
buildFlow: (nodeId?: string) => Promise<void>;
buildFlow: ({nodeId, input_value}: {nodeId?: string, input_value?: string}) => Promise<void>;
getFlow: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport };
updateVerticesBuild: (vertices: string[]) => void;
removeFromVerticesBuild: (vertices: string[]) => void;
verticesBuild: string[];
updateVerticesBuild: (vertices: {verticesIds: string[], verticesLayers: string[][], verticesOrder: string[][], runId: string} | null) => void;
removeFromVerticesBuild: (vertices: string[]) => void;
verticesBuild: {verticesIds: string[], verticesLayers: string[][], verticesOrder: string[][], runId: string} | null;
updateBuildStatus: (nodeId: string[], status: BuildStatus) => void;
revertBuiltStatusFromBuilding: () => void;
flowBuildStatus: { [key: string]: BuildStatus };

View file

@ -7,9 +7,14 @@ import { VertexBuildTypeAPI } from "../types/api";
type BuildVerticesParams = {
flowId: string; // Assuming FlowType is the type for your flow
input_value?: any; // Replace any with the actual type if it's not any
nodeId?: string | null; // Assuming nodeId is of type string, and it's optional
onGetOrderSuccess?: () => void;
onBuildUpdate?: (data: VertexBuildTypeAPI, status: BuildStatus,buildId:string) => void; // Replace any with the actual type if it's not any
onBuildUpdate?: (
data: VertexBuildTypeAPI,
status: BuildStatus,
buildId: string
) => void; // Replace any with the actual type if it's not any
onBuildComplete?: (allNodesValid: boolean) => void;
onBuildError?: (title, list, idList: string[]) => void;
onBuildStart?: (idList: string[]) => void;
@ -34,8 +39,54 @@ function getInactiveVertexData(vertexId: string): VertexBuildTypeAPI {
return inactiveVertexData;
}
export async function updateVerticesOrder(flowId: string, nodeId: string | null): Promise<{ verticesLayers: string[][], verticesIds: string[], verticesOrder: string[][], runId: string }> {
return new Promise(async (resolve, reject) => {
const setErrorData = useAlertStore.getState().setErrorData;
let orderResponse;
try {
orderResponse = await getVerticesOrder(flowId, nodeId);
} catch (error: any) {
console.log(error);
setErrorData({
title: "Oops! Looks like you missed something",
list: [error.response?.data?.detail ?? "Unknown Error"],
});
useFlowStore.getState().setIsBuilding(false);
throw new Error("Invalid nodes");
}
let verticesOrder: Array<Array<string>> = orderResponse.data.ids;
const runId = orderResponse.data.run_id;
let verticesLayers: Array<Array<string>> = [];
if (nodeId) {
for (let i = 0; i < verticesOrder.length; i += 1) {
const innerArray = verticesOrder[i];
const idIndex = innerArray.indexOf(nodeId);
if (idIndex !== -1) {
// If there's a nodeId, we want to run just that component and not the entire layer
// because a layer contains dependencies for the next layer
// and we are stopping at the layer that contains the nodeId
verticesLayers.push([innerArray[idIndex]]);
break; // Stop searching after finding the first occurrence
}
// If the targetId is not found, include the entire inner array
verticesLayers.push(innerArray);
}
} else {
verticesLayers = verticesOrder;
}
const verticesIds = verticesLayers.flat();
useFlowStore
.getState()
.updateVerticesBuild({ verticesLayers, verticesIds, verticesOrder, runId });
resolve({ verticesLayers, verticesIds, verticesOrder, runId });
});
}
export async function buildVertices({
flowId,
input_value,
nodeId = null,
onGetOrderSuccess,
onBuildUpdate,
@ -44,24 +95,18 @@ export async function buildVertices({
onBuildStart,
validateNodes,
}: BuildVerticesParams) {
const setErrorData = useAlertStore.getState().setErrorData;
let orderResponse;
try {
orderResponse = await getVerticesOrder(flowId, nodeId);
} catch (error:any) {
console.log(error);
setErrorData({
title: "Oops! Looks like you missed something",
list: [error.response?.data?.detail ?? "Unknown Error"],
});
useFlowStore.getState().setIsBuilding(false);
throw new Error("Invalid nodes");
let verticesBuild = useFlowStore.getState().verticesBuild;
if (!verticesBuild || nodeId) {
verticesBuild = await updateVerticesOrder(flowId, nodeId);
}
if (onGetOrderSuccess) onGetOrderSuccess();
let verticesOrder: Array<Array<string>> = orderResponse.data.ids;
const runId = orderResponse.data.run_id;
let vertices_layers: Array<Array<string>> = [];
const verticesIds = verticesBuild?.verticesIds!;
const verticesLayers = verticesBuild?.verticesLayers!;
const verticesOrder = verticesBuild?.verticesOrder!;
const runId = verticesBuild?.runId!;
let stop = false;
if (onGetOrderSuccess) onGetOrderSuccess();
if (validateNodes) {
try {
validateNodes(verticesOrder.flatMap((id) => id));
@ -69,48 +114,29 @@ export async function buildVertices({
return;
}
}
if (nodeId) {
for (let i = 0; i < verticesOrder.length; i += 1) {
const innerArray = verticesOrder[i];
const idIndex = innerArray.indexOf(nodeId);
if (idIndex !== -1) {
// If there's a nodeId, we want to run just that component and not the entire layer
// because a layer contains dependencies for the next layer
// and we are stopping at the layer that contains the nodeId
vertices_layers.push([innerArray[idIndex]]);
break; // Stop searching after finding the first occurrence
}
// If the targetId is not found, include the entire inner array
vertices_layers.push(innerArray);
}
} else {
vertices_layers = verticesOrder;
}
const verticesIds = vertices_layers.flat();
useFlowStore.getState().updateBuildStatus(verticesIds, BuildStatus.TO_BUILD);
useFlowStore.getState().updateVerticesBuild(verticesIds);
useFlowStore.getState().setIsBuilding(true);
// Set each vertex state to building
const buildResults: Array<boolean> = [];
for (const layer of vertices_layers) {
for (const layer of verticesLayers) {
if (onBuildStart) onBuildStart(layer);
for (const id of layer) {
// Check if id is in the list of inactive nodes
if (
!useFlowStore.getState().verticesBuild.includes(id) &&
onBuildUpdate
) {
if (!verticesIds.includes(id) && onBuildUpdate) {
// If it is, skip building and set the state to inactive
onBuildUpdate(getInactiveVertexData(id), BuildStatus.INACTIVE,runId);
onBuildUpdate(getInactiveVertexData(id), BuildStatus.INACTIVE, runId);
buildResults.push(false);
continue;
}
await buildVertex({
flowId,
id,
onBuildUpdate:(data: VertexBuildTypeAPI, status: BuildStatus) => {if(onBuildUpdate) onBuildUpdate(data, status,runId)},
input_value,
onBuildUpdate: (data: VertexBuildTypeAPI, status: BuildStatus) => {
if (onBuildUpdate) onBuildUpdate(data, status, runId);
},
onBuildError,
verticesIds,
buildResults,
@ -137,6 +163,7 @@ export async function buildVertices({
async function buildVertex({
flowId,
id,
input_value,
onBuildUpdate,
onBuildError,
verticesIds,
@ -145,6 +172,7 @@ async function buildVertex({
}: {
flowId: string;
id: string;
input_value: string;
onBuildUpdate?: (data: any, status: BuildStatus) => void;
onBuildError?: (title, list, idList: string[]) => void;
verticesIds: string[];
@ -152,7 +180,7 @@ async function buildVertex({
stopBuild: () => void;
}) {
try {
const buildRes = await postBuildVertex(flowId, id);
const buildRes = await postBuildVertex(flowId, id, input_value);
const buildData: VertexBuildTypeAPI = buildRes.data;
if (onBuildUpdate) {
if (!buildData.valid) {

View file

@ -219,7 +219,7 @@ export const nodeColors: { [char: string]: string } = {
wrappers: "#E6277A",
utilities: "#31A3CC",
output_parsers: "#E6A627",
str: "#049524",
str: "#31a3cc",
retrievers: "#e6b25a",
unknown: "#9CA3AF",
custom_components: "#ab11ab",
@ -258,6 +258,7 @@ export const nodeIconsLucide: iconsType = {
Chroma: ChromaIcon,
AirbyteJSONLoader: AirbyteIcon,
AmazonBedrockEmbeddings: AWSIcon,
Amazon: AWSIcon,
Anthropic: AnthropicIcon,
ChatAnthropic: AnthropicIcon,
BingSearchAPIWrapper: BingIcon,
@ -270,13 +271,17 @@ export const nodeIconsLucide: iconsType = {
GoogleSearchAPIWrapper: GoogleIcon,
GoogleSearchResults: GoogleIcon,
GoogleSearchRun: GoogleIcon,
Google: GoogleIcon,
HNLoader: HackerNewsIcon,
HuggingFaceHub: HuggingFaceIcon,
HuggingFace: HuggingFaceIcon,
HuggingFaceEmbeddings: HuggingFaceIcon,
IFixitLoader: IFixIcon,
Meta: MetaIcon,
Midjorney: MidjourneyIcon,
MongoDBAtlasVectorSearch: MongoDBIcon,
MongoDB:MongoDBIcon,
MongoDBChatMessageHistory: MongoDBIcon,
NotionDirectoryLoader: NotionIcon,
ChatOpenAI: OpenAiIcon,
AzureChatOpenAI: OpenAiIcon,
@ -289,6 +294,7 @@ export const nodeIconsLucide: iconsType = {
Searx: SearxIcon,
SlackDirectoryLoader: SvgSlackIcon,
SupabaseVectorStore: SupabaseIcon,
Supabase: SupabaseIcon,
VertexAI: VertexAIIcon,
ChatVertexAI: VertexAIIcon,
VertexAIEmbeddings: VertexAIIcon,