diff --git a/src/backend/base/langflow/components/crewai/crewai.py b/src/backend/base/langflow/components/crewai/crewai.py
index 56a9d7a13442..a89762384a7e 100644
--- a/src/backend/base/langflow/components/crewai/crewai.py
+++ b/src/backend/base/langflow/components/crewai/crewai.py
@@ -21,6 +21,7 @@ class CrewAIAgentComponent(Component):
documentation: str = "https://docs.crewai.com/how-to/LLM-Connections/"
icon = "CrewAI"
legacy = True
+ replacement = "agents.Agent"
inputs = [
MultilineInput(name="role", display_name="Role", info="The role of the agent."),
diff --git a/src/backend/base/langflow/components/crewai/hierarchical_crew.py b/src/backend/base/langflow/components/crewai/hierarchical_crew.py
index 22c5cee48676..0935b006278c 100644
--- a/src/backend/base/langflow/components/crewai/hierarchical_crew.py
+++ b/src/backend/base/langflow/components/crewai/hierarchical_crew.py
@@ -10,6 +10,7 @@ class HierarchicalCrewComponent(BaseCrewComponent):
documentation: str = "https://docs.crewai.com/how-to/Hierarchical/"
icon = "CrewAI"
legacy = True
+ replacement = "agents.Agent"
inputs = [
*BaseCrewComponent._base_inputs,
diff --git a/src/backend/base/langflow/components/crewai/hierarchical_task.py b/src/backend/base/langflow/components/crewai/hierarchical_task.py
index 1aae41c8e662..afab3e5c1a8b 100644
--- a/src/backend/base/langflow/components/crewai/hierarchical_task.py
+++ b/src/backend/base/langflow/components/crewai/hierarchical_task.py
@@ -8,6 +8,7 @@ class HierarchicalTaskComponent(Component):
description: str = "Each task must have a description, an expected output and an agent responsible for execution."
icon = "CrewAI"
legacy = True
+ replacement = "agents.Agent"
inputs = [
MultilineInput(
name="task_description",
diff --git a/src/backend/base/langflow/components/crewai/sequential_crew.py b/src/backend/base/langflow/components/crewai/sequential_crew.py
index b054099cbf49..0222a0ee1e6d 100644
--- a/src/backend/base/langflow/components/crewai/sequential_crew.py
+++ b/src/backend/base/langflow/components/crewai/sequential_crew.py
@@ -9,6 +9,7 @@ class SequentialCrewComponent(BaseCrewComponent):
documentation: str = "https://docs.crewai.com/how-to/Sequential/"
icon = "CrewAI"
legacy = True
+ replacement = "agents.Agent"
inputs = [
*BaseCrewComponent._base_inputs,
diff --git a/src/backend/base/langflow/components/crewai/sequential_task.py b/src/backend/base/langflow/components/crewai/sequential_task.py
index 3c4a69159c24..d99175f8044e 100644
--- a/src/backend/base/langflow/components/crewai/sequential_task.py
+++ b/src/backend/base/langflow/components/crewai/sequential_task.py
@@ -8,6 +8,7 @@ class SequentialTaskComponent(Component):
description: str = "Each task must have a description, an expected output and an agent responsible for execution."
icon = "CrewAI"
legacy = True
+ replacement = "agents.Agent"
inputs = [
MultilineInput(
name="task_description",
diff --git a/src/backend/base/langflow/components/crewai/sequential_task_agent.py b/src/backend/base/langflow/components/crewai/sequential_task_agent.py
index 9b2caef762a8..8ec505298c69 100644
--- a/src/backend/base/langflow/components/crewai/sequential_task_agent.py
+++ b/src/backend/base/langflow/components/crewai/sequential_task_agent.py
@@ -9,6 +9,7 @@ class SequentialTaskAgentComponent(Component):
documentation = "https://docs.crewai.com/how-to/LLM-Connections/"
icon = "CrewAI"
legacy = True
+ replacement = "agents.Agent"
inputs = [
# Agent inputs
diff --git a/src/backend/base/langflow/components/data/csv_to_data.py b/src/backend/base/langflow/components/data/csv_to_data.py
index 4b95563fc4de..dd50e0b3e915 100644
--- a/src/backend/base/langflow/components/data/csv_to_data.py
+++ b/src/backend/base/langflow/components/data/csv_to_data.py
@@ -13,6 +13,7 @@ class CSVToDataComponent(Component):
icon = "file-spreadsheet"
name = "CSVtoData"
legacy = True
+ replacement = ["data.File"]
inputs = [
FileInput(
diff --git a/src/backend/base/langflow/components/data/json_to_data.py b/src/backend/base/langflow/components/data/json_to_data.py
index a41fb3a21b7a..684c8d2b0a74 100644
--- a/src/backend/base/langflow/components/data/json_to_data.py
+++ b/src/backend/base/langflow/components/data/json_to_data.py
@@ -16,6 +16,7 @@ class JSONToDataComponent(Component):
icon = "braces"
name = "JSONtoData"
legacy = True
+ replacement = ["data.File"]
inputs = [
FileInput(
diff --git a/src/backend/base/langflow/components/datastax/astra_vectorize.py b/src/backend/base/langflow/components/datastax/astra_vectorize.py
index 1e867832b230..12c387951dd7 100644
--- a/src/backend/base/langflow/components/datastax/astra_vectorize.py
+++ b/src/backend/base/langflow/components/datastax/astra_vectorize.py
@@ -6,15 +6,13 @@
class AstraVectorizeComponent(Component):
- display_name: str = "Astra Vectorize [DEPRECATED]"
- description: str = (
- "Configuration options for Astra Vectorize server-side embeddings. "
- "This component is deprecated. Please use the Astra DB Component directly."
- )
+ display_name: str = "Astra Vectorize"
+ description: str = "Configuration options for Astra Vectorize server-side embeddings. "
documentation: str = "https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html"
legacy = True
icon = "AstraDB"
name = "AstraVectorize"
+ replacement = ["datastax.AstraDB"]
VECTORIZE_PROVIDERS_MAPPING = {
"Azure OpenAI": ["azureOpenAI", ["text-embedding-3-small", "text-embedding-3-large", "text-embedding-ada-002"]],
diff --git a/src/backend/base/langflow/components/deactivated/mcp_sse.py b/src/backend/base/langflow/components/deactivated/mcp_sse.py
deleted file mode 100644
index b68910206fc2..000000000000
--- a/src/backend/base/langflow/components/deactivated/mcp_sse.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# from langflow.field_typing import Data
-
-from langchain_core.tools import StructuredTool
-from mcp import types
-
-from langflow.base.mcp.util import (
- MCPSseClient,
- create_input_schema_from_json_schema,
- create_tool_coroutine,
- create_tool_func,
-)
-from langflow.custom.custom_component.component import Component
-from langflow.field_typing import Tool
-from langflow.io import MessageTextInput, Output
-
-
-class MCPSse(Component):
- client = MCPSseClient()
- tools = types.ListToolsResult
- tool_names = [str]
- display_name = "MCP Tools (SSE) [DEPRECATED]"
- description = "Connects to an MCP server over SSE and exposes it's tools as langflow tools to be used by an Agent."
- documentation: str = "https://docs.langflow.org/components-custom-components"
- icon = "code"
- name = "MCPSse"
- legacy = True
-
- inputs = [
- MessageTextInput(
- name="url",
- display_name="mcp sse url",
- info="sse url",
- value="http://localhost:7860/api/v1/mcp/sse",
- tool_mode=True,
- ),
- ]
-
- outputs = [
- Output(display_name="Tools", name="tools", method="build_output"),
- ]
-
- async def build_output(self) -> list[Tool]:
- if self.client.session is None:
- self.tools = await self.client.connect_to_server(self.url, {})
-
- tool_list = []
-
- for tool in self.tools:
- args_schema = create_input_schema_from_json_schema(tool.inputSchema)
- tool_list.append(
- StructuredTool(
- name=tool.name, # maybe format this
- description=tool.description,
- args_schema=args_schema,
- func=create_tool_func(tool.name, args_schema, self.client.session),
- coroutine=create_tool_coroutine(tool.name, args_schema, self.client.session),
- )
- )
-
- self.tool_names = [tool.name for tool in self.tools]
- return tool_list
diff --git a/src/backend/base/langflow/components/deactivated/mcp_stdio.py b/src/backend/base/langflow/components/deactivated/mcp_stdio.py
deleted file mode 100644
index 059c4dec64d2..000000000000
--- a/src/backend/base/langflow/components/deactivated/mcp_stdio.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# from langflow.field_typing import Data
-
-from langchain_core.tools import StructuredTool
-from mcp import types
-
-from langflow.base.mcp.util import (
- MCPStdioClient,
- create_input_schema_from_json_schema,
- create_tool_coroutine,
- create_tool_func,
-)
-from langflow.custom.custom_component.component import Component
-from langflow.field_typing import Tool
-from langflow.io import MessageTextInput, Output
-
-
-class MCPStdio(Component):
- client = MCPStdioClient()
- tools = types.ListToolsResult
- tool_names = [str]
- display_name = "MCP Tools (stdio) [DEPRECATED]"
- description = (
- "Connects to an MCP server over stdio and exposes it's tools as langflow tools to be used by an Agent."
- )
- documentation: str = "https://docs.langflow.org/components-custom-components"
- icon = "code"
- name = "MCPStdio"
- legacy = True
-
- inputs = [
- MessageTextInput(
- name="command",
- display_name="mcp command",
- info="mcp command",
- value="uvx mcp-sse-shim@latest",
- tool_mode=True,
- ),
- ]
-
- outputs = [
- Output(display_name="Tools", name="tools", method="build_output"),
- ]
-
- async def build_output(self) -> list[Tool]:
- if self.client.session is None:
- self.tools = await self.client.connect_to_server(self.command)
-
- tool_list = []
-
- for tool in self.tools:
- args_schema = create_input_schema_from_json_schema(tool.inputSchema)
- tool_list.append(
- StructuredTool(
- name=tool.name,
- description=tool.description,
- args_schema=args_schema,
- func=create_tool_func(tool.name, args_schema, self.client.session),
- coroutine=create_tool_coroutine(tool.name, args_schema, self.client.session),
- )
- )
- self.tool_names = [tool.name for tool in self.tools]
- return tool_list
diff --git a/src/backend/base/langflow/components/embeddings/similarity.py b/src/backend/base/langflow/components/embeddings/similarity.py
index dc132214bc9d..f8b100c0a5dd 100644
--- a/src/backend/base/langflow/components/embeddings/similarity.py
+++ b/src/backend/base/langflow/components/embeddings/similarity.py
@@ -12,6 +12,7 @@ class EmbeddingSimilarityComponent(Component):
description: str = "Compute selected form of similarity between two embedding vectors."
icon = "equal"
legacy: bool = True
+ replacement = ["datastax.AstraDB"]
inputs = [
DataInput(
diff --git a/src/backend/base/langflow/components/embeddings/text_embedder.py b/src/backend/base/langflow/components/embeddings/text_embedder.py
index 7feba70af681..edf3f7d72f58 100644
--- a/src/backend/base/langflow/components/embeddings/text_embedder.py
+++ b/src/backend/base/langflow/components/embeddings/text_embedder.py
@@ -15,6 +15,7 @@ class TextEmbedderComponent(Component):
description: str = "Generate embeddings for a given message using the specified embedding model."
icon = "binary"
legacy: bool = True
+ replacement = ["models.EmbeddingModel"]
inputs = [
HandleInput(
name="embedding_model",
diff --git a/src/backend/base/langflow/components/google/gmail.py b/src/backend/base/langflow/components/google/gmail.py
index a5e486b6949d..b09faffa655c 100644
--- a/src/backend/base/langflow/components/google/gmail.py
+++ b/src/backend/base/langflow/components/google/gmail.py
@@ -25,6 +25,7 @@ class GmailLoaderComponent(Component):
description = "Loads emails from Gmail using provided credentials."
icon = "Google"
legacy: bool = True
+ replacement = ["composio.ComposioGmailAPIComponent"]
inputs = [
SecretStrInput(
diff --git a/src/backend/base/langflow/components/helpers/output_parser.py b/src/backend/base/langflow/components/helpers/output_parser.py
index 7fa3f5495f85..4c0a41f579db 100644
--- a/src/backend/base/langflow/components/helpers/output_parser.py
+++ b/src/backend/base/langflow/components/helpers/output_parser.py
@@ -12,6 +12,7 @@ class OutputParserComponent(Component):
icon = "type"
name = "OutputParser"
legacy = True
+ replacement = ["processing.StructuredOutput", "processing.ParserComponent"]
inputs = [
DropdownInput(
diff --git a/src/backend/base/langflow/components/helpers/store_message.py b/src/backend/base/langflow/components/helpers/store_message.py
index c1db3da3d736..bdf84be7346a 100644
--- a/src/backend/base/langflow/components/helpers/store_message.py
+++ b/src/backend/base/langflow/components/helpers/store_message.py
@@ -15,6 +15,7 @@ class MessageStoreComponent(Component):
icon = "message-square-text"
name = "StoreMessage"
legacy = True
+ replacement = ["helpers.Memory"]
inputs = [
MessageTextInput(
diff --git a/src/backend/base/langflow/components/logic/data_conditional_router.py b/src/backend/base/langflow/components/logic/data_conditional_router.py
index d9547c0d6e79..c3da0c4f0ab6 100644
--- a/src/backend/base/langflow/components/logic/data_conditional_router.py
+++ b/src/backend/base/langflow/components/logic/data_conditional_router.py
@@ -12,6 +12,7 @@ class DataConditionalRouterComponent(Component):
icon = "split"
name = "DataConditionalRouter"
legacy = True
+ replacement = ["logic.ConditionalRouter"]
inputs = [
DataInput(
diff --git a/src/backend/base/langflow/components/logic/flow_tool.py b/src/backend/base/langflow/components/logic/flow_tool.py
index f522346a3878..210f1912ac1c 100644
--- a/src/backend/base/langflow/components/logic/flow_tool.py
+++ b/src/backend/base/langflow/components/logic/flow_tool.py
@@ -14,12 +14,13 @@
class FlowToolComponent(LCToolComponent):
- display_name = "Flow as Tool [Deprecated]"
+ display_name = "Flow as Tool"
description = "Construct a Tool from a function that runs the loaded Flow."
field_order = ["flow_name", "name", "description", "return_direct"]
trace_type = "tool"
name = "FlowTool"
legacy: bool = True
+ replacement = ["logic.RunFlow"]
icon = "hammer"
async def get_flow_names(self) -> list[str]:
diff --git a/src/backend/base/langflow/components/logic/pass_message.py b/src/backend/base/langflow/components/logic/pass_message.py
index a8b066519ee6..b16529822ff0 100644
--- a/src/backend/base/langflow/components/logic/pass_message.py
+++ b/src/backend/base/langflow/components/logic/pass_message.py
@@ -10,6 +10,7 @@ class PassMessageComponent(Component):
name = "Pass"
icon = "arrow-right"
legacy: bool = True
+ replacement = ["logic.ConditionalRouter"]
inputs = [
MessageInput(
diff --git a/src/backend/base/langflow/components/logic/sub_flow.py b/src/backend/base/langflow/components/logic/sub_flow.py
index 19f427a3f2f9..e0aad7f564e9 100644
--- a/src/backend/base/langflow/components/logic/sub_flow.py
+++ b/src/backend/base/langflow/components/logic/sub_flow.py
@@ -12,10 +12,11 @@
class SubFlowComponent(Component):
- display_name = "Sub Flow [Deprecated]"
+ display_name = "Sub Flow"
description = "Generates a Component from a Flow, with all of its inputs, and "
name = "SubFlow"
legacy: bool = True
+ replacement = ["logic.RunFlow"]
icon = "Workflow"
async def get_flow_names(self) -> list[str]:
diff --git a/src/backend/base/langflow/components/processing/alter_metadata.py b/src/backend/base/langflow/components/processing/alter_metadata.py
index 5f158292a605..5169e8d7ee5e 100644
--- a/src/backend/base/langflow/components/processing/alter_metadata.py
+++ b/src/backend/base/langflow/components/processing/alter_metadata.py
@@ -11,6 +11,7 @@ class AlterMetadataComponent(Component):
icon = "merge"
name = "AlterMetadata"
legacy = True
+ replacement = ["processing.DataOperations"]
inputs = [
HandleInput(
diff --git a/src/backend/base/langflow/components/processing/combine_text.py b/src/backend/base/langflow/components/processing/combine_text.py
index 9ac1fc5240e6..a25ba7d05a09 100644
--- a/src/backend/base/langflow/components/processing/combine_text.py
+++ b/src/backend/base/langflow/components/processing/combine_text.py
@@ -9,6 +9,7 @@ class CombineTextComponent(Component):
icon = "merge"
name = "CombineText"
legacy: bool = True
+ replacement = ["processing.DataOperations"]
inputs = [
MessageTextInput(
diff --git a/src/backend/base/langflow/components/processing/create_data.py b/src/backend/base/langflow/components/processing/create_data.py
index 639f41278008..ef72e6560eaa 100644
--- a/src/backend/base/langflow/components/processing/create_data.py
+++ b/src/backend/base/langflow/components/processing/create_data.py
@@ -14,6 +14,7 @@ class CreateDataComponent(Component):
name: str = "CreateData"
MAX_FIELDS = 15 # Define a constant for maximum number of fields
legacy = True
+ replacement = ["processing.DataOperations"]
icon = "ListFilter"
inputs = [
diff --git a/src/backend/base/langflow/components/processing/data_to_dataframe.py b/src/backend/base/langflow/components/processing/data_to_dataframe.py
index 1620f7b82f0d..69ba2224a1fe 100644
--- a/src/backend/base/langflow/components/processing/data_to_dataframe.py
+++ b/src/backend/base/langflow/components/processing/data_to_dataframe.py
@@ -14,6 +14,7 @@ class DataToDataFrameComponent(Component):
icon = "table"
name = "DataToDataFrame"
legacy = True
+ replacement = ["processing.DataOperations", "processing.TypeConverterComponent"]
inputs = [
DataInput(
diff --git a/src/backend/base/langflow/components/processing/extract_key.py b/src/backend/base/langflow/components/processing/extract_key.py
index b9054cd6497a..65f4797be6ba 100644
--- a/src/backend/base/langflow/components/processing/extract_key.py
+++ b/src/backend/base/langflow/components/processing/extract_key.py
@@ -12,6 +12,7 @@ class ExtractDataKeyComponent(Component):
icon = "key"
name = "ExtractaKey"
legacy = True
+ replacement = ["processing.DataOperations"]
inputs = [
DataInput(
diff --git a/src/backend/base/langflow/components/processing/filter_data.py b/src/backend/base/langflow/components/processing/filter_data.py
index 99a6213d6171..28e1dcc260bc 100644
--- a/src/backend/base/langflow/components/processing/filter_data.py
+++ b/src/backend/base/langflow/components/processing/filter_data.py
@@ -10,6 +10,7 @@ class FilterDataComponent(Component):
beta = True
name = "FilterData"
legacy = True
+ replacement = ["processing.DataOperations"]
inputs = [
DataInput(
diff --git a/src/backend/base/langflow/components/processing/filter_data_values.py b/src/backend/base/langflow/components/processing/filter_data_values.py
index c2aab6b4eb93..a1e43fa4c084 100644
--- a/src/backend/base/langflow/components/processing/filter_data_values.py
+++ b/src/backend/base/langflow/components/processing/filter_data_values.py
@@ -15,6 +15,7 @@ class DataFilterComponent(Component):
beta = True
name = "FilterDataValues"
legacy = True
+ replacement = ["processing.DataOperations"]
inputs = [
DataInput(name="input_data", display_name="Input Data", info="The list of data items to filter.", is_list=True),
diff --git a/src/backend/base/langflow/components/processing/json_cleaner.py b/src/backend/base/langflow/components/processing/json_cleaner.py
index d8b8290cf0d2..b8f3a675808b 100644
--- a/src/backend/base/langflow/components/processing/json_cleaner.py
+++ b/src/backend/base/langflow/components/processing/json_cleaner.py
@@ -15,6 +15,7 @@ class JSONCleaner(Component):
"so that they are fully compliant with the JSON spec."
)
legacy = True
+ replacement = ["processing.ParserComponent"]
inputs = [
MessageTextInput(
name="json_str", display_name="JSON String", info="The JSON string to be cleaned.", required=True
diff --git a/src/backend/base/langflow/components/processing/merge_data.py b/src/backend/base/langflow/components/processing/merge_data.py
index 81d53c7c4c4e..d578dce495a7 100644
--- a/src/backend/base/langflow/components/processing/merge_data.py
+++ b/src/backend/base/langflow/components/processing/merge_data.py
@@ -20,6 +20,7 @@ class MergeDataComponent(Component):
icon = "merge"
MIN_INPUTS_REQUIRED = 2
legacy = True
+ replacement = ["processing.DataOperations"]
inputs = [
DataInput(name="data_inputs", display_name="Data Inputs", info="Data to combine", is_list=True, required=True),
diff --git a/src/backend/base/langflow/components/processing/message_to_data.py b/src/backend/base/langflow/components/processing/message_to_data.py
index 61cc0e26a7fe..455d73b66655 100644
--- a/src/backend/base/langflow/components/processing/message_to_data.py
+++ b/src/backend/base/langflow/components/processing/message_to_data.py
@@ -12,6 +12,7 @@ class MessageToDataComponent(Component):
beta = True
name = "MessagetoData"
legacy = True
+ replacement = ["processing.TypeConverterComponent"]
inputs = [
MessageInput(
diff --git a/src/backend/base/langflow/components/processing/parse_data.py b/src/backend/base/langflow/components/processing/parse_data.py
index 2608a09b6cdb..e4e3814d13ec 100644
--- a/src/backend/base/langflow/components/processing/parse_data.py
+++ b/src/backend/base/langflow/components/processing/parse_data.py
@@ -11,6 +11,7 @@ class ParseDataComponent(Component):
icon = "message-square"
name = "ParseData"
legacy = True
+ replacement = ["processing.DataOperations", "processing.TypeConverterComponent"]
metadata = {
"legacy_name": "Parse Data",
}
diff --git a/src/backend/base/langflow/components/processing/parse_dataframe.py b/src/backend/base/langflow/components/processing/parse_dataframe.py
index ce1d8f076f87..b8c9b7a556db 100644
--- a/src/backend/base/langflow/components/processing/parse_dataframe.py
+++ b/src/backend/base/langflow/components/processing/parse_dataframe.py
@@ -12,6 +12,7 @@ class ParseDataFrameComponent(Component):
icon = "braces"
name = "ParseDataFrame"
legacy = True
+ replacement = ["processing.DataFrameOperations", "processing.TypeConverterComponent"]
inputs = [
DataFrameInput(name="df", display_name="DataFrame", info="The DataFrame to convert to text rows."),
diff --git a/src/backend/base/langflow/components/processing/parse_json_data.py b/src/backend/base/langflow/components/processing/parse_json_data.py
index 7dee10d1d090..9bea3ab3db28 100644
--- a/src/backend/base/langflow/components/processing/parse_json_data.py
+++ b/src/backend/base/langflow/components/processing/parse_json_data.py
@@ -18,6 +18,7 @@ class ParseJSONDataComponent(Component):
icon = "braces"
name = "ParseJSONData"
legacy: bool = True
+ replacement = ["processing.ParserComponent"]
inputs = [
HandleInput(
diff --git a/src/backend/base/langflow/components/processing/regex.py b/src/backend/base/langflow/components/processing/regex.py
index 49c4ccca3c05..1f0da6547d21 100644
--- a/src/backend/base/langflow/components/processing/regex.py
+++ b/src/backend/base/langflow/components/processing/regex.py
@@ -11,6 +11,7 @@ class RegexExtractorComponent(Component):
description = "Extract patterns from text using regular expressions."
icon = "regex"
legacy = True
+ replacement = ["processing.ParserComponent"]
inputs = [
MessageTextInput(
diff --git a/src/backend/base/langflow/components/processing/select_data.py b/src/backend/base/langflow/components/processing/select_data.py
index 82b839b90f44..a5de81a79885 100644
--- a/src/backend/base/langflow/components/processing/select_data.py
+++ b/src/backend/base/langflow/components/processing/select_data.py
@@ -11,6 +11,7 @@ class SelectDataComponent(Component):
name: str = "SelectData"
icon = "prototypes"
legacy = True
+ replacement = ["processing.DataOperations"]
inputs = [
DataInput(
diff --git a/src/backend/base/langflow/components/processing/update_data.py b/src/backend/base/langflow/components/processing/update_data.py
index 38362cc9322f..4c3afdd420cc 100644
--- a/src/backend/base/langflow/components/processing/update_data.py
+++ b/src/backend/base/langflow/components/processing/update_data.py
@@ -21,6 +21,7 @@ class UpdateDataComponent(Component):
MAX_FIELDS = 15 # Define a constant for maximum number of fields
icon = "FolderSync"
legacy = True
+ replacement = ["processing.DataOperations"]
inputs = [
DataInput(
diff --git a/src/backend/base/langflow/components/tools/__init__.py b/src/backend/base/langflow/components/tools/__init__.py
index ec9068337085..cd4c1b21f2b2 100644
--- a/src/backend/base/langflow/components/tools/__init__.py
+++ b/src/backend/base/langflow/components/tools/__init__.py
@@ -9,8 +9,6 @@
if TYPE_CHECKING:
from .calculator import CalculatorToolComponent
- from .google_search_api import GoogleSearchAPIComponent
- from .google_serper_api import GoogleSerperAPIComponent
from .python_code_structured_tool import PythonCodeStructuredTool
from .python_repl import PythonREPLToolComponent
from .search_api import SearchAPIComponent
@@ -23,8 +21,6 @@
_dynamic_imports = {
"CalculatorToolComponent": "calculator",
- "GoogleSearchAPIComponent": "google_search_api",
- "GoogleSerperAPIComponent": "google_serper_api",
"PythonCodeStructuredTool": "python_code_structured_tool",
"PythonREPLToolComponent": "python_repl",
"SearchAPIComponent": "search_api",
@@ -38,8 +34,6 @@
__all__ = [
"CalculatorToolComponent",
- "GoogleSearchAPIComponent",
- "GoogleSerperAPIComponent",
"PythonCodeStructuredTool",
"PythonREPLToolComponent",
"SearXNGToolComponent",
diff --git a/src/backend/base/langflow/components/tools/calculator.py b/src/backend/base/langflow/components/tools/calculator.py
index 767f1c3bb4b7..7821c409479a 100644
--- a/src/backend/base/langflow/components/tools/calculator.py
+++ b/src/backend/base/langflow/components/tools/calculator.py
@@ -13,11 +13,12 @@
class CalculatorToolComponent(LCToolComponent):
- display_name = "Calculator [DEPRECATED]"
+ display_name = "Calculator"
description = "Perform basic arithmetic operations on a given expression."
icon = "calculator"
name = "CalculatorTool"
legacy = True
+ replacement = ["helpers.CalculatorComponent"]
inputs = [
MessageTextInput(
diff --git a/src/backend/base/langflow/components/tools/google_search_api.py b/src/backend/base/langflow/components/tools/google_search_api.py
deleted file mode 100644
index 267d3305a6d7..000000000000
--- a/src/backend/base/langflow/components/tools/google_search_api.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from langchain_core.tools import Tool
-
-from langflow.base.langchain_utilities.model import LCToolComponent
-from langflow.inputs.inputs import IntInput, MultilineInput, SecretStrInput
-from langflow.schema.data import Data
-
-
-class GoogleSearchAPIComponent(LCToolComponent):
- display_name = "Google Search API [DEPRECATED]"
- description = "Call Google Search API."
- name = "GoogleSearchAPI"
- icon = "Google"
- legacy = True
- inputs = [
- SecretStrInput(name="google_api_key", display_name="Google API Key", required=True),
- SecretStrInput(name="google_cse_id", display_name="Google CSE ID", required=True),
- MultilineInput(
- name="input_value",
- display_name="Input",
- ),
- IntInput(name="k", display_name="Number of results", value=4, required=True),
- ]
-
- def run_model(self) -> Data | list[Data]:
- wrapper = self._build_wrapper()
- results = wrapper.results(query=self.input_value, num_results=self.k)
- data = [Data(data=result, text=result["snippet"]) for result in results]
- self.status = data
- return data
-
- def build_tool(self) -> Tool:
- wrapper = self._build_wrapper()
- return Tool(
- name="google_search",
- description="Search Google for recent results.",
- func=wrapper.run,
- )
-
- def _build_wrapper(self):
- try:
- from langchain_google_community import GoogleSearchAPIWrapper
- except ImportError as e:
- msg = "Please install langchain-google-community to use GoogleSearchAPIWrapper."
- raise ImportError(msg) from e
- return GoogleSearchAPIWrapper(google_api_key=self.google_api_key, google_cse_id=self.google_cse_id, k=self.k)
diff --git a/src/backend/base/langflow/components/tools/google_serper_api.py b/src/backend/base/langflow/components/tools/google_serper_api.py
deleted file mode 100644
index e78d58cfb671..000000000000
--- a/src/backend/base/langflow/components/tools/google_serper_api.py
+++ /dev/null
@@ -1,115 +0,0 @@
-from typing import Any
-
-from langchain.tools import StructuredTool
-from langchain_community.utilities.google_serper import GoogleSerperAPIWrapper
-from pydantic import BaseModel, Field
-
-from langflow.base.langchain_utilities.model import LCToolComponent
-from langflow.field_typing import Tool
-from langflow.inputs.inputs import (
- DictInput,
- DropdownInput,
- IntInput,
- MultilineInput,
- SecretStrInput,
-)
-from langflow.schema.data import Data
-
-
-class QuerySchema(BaseModel):
- query: str = Field(..., description="The query to search for.")
- query_type: str = Field(
- "search",
- description="The type of search to perform (e.g., 'news' or 'search').",
- )
- k: int = Field(4, description="The number of results to return.")
- query_params: dict[str, Any] = Field({}, description="Additional query parameters to pass to the API.")
-
-
-class GoogleSerperAPIComponent(LCToolComponent):
- display_name = "Google Serper API [DEPRECATED]"
- description = "Call the Serper.dev Google Search API."
- name = "GoogleSerperAPI"
- icon = "Google"
- legacy = True
- inputs = [
- SecretStrInput(name="serper_api_key", display_name="Serper API Key", required=True),
- MultilineInput(
- name="query",
- display_name="Query",
- ),
- IntInput(name="k", display_name="Number of results", value=4, required=True),
- DropdownInput(
- name="query_type",
- display_name="Query Type",
- required=False,
- options=["news", "search"],
- value="search",
- ),
- DictInput(
- name="query_params",
- display_name="Query Params",
- required=False,
- value={
- "gl": "us",
- "hl": "en",
- },
- list=True,
- ),
- ]
-
- def run_model(self) -> Data | list[Data]:
- wrapper = self._build_wrapper(self.k, self.query_type, self.query_params)
- results = wrapper.results(query=self.query)
-
- # Adjust the extraction based on the `type`
- if self.query_type == "search":
- list_results = results.get("organic", [])
- elif self.query_type == "news":
- list_results = results.get("news", [])
- else:
- list_results = []
-
- data_list = []
- for result in list_results:
- result["text"] = result.pop("snippet", "")
- data_list.append(Data(data=result))
- self.status = data_list
- return data_list
-
- def build_tool(self) -> Tool:
- return StructuredTool.from_function(
- name="google_search",
- description="Search Google for recent results.",
- func=self._search,
- args_schema=self.QuerySchema,
- )
-
- def _build_wrapper(
- self,
- k: int = 5,
- query_type: str = "search",
- query_params: dict | None = None,
- ) -> GoogleSerperAPIWrapper:
- wrapper_args = {
- "serper_api_key": self.serper_api_key,
- "k": k,
- "type": query_type,
- }
-
- # Add query_params if provided
- if query_params:
- wrapper_args.update(query_params) # Merge with additional query params
-
- # Dynamically pass parameters to the wrapper
- return GoogleSerperAPIWrapper(**wrapper_args)
-
- def _search(
- self,
- query: str,
- k: int = 5,
- query_type: str = "search",
- query_params: dict | None = None,
- ) -> dict:
- wrapper = self._build_wrapper(k, query_type, query_params)
- return wrapper.results(query=query)
diff --git a/src/backend/base/langflow/components/tools/python_code_structured_tool.py b/src/backend/base/langflow/components/tools/python_code_structured_tool.py
index c5999f9f2146..e2baa44a6005 100644
--- a/src/backend/base/langflow/components/tools/python_code_structured_tool.py
+++ b/src/backend/base/langflow/components/tools/python_code_structured_tool.py
@@ -37,6 +37,7 @@ class PythonCodeStructuredTool(LCToolComponent):
icon = "Python"
field_order = ["name", "description", "tool_code", "return_direct", "tool_function"]
legacy: bool = True
+ replacement = ["processing.PythonREPLComponent"]
inputs = [
MultilineInput(
diff --git a/src/backend/base/langflow/components/tools/python_repl.py b/src/backend/base/langflow/components/tools/python_repl.py
index 46791fe141a3..cb740982acb3 100644
--- a/src/backend/base/langflow/components/tools/python_repl.py
+++ b/src/backend/base/langflow/components/tools/python_repl.py
@@ -13,11 +13,12 @@
class PythonREPLToolComponent(LCToolComponent):
- display_name = "Python REPL [DEPRECATED]"
+ display_name = "Python REPL"
description = "A tool for running Python code in a REPL environment."
name = "PythonREPLTool"
icon = "Python"
legacy = True
+ replacement = ["processing.PythonREPLComponent"]
inputs = [
StrInput(
diff --git a/src/backend/base/langflow/components/tools/search_api.py b/src/backend/base/langflow/components/tools/search_api.py
index 46fe3e9253d1..a013971456c8 100644
--- a/src/backend/base/langflow/components/tools/search_api.py
+++ b/src/backend/base/langflow/components/tools/search_api.py
@@ -11,12 +11,13 @@
class SearchAPIComponent(LCToolComponent):
- display_name: str = "Search API [DEPRECATED]"
+ display_name: str = "Search API"
description: str = "Call the searchapi.io API with result limiting"
name = "SearchAPI"
documentation: str = "https://www.searchapi.io/docs/google"
icon = "SearchAPI"
legacy = True
+ replacement = ["searchapi.SearchComponent"]
inputs = [
MessageTextInput(name="engine", display_name="Engine", value="google"),
diff --git a/src/backend/base/langflow/components/tools/serp_api.py b/src/backend/base/langflow/components/tools/serp_api.py
index 19fb42853ecb..2b7c0405e5a6 100644
--- a/src/backend/base/langflow/components/tools/serp_api.py
+++ b/src/backend/base/langflow/components/tools/serp_api.py
@@ -30,11 +30,12 @@ class SerpAPISchema(BaseModel):
class SerpAPIComponent(LCToolComponent):
- display_name = "Serp Search API [DEPRECATED]"
+ display_name = "Serp Search API"
description = "Call Serp Search API with result limiting"
name = "SerpAPI"
icon = "SerpSearch"
legacy = True
+ replacement = ["serpapi.Serp"]
inputs = [
SecretStrInput(name="serpapi_api_key", display_name="SerpAPI API Key", required=True),
diff --git a/src/backend/base/langflow/components/tools/tavily_search_tool.py b/src/backend/base/langflow/components/tools/tavily_search_tool.py
index c01e463bc1a1..602a67d074c9 100644
--- a/src/backend/base/langflow/components/tools/tavily_search_tool.py
+++ b/src/backend/base/langflow/components/tools/tavily_search_tool.py
@@ -81,6 +81,7 @@ class TavilySearchToolComponent(LCToolComponent):
name = "TavilyAISearch"
documentation = "https://docs.tavily.com/"
legacy = True
+ replacement = ["tavily.TavilySearchComponent"]
inputs = [
SecretStrInput(
diff --git a/src/backend/base/langflow/components/tools/wikidata_api.py b/src/backend/base/langflow/components/tools/wikidata_api.py
index 755784e44f4b..7f51a2e3a6ad 100644
--- a/src/backend/base/langflow/components/tools/wikidata_api.py
+++ b/src/backend/base/langflow/components/tools/wikidata_api.py
@@ -53,11 +53,12 @@ def run(self, query: str) -> list[dict[str, Any]]:
class WikidataAPIComponent(LCToolComponent):
- display_name = "Wikidata API [Deprecated]"
+ display_name = "Wikidata API"
description = "Performs a search using the Wikidata API."
name = "WikidataAPI"
icon = "Wikipedia"
legacy = True
+ replacement = ["wikipedia.WikidataComponent"]
inputs = [
MultilineInput(
diff --git a/src/backend/base/langflow/components/tools/wikipedia_api.py b/src/backend/base/langflow/components/tools/wikipedia_api.py
index 0608bbcd1c47..d80abe2b2f58 100644
--- a/src/backend/base/langflow/components/tools/wikipedia_api.py
+++ b/src/backend/base/langflow/components/tools/wikipedia_api.py
@@ -10,11 +10,12 @@
class WikipediaAPIComponent(LCToolComponent):
- display_name = "Wikipedia API [Deprecated]"
+ display_name = "Wikipedia API"
description = "Call Wikipedia API."
name = "WikipediaAPI"
icon = "Wikipedia"
legacy = True
+ replacement = ["wikipedia.WikipediaComponent"]
inputs = [
MultilineInput(
diff --git a/src/backend/base/langflow/components/tools/yahoo_finance.py b/src/backend/base/langflow/components/tools/yahoo_finance.py
index fd1615b44879..ba3a9eee12fd 100644
--- a/src/backend/base/langflow/components/tools/yahoo_finance.py
+++ b/src/backend/base/langflow/components/tools/yahoo_finance.py
@@ -49,12 +49,13 @@ class YahooFinanceSchema(BaseModel):
class YfinanceToolComponent(LCToolComponent):
- display_name = "Yahoo! Finance [DEPRECATED]"
+ display_name = "Yahoo! Finance"
description = """Uses [yfinance](https://pypi.org/project/yfinance/) (unofficial package) \
to access financial data and market information from Yahoo! Finance."""
icon = "trending-up"
name = "YahooFinanceTool"
legacy = True
+ replacement = ["yahoosearch.YfinanceComponent"]
inputs = [
MessageTextInput(
diff --git a/src/backend/base/langflow/components/vectorstores/local_db.py b/src/backend/base/langflow/components/vectorstores/local_db.py
index 7dedc2bed971..2046c37b7511 100644
--- a/src/backend/base/langflow/components/vectorstores/local_db.py
+++ b/src/backend/base/langflow/components/vectorstores/local_db.py
@@ -22,6 +22,7 @@ class LocalDBComponent(LCVectorStoreComponent):
name = "LocalDB"
icon = "database"
legacy = True
+ replacement = ["knowledgebases.KnowledgeRetrieval", "knowledgebases.KnowledgeIngestion"]
inputs = [
TabInput(
diff --git a/src/backend/base/langflow/components/zep/zep.py b/src/backend/base/langflow/components/zep/zep.py
index a4e60ef48c3b..50ce986239c0 100644
--- a/src/backend/base/langflow/components/zep/zep.py
+++ b/src/backend/base/langflow/components/zep/zep.py
@@ -9,6 +9,7 @@ class ZepChatMemory(LCChatMemoryComponent):
name = "ZepChatMemory"
icon = "ZepMemory"
legacy = True
+ replacement = ["helpers.Memory"]
inputs = [
MessageTextInput(name="url", display_name="Zep URL", info="URL of the Zep instance."),
diff --git a/src/backend/base/langflow/custom/attributes.py b/src/backend/base/langflow/custom/attributes.py
index 2d39f86bc0b4..a1dec20a0fe8 100644
--- a/src/backend/base/langflow/custom/attributes.py
+++ b/src/backend/base/langflow/custom/attributes.py
@@ -69,6 +69,7 @@ def getattr_return_dict(value):
"description": getattr_return_str,
"beta": getattr_return_bool,
"legacy": getattr_return_bool,
+ "replacement": getattr_return_list_of_str,
"documentation": getattr_return_str,
"priority": getattr_return_int,
"icon": validate_icon,
diff --git a/src/backend/base/langflow/template/frontend_node/base.py b/src/backend/base/langflow/template/frontend_node/base.py
index b0292011747b..4947fdb2ee9a 100644
--- a/src/backend/base/langflow/template/frontend_node/base.py
+++ b/src/backend/base/langflow/template/frontend_node/base.py
@@ -55,6 +55,8 @@ class FrontendNode(BaseModel):
"""Whether the frontend node is in beta."""
legacy: bool = False
"""Whether the frontend node is legacy."""
+ replacement: list[str] | None = None
+ """Replacement for the frontend node when it is deprecated."""
error: str | None = None
"""Error message for the frontend node."""
edited: bool = False
diff --git a/src/frontend/src/CustomNodes/GenericNode/components/NodeLegacyComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/NodeLegacyComponent/index.tsx
new file mode 100644
index 000000000000..c1710765aa62
--- /dev/null
+++ b/src/frontend/src/CustomNodes/GenericNode/components/NodeLegacyComponent/index.tsx
@@ -0,0 +1,75 @@
+import { Button } from "@/components/ui/button";
+import useFlowStore from "@/stores/flowStore";
+import { cn } from "@/utils/utils";
+import { useGetReplacementComponents } from "../../hooks/use-get-replacement-components";
+
+export default function NodeLegacyComponent({
+ legacy,
+ replacement,
+ setDismissAll,
+}: {
+ legacy?: boolean;
+ replacement?: string[];
+ setDismissAll: (value: boolean) => void;
+}) {
+ const setFilterComponent = useFlowStore((state) => state.setFilterComponent);
+ const setFilterType = useFlowStore((state) => state.setFilterType);
+ const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
+
+ const handleFilterComponent = (component: string) => {
+ setFilterComponent(component);
+ setFilterType(undefined);
+ setFilterEdge([]);
+ };
+
+ const foundComponents = useGetReplacementComponents(replacement);
+ return (
+
+
+
+
Legacy
+
+
+
+
+ {replacement && Array.isArray(replacement) && replacement.length > 0 ? (
+
+ Use{" "}
+ {foundComponents.map((component, index) => (
+ <>
+ {index > 0 && ", "}
+
+ >
+ ))}
+ .
+
+ ) : (
+ "No direct replacement."
+ )}
+
+
+ );
+}
diff --git a/src/frontend/src/CustomNodes/GenericNode/components/NodeName/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/NodeName/index.tsx
index 6c397b1cba45..fe36caacb836 100644
--- a/src/frontend/src/CustomNodes/GenericNode/components/NodeName/index.tsx
+++ b/src/frontend/src/CustomNodes/GenericNode/components/NodeName/index.tsx
@@ -12,6 +12,7 @@ export default function NodeName({
nodeId,
showNode,
beta,
+ legacy,
editNameDescription,
toggleEditNameDescription,
setHasChangedNodeDescription,
@@ -21,6 +22,7 @@ export default function NodeName({
nodeId: string;
showNode: boolean;
beta: boolean;
+ legacy?: boolean;
editNameDescription: boolean;
toggleEditNameDescription: () => void;
setHasChangedNodeDescription: (hasChanged: boolean) => void;
@@ -98,6 +100,13 @@ export default function NodeName({
{display_name}
+ {legacy && (
+
+ )}
{beta && (
diff --git a/src/frontend/src/CustomNodes/GenericNode/components/NodeUpdateComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/NodeUpdateComponent/index.tsx
index d0de7b8231f8..7f44b971bb37 100644
--- a/src/frontend/src/CustomNodes/GenericNode/components/NodeUpdateComponent/index.tsx
+++ b/src/frontend/src/CustomNodes/GenericNode/components/NodeUpdateComponent/index.tsx
@@ -1,6 +1,4 @@
-import ForwardedIconComponent from "@/components/common/genericIconComponent";
import { Button } from "@/components/ui/button";
-import { ICON_STROKE_WIDTH } from "@/constants/constants";
import { cn } from "@/utils/utils";
export default function NodeUpdateComponent({
diff --git a/src/frontend/src/CustomNodes/GenericNode/components/handleRenderComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/handleRenderComponent/index.tsx
index ce375a7bc79f..c6745c7a93fd 100644
--- a/src/frontend/src/CustomNodes/GenericNode/components/handleRenderComponent/index.tsx
+++ b/src/frontend/src/CustomNodes/GenericNode/components/handleRenderComponent/index.tsx
@@ -189,6 +189,7 @@ const HandleRenderComponent = memo(function HandleRenderComponent({
const {
setHandleDragging,
setFilterType,
+ setFilterComponent,
handleDragging,
filterType,
onConnect,
@@ -197,6 +198,7 @@ const HandleRenderComponent = memo(function HandleRenderComponent({
(state) => ({
setHandleDragging: state.setHandleDragging,
setFilterType: state.setFilterType,
+ setFilterComponent: state.setFilterComponent,
handleDragging: state.handleDragging,
filterType: state.filterType,
onConnect: state.onConnect,
@@ -365,10 +367,12 @@ const HandleRenderComponent = memo(function HandleRenderComponent({
const nodes = useFlowStore.getState().nodes;
setFilterEdge(groupByFamily(myData, tooltipTitle!, left, nodes!));
setFilterType(currentFilter);
+ setFilterComponent("");
if (filterOpenHandle && filterType) {
onConnect(getConnection(filterType));
setFilterType(undefined);
setFilterEdge([]);
+ setFilterComponent("");
}
}, [
myData,
@@ -376,6 +380,7 @@ const HandleRenderComponent = memo(function HandleRenderComponent({
left,
setFilterEdge,
setFilterType,
+ setFilterComponent,
currentFilter,
filterOpenHandle,
filterType,
diff --git a/src/frontend/src/CustomNodes/GenericNode/hooks/use-get-replacement-components.ts b/src/frontend/src/CustomNodes/GenericNode/hooks/use-get-replacement-components.ts
new file mode 100644
index 000000000000..efbe7c52de73
--- /dev/null
+++ b/src/frontend/src/CustomNodes/GenericNode/hooks/use-get-replacement-components.ts
@@ -0,0 +1,18 @@
+import { useTypesStore } from "@/stores/typesStore";
+
+export const useGetReplacementComponents = (replacement?: string[]) => {
+ const data = useTypesStore((state) => state.data);
+
+ return replacement && Array.isArray(replacement) && replacement.length > 0
+ ? replacement.map((component) => {
+ const categoryName = component?.split(".")[0];
+ const componentName = component?.split(".")[1];
+
+ return (
+ categoryName &&
+ componentName &&
+ data[categoryName][componentName].display_name
+ );
+ })
+ : [];
+};
diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx
index b2eb1eeedf0f..7cbf65e85f7a 100644
--- a/src/frontend/src/CustomNodes/GenericNode/index.tsx
+++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx
@@ -30,6 +30,7 @@ import { classNames, cn } from "../../utils/utils";
import { processNodeAdvancedFields } from "../helpers/process-node-advanced-fields";
import useUpdateNodeCode from "../hooks/use-update-node-code";
import NodeDescription from "./components/NodeDescription";
+import NodeLegacyComponent from "./components/NodeLegacyComponent";
import NodeName from "./components/NodeName";
import NodeOutputs from "./components/NodeOutputParameter/NodeOutputs";
import NodeUpdateComponent from "./components/NodeUpdateComponent";
@@ -97,10 +98,22 @@ function GenericNode({
const removeDismissedNodes = useFlowStore(
(state) => state.removeDismissedNodes,
);
+
+ const dismissedNodesLegacy = useFlowStore(
+ (state) => state.dismissedNodesLegacy,
+ );
+ const addDismissedNodesLegacy = useFlowStore(
+ (state) => state.addDismissedNodesLegacy,
+ );
+
const dismissAll = useMemo(
() => dismissedNodes.includes(data.id),
[dismissedNodes, data.id],
);
+ const dismissAllLegacy = useMemo(
+ () => dismissedNodesLegacy.includes(data.id),
+ [dismissedNodesLegacy, data.id],
+ );
const showNode = data.showNode ?? true;
@@ -356,6 +369,11 @@ function GenericNode({
[isOutdated, hasBreakingChange, isUserEdited, dismissAll],
);
+ const shouldShowLegacyComponent = useMemo(
+ () => (data.node?.legacy || data.node?.replacement) && !dismissAllLegacy,
+ [data.node?.legacy, data.node?.replacement, dismissAllLegacy],
+ );
+
const memoizedNodeToolbarComponent = useMemo(() => {
return selected && selectedNodesCount === 1 ? (
<>
@@ -457,6 +475,11 @@ function GenericNode({
[addDismissedNodes, data.id],
);
+ const memoizedSetDismissAllLegacy = useCallback(
+ () => addDismissedNodesLegacy([data.id]),
+ [addDismissedNodesLegacy, data.id],
+ );
+
return (
)}
{memoizedNodeToolbarComponent}
- {shouldShowUpdateComponent && (
+ {shouldShowUpdateComponent ? (
+ ) : shouldShowLegacyComponent ? (
+
+ ) : (
+ <>>
)}
+
0
+ }
editNameDescription={editNameDescription}
toggleEditNameDescription={toggleEditNameDescription}
setHasChangedNodeDescription={setHasChangedNodeDescription}
diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx
index 9a6f1f0a9836..9e65e5f59948 100644
--- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx
+++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx
@@ -102,6 +102,7 @@ export default function Page({
const types = useTypesStore((state) => state.types);
const templates = useTypesStore((state) => state.templates);
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
+ const setFilterComponent = useFlowStore((state) => state.setFilterComponent);
const reactFlowWrapper = useRef
(null);
const setPositionDictionary = useFlowStore(
(state) => state.setPositionDictionary,
@@ -580,6 +581,7 @@ export default function Page({
const onPaneClick = useCallback(
(event: React.MouseEvent) => {
setFilterEdge([]);
+ setFilterComponent("");
if (isAddingNote) {
const shadowBox = document.getElementById("shadow-box");
if (shadowBox) {
@@ -613,7 +615,14 @@ export default function Page({
setIsAddingNote(false);
}
},
- [isAddingNote, setNodes, reactFlowInstance, getNodeId, setFilterEdge],
+ [
+ isAddingNote,
+ setNodes,
+ reactFlowInstance,
+ getNodeId,
+ setFilterEdge,
+ setFilterComponent,
+ ],
);
const handleEdgeClick = (event, edge) => {
diff --git a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/__tests__/sidebarFilterComponent.test.tsx b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/__tests__/sidebarFilterComponent.test.tsx
index 2587c9b2d136..6769b9bf0c9c 100644
--- a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/__tests__/sidebarFilterComponent.test.tsx
+++ b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/__tests__/sidebarFilterComponent.test.tsx
@@ -44,9 +44,8 @@ describe("SidebarFilterComponent", () => {
const mockResetFilters = jest.fn();
const defaultProps = {
- isInput: true,
- type: "string",
- color: "blue",
+ name: "Input",
+ description: "string",
resetFilters: mockResetFilters,
};
@@ -97,141 +96,119 @@ describe("SidebarFilterComponent", () => {
});
});
- describe("Input/Output Display", () => {
- it("should display 'Input:' when isInput is true", () => {
+ describe("Name Display", () => {
+ it("should display name correctly", () => {
render();
expect(screen.getByText("Input:")).toBeInTheDocument();
- expect(screen.queryByText("Output:")).not.toBeInTheDocument();
});
- it("should display 'Output:' when isInput is false", () => {
- const propsWithOutput = { ...defaultProps, isInput: false };
- render();
+ it("should display different names correctly", () => {
+ const propsWithDifferentName = { ...defaultProps, name: "Output" };
+ render();
expect(screen.getByText("Output:")).toBeInTheDocument();
expect(screen.queryByText("Input:")).not.toBeInTheDocument();
});
- it("should display correct label for input", () => {
- render();
+ it("should display custom names correctly", () => {
+ const propsWithCustomName = { ...defaultProps, name: "Custom Filter" };
+ render();
- expect(screen.getByText("Input:")).toBeInTheDocument();
+ expect(screen.getByText("Custom Filter:")).toBeInTheDocument();
});
- it("should display correct label for output", () => {
- const propsWithOutput = { ...defaultProps, isInput: false };
- render();
+ it("should handle empty names", () => {
+ const propsWithEmptyName = { ...defaultProps, name: "" };
+ render();
- expect(screen.getByText("Output:")).toBeInTheDocument();
+ expect(screen.getByText(":")).toBeInTheDocument();
});
});
- describe("Type Display", () => {
- it("should display single type correctly", () => {
+ describe("Description Display", () => {
+ it("should display single description correctly", () => {
render();
expect(screen.getByText("string")).toBeInTheDocument();
});
- it("should display multiple types correctly", () => {
- const propsWithMultipleTypes = {
+ it("should display multiple descriptions correctly", () => {
+ const propsWithMultipleDescriptions = {
...defaultProps,
- type: "string\nint\nboolean",
+ description: "string\nint\nboolean",
};
- render();
+ render();
expect(screen.getByText("string, int, boolean")).toBeInTheDocument();
});
- it("should handle empty type", () => {
- const propsWithEmptyType = { ...defaultProps, type: "" };
- render();
+ it("should handle empty description", () => {
+ const propsWithEmptyDescription = { ...defaultProps, description: "" };
+ render();
// Should not crash
expect(screen.getByTestId("icon-ListFilter")).toBeInTheDocument();
});
- it("should handle type with special characters", () => {
- const propsWithSpecialType = { ...defaultProps, type: "List[str]" };
- render();
+ it("should handle description with special characters", () => {
+ const propsWithSpecialDescription = {
+ ...defaultProps,
+ description: "List[str]",
+ };
+ render();
expect(screen.getByText("List[str]")).toBeInTheDocument();
});
});
describe("Pluralization", () => {
- it("should use singular form for single type", () => {
+ it("should use singular form for single description", () => {
render();
expect(screen.getByText("Input:")).toBeInTheDocument();
expect(screen.queryByText("Inputs:")).not.toBeInTheDocument();
});
- it("should use plural form for multiple types", () => {
- const propsWithMultipleTypes = {
+ it("should use plural form for multiple descriptions", () => {
+ const propsWithMultipleDescriptions = {
...defaultProps,
- type: "string\nint",
+ description: "string\nint",
};
- render();
+ render();
expect(screen.getByText("Inputs:")).toBeInTheDocument();
expect(screen.queryByText("Input:")).not.toBeInTheDocument();
});
- it("should use plural form for output with multiple types", () => {
- const propsWithMultipleOutputTypes = {
+ it("should use plural form for any name with multiple descriptions", () => {
+ const propsWithMultipleDescriptions = {
...defaultProps,
- isInput: false,
- type: "string\nint\nfloat",
+ name: "Output",
+ description: "string\nint\nfloat",
};
- render();
+ render();
expect(screen.getByText("Outputs:")).toBeInTheDocument();
expect(screen.queryByText("Output:")).not.toBeInTheDocument();
});
- it("should handle edge case with empty lines in type", () => {
+ it("should handle edge case with empty lines in description", () => {
const propsWithEmptyLines = {
...defaultProps,
- type: "string\n\nint",
+ description: "string\n\nint",
};
render();
- // Should treat empty line as a separate type for pluralization
+ // Should treat empty line as a separate description for pluralization
expect(screen.getByText("Inputs:")).toBeInTheDocument();
});
});
- describe("Color Styling", () => {
- it("should render with different color props", () => {
- render();
-
- // Just verify component renders with color prop - inline styles are complex to test
- expect(screen.getByTestId("icon-ListFilter")).toBeInTheDocument();
- });
-
- it("should handle different colors", () => {
- const propsWithDifferentColor = { ...defaultProps, color: "red" };
- render();
-
- // Verify component renders without errors with different color
- expect(screen.getByTestId("icon-ListFilter")).toBeInTheDocument();
- });
-
- it("should handle custom color values", () => {
- const propsWithCustomColor = { ...defaultProps, color: "custom-green" };
- render();
-
- // Verify component renders without errors with custom color
- expect(screen.getByTestId("icon-ListFilter")).toBeInTheDocument();
- });
- });
-
describe("Reset Functionality", () => {
it("should call resetFilters when reset button is clicked", async () => {
const user = userEvent.setup();
@@ -297,7 +274,7 @@ describe("SidebarFilterComponent", () => {
const tooltip = screen.getByTestId("tooltip");
expect(container).toBeInTheDocument();
- expect(container).toContainElement(iconContainer);
+ expect(container).toContainElement(iconContainer!);
expect(container).toContainElement(tooltip);
expect(tooltip).toContainElement(resetButton);
});
@@ -360,34 +337,26 @@ describe("SidebarFilterComponent", () => {
});
describe("Props Handling", () => {
- it("should handle different isInput values", () => {
+ it("should handle different name values", () => {
const { rerender } = render(
- ,
+ ,
);
expect(screen.getByText("Input:")).toBeInTheDocument();
- rerender();
+ rerender();
expect(screen.getByText("Output:")).toBeInTheDocument();
});
- it("should handle different type values", () => {
+ it("should handle different description values", () => {
const { rerender } = render(
- ,
+ ,
);
expect(screen.getByText("string")).toBeInTheDocument();
- rerender();
- expect(screen.getByText("number")).toBeInTheDocument();
- });
-
- it("should handle different color values", () => {
- const { rerender } = render(
- ,
+ rerender(
+ ,
);
- expect(screen.getByText("string")).toBeInTheDocument();
-
- rerender();
- expect(screen.getByText("string")).toBeInTheDocument();
+ expect(screen.getByText("number")).toBeInTheDocument();
});
it("should handle missing resetFilters function gracefully", () => {
@@ -403,94 +372,103 @@ describe("SidebarFilterComponent", () => {
});
describe("Edge Cases", () => {
- it("should handle very long type names", () => {
- const propsWithLongType = {
+ it("should handle very long description names", () => {
+ const propsWithLongDescription = {
...defaultProps,
- type: "VeryLongTypeNameThatExceedsNormalLength",
+ description: "VeryLongDescriptionNameThatExceedsNormalLength",
};
- render();
+ render();
expect(
- screen.getByText("VeryLongTypeNameThatExceedsNormalLength"),
+ screen.getByText("VeryLongDescriptionNameThatExceedsNormalLength"),
).toBeInTheDocument();
});
- it("should handle multiple very long type names", () => {
- const propsWithMultipleLongTypes = {
+ it("should handle multiple very long description names", () => {
+ const propsWithMultipleLongDescriptions = {
...defaultProps,
- type: "FirstVeryLongTypeName\nSecondVeryLongTypeName\nThirdVeryLongTypeName",
+ description:
+ "FirstVeryLongDescriptionName\nSecondVeryLongDescriptionName\nThirdVeryLongDescriptionName",
};
- render();
+ render();
expect(
screen.getByText(
- "FirstVeryLongTypeName, SecondVeryLongTypeName, ThirdVeryLongTypeName",
+ "FirstVeryLongDescriptionName, SecondVeryLongDescriptionName, ThirdVeryLongDescriptionName",
),
).toBeInTheDocument();
});
- it("should handle types with newlines and commas", () => {
- const propsWithComplexTypes = {
+ it("should handle descriptions with newlines and commas", () => {
+ const propsWithComplexDescriptions = {
...defaultProps,
- type: "List[str, int]\nDict[str, Any]",
+ description: "List[str, int]\nDict[str, Any]",
};
- render();
+ render();
expect(
screen.getByText("List[str, int], Dict[str, Any]"),
).toBeInTheDocument();
});
- it("should handle empty color", () => {
- const propsWithEmptyColor = { ...defaultProps, color: "" };
- render();
+ it("should handle very long names", () => {
+ const propsWithLongName = {
+ ...defaultProps,
+ name: "VeryLongFilterNameThatMightCauseIssues",
+ };
+ render();
- // Verify component renders without errors with empty color
- expect(screen.getByTestId("icon-ListFilter")).toBeInTheDocument();
+ // Verify component renders without errors with long name
+ expect(
+ screen.getByText("VeryLongFilterNameThatMightCauseIssues:"),
+ ).toBeInTheDocument();
});
- it("should handle single newline character as type", () => {
- const propsWithNewlineType = { ...defaultProps, type: "\n" };
- render();
+ it("should handle single newline character as description", () => {
+ const propsWithNewlineDescription = {
+ ...defaultProps,
+ description: "\n",
+ };
+ render();
expect(screen.getByText("Inputs:")).toBeInTheDocument(); // Should be plural due to split
});
});
describe("Text Content", () => {
- it("should display correct text for input with single type", () => {
+ it("should display correct text for filter with single description", () => {
render();
expect(screen.getByText("Input:")).toBeInTheDocument();
expect(screen.getByText("string")).toBeInTheDocument();
});
- it("should display correct text for output with multiple types", () => {
- const propsWithMultipleOutputTypes = {
+ it("should display correct text for filter with multiple descriptions", () => {
+ const propsWithMultipleDescriptions = {
...defaultProps,
- isInput: false,
- type: "str\nint\nfloat",
+ name: "Output",
+ description: "str\nint\nfloat",
};
- render();
+ render();
expect(screen.getByText("Outputs:")).toBeInTheDocument();
expect(screen.getByText("str, int, float")).toBeInTheDocument();
});
- it("should join multiple types with commas and spaces", () => {
- const propsWithMultipleTypes = {
+ it("should join multiple descriptions with commas and spaces", () => {
+ const propsWithMultipleDescriptions = {
...defaultProps,
- type: "type1\ntype2\ntype3\ntype4",
+ description: "desc1\ndesc2\ndesc3\ndesc4",
};
- render();
+ render();
expect(
- screen.getByText("type1, type2, type3, type4"),
+ screen.getByText("desc1, desc2, desc3, desc4"),
).toBeInTheDocument();
});
});
@@ -515,9 +493,9 @@ describe("SidebarFilterComponent", () => {
describe("Callback Functions", () => {
it("should work with different resetFilters implementations", async () => {
const user = userEvent.setup();
- const customResetFilters = jest.fn((data) => {
- // Custom implementation that might accept parameters
- console.log("Custom reset", data);
+ const customResetFilters = jest.fn(() => {
+ // Custom implementation
+ console.log("Custom reset");
});
const propsWithCustomCallback = {
diff --git a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/__tests__/sidebarHeader.test.tsx b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/__tests__/sidebarHeader.test.tsx
index 012d7348b6f0..71d7b0675f64 100644
--- a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/__tests__/sidebarHeader.test.tsx
+++ b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/__tests__/sidebarHeader.test.tsx
@@ -1,5 +1,4 @@
import { render, screen } from "@testing-library/react";
-import mockAPIData from "@/utils/testUtils/mockData/mockAPIData";
import { SidebarHeaderComponentProps } from "../../types";
import { SidebarHeaderComponent } from "../sidebarHeader";
@@ -104,12 +103,11 @@ jest.mock("../searchInput", () => ({
}));
jest.mock("../sidebarFilterComponent", () => ({
- SidebarFilterComponent: ({ isInput, type, color, resetFilters }: any) => (
+ SidebarFilterComponent: ({ name, description, resetFilters }: any) => (
Sidebar Filter
@@ -129,8 +127,7 @@ describe("SidebarHeaderComponent", () => {
const mockHandleInputFocus = jest.fn();
const mockHandleInputBlur = jest.fn();
const mockHandleInputChange = jest.fn();
- const mockSetFilterEdge = jest.fn();
- const mockSetFilterData = jest.fn();
+ const mockResetFilters = jest.fn();
const defaultProps: SidebarHeaderComponentProps = {
showConfig: false,
@@ -145,10 +142,9 @@ describe("SidebarHeaderComponent", () => {
handleInputFocus: mockHandleInputFocus,
handleInputBlur: mockHandleInputBlur,
handleInputChange: mockHandleInputChange,
- filterType: undefined,
- setFilterEdge: mockSetFilterEdge,
- setFilterData: mockSetFilterData,
- data: mockAPIData,
+ filterName: "",
+ filterDescription: "",
+ resetFilters: mockResetFilters,
};
beforeEach(() => {
@@ -349,66 +345,80 @@ describe("SidebarHeaderComponent", () => {
});
describe("Filter Component Conditional Rendering", () => {
- it("should not render filter component when filterType is null", () => {
+ it("should not render filter component when filterName and filterDescription are empty", () => {
render(
);
expect(screen.queryByTestId("sidebar-filter")).not.toBeInTheDocument();
});
- it("should render filter component when filterType is provided", () => {
+ it("should not render filter component when only filterName is provided", () => {
+ const propsWithOnlyName = {
+ ...defaultProps,
+ filterName: "Test Filter",
+ filterDescription: "",
+ };
+
+ render(
);
+
+ expect(screen.queryByTestId("sidebar-filter")).not.toBeInTheDocument();
+ });
+
+ it("should not render filter component when only filterDescription is provided", () => {
+ const propsWithOnlyDescription = {
+ ...defaultProps,
+ filterName: "",
+ filterDescription: "Test Description",
+ };
+
+ render(
);
+
+ expect(screen.queryByTestId("sidebar-filter")).not.toBeInTheDocument();
+ });
+
+ it("should render filter component when both filterName and filterDescription are provided", () => {
const propsWithFilter = {
...defaultProps,
- filterType: {
- source: "input",
- sourceHandle: "input",
- target: undefined,
- targetHandle: undefined,
- type: "input",
- color: "#FF0000",
- },
+ filterName: "Input Filter",
+ filterDescription: "Showing input components",
};
render(
);
const filterComponent = screen.getByTestId("sidebar-filter");
expect(filterComponent).toBeInTheDocument();
- expect(filterComponent).toHaveAttribute("data-is-input", "true");
- expect(filterComponent).toHaveAttribute("data-type", "input");
- expect(filterComponent).toHaveAttribute("data-color", "#FF0000");
+ expect(filterComponent).toHaveAttribute("data-name", "Input Filter");
+ expect(filterComponent).toHaveAttribute(
+ "data-description",
+ "Showing input components",
+ );
});
- it("should pass correct props to filter component for output", () => {
- const propsWithOutputFilter = {
+ it("should pass correct props to filter component", () => {
+ const propsWithFilter = {
...defaultProps,
- filterType: {
- source: undefined,
- sourceHandle: undefined,
- target: "output",
- targetHandle: "output",
- type: "output",
- color: "#00FF00",
- },
+ filterName: "Output Filter",
+ filterDescription: "Showing output components",
};
- render(
);
+ render(
);
const filterComponent = screen.getByTestId("sidebar-filter");
- expect(filterComponent).toHaveAttribute("data-is-input", "false");
- expect(filterComponent).toHaveAttribute("data-type", "output");
- expect(filterComponent).toHaveAttribute("data-color", "#00FF00");
+ expect(filterComponent).toHaveAttribute("data-name", "Output Filter");
+ expect(filterComponent).toHaveAttribute(
+ "data-description",
+ "Showing output components",
+ );
+ expect(filterComponent).toHaveAttribute(
+ "data-reset-filters",
+ expect.stringContaining("function"),
+ );
});
it("should handle filter reset correctly", () => {
const propsWithFilter = {
...defaultProps,
- filterType: {
- source: "input",
- sourceHandle: "input",
- target: "output",
- targetHandle: "output",
- type: "input",
- color: "#FF0000",
- },
+ filterName: "Test Filter",
+ filterDescription: "Test Description",
};
render(
);
@@ -416,7 +426,7 @@ describe("SidebarHeaderComponent", () => {
const filterComponent = screen.getByTestId("sidebar-filter");
expect(filterComponent).toHaveAttribute(
"data-reset-filters",
- expect.stringContaining("function"),
+ mockResetFilters.toString(),
);
});
});
@@ -440,14 +450,8 @@ describe("SidebarHeaderComponent", () => {
it("should maintain structure with filter component", () => {
const propsWithFilter = {
...defaultProps,
- filterType: {
- source: "input",
- sourceHandle: "input",
- target: "output",
- targetHandle: "output",
- type: "input",
- color: "#FF0000",
- },
+ filterName: "Test Filter",
+ filterDescription: "Test Description",
};
render(
);
@@ -515,6 +519,7 @@ describe("SidebarHeaderComponent", () => {
handleInputFocus: undefined as any,
handleInputBlur: undefined as any,
handleInputChange: undefined as any,
+ resetFilters: undefined as any,
};
expect(() => {
@@ -522,36 +527,39 @@ describe("SidebarHeaderComponent", () => {
}).not.toThrow();
});
- it("should handle undefined filterType gracefully", () => {
- const propsWithUndefinedFilter = {
+ it("should handle empty filter strings gracefully", () => {
+ const propsWithEmptyFilters = {
...defaultProps,
- filterType: undefined,
+ filterName: "",
+ filterDescription: "",
};
- render(
);
+ render(
);
expect(screen.queryByTestId("sidebar-filter")).not.toBeInTheDocument();
});
- it("should handle complex filterType objects", () => {
- const propsWithComplexFilter = {
+ it("should handle long filter names and descriptions", () => {
+ const propsWithLongFilters = {
...defaultProps,
- filterType: {
- source: "input",
- sourceHandle: "input",
- target: undefined,
- targetHandle: undefined,
- type: "complex-input",
- color: "#ABCDEF",
- additionalProp: "ignored",
- },
+ filterName: "Very Long Filter Name That Might Cause Issues",
+ filterDescription:
+ "This is a very long description that might contain multiple lines and special characters @#$%",
};
expect(() => {
- render(
);
+ render(
);
}).not.toThrow();
const filterComponent = screen.getByTestId("sidebar-filter");
expect(filterComponent).toBeInTheDocument();
+ expect(filterComponent).toHaveAttribute(
+ "data-name",
+ "Very Long Filter Name That Might Cause Issues",
+ );
+ expect(filterComponent).toHaveAttribute(
+ "data-description",
+ "This is a very long description that might contain multiple lines and special characters @#$%",
+ );
});
});
@@ -582,14 +590,8 @@ describe("SidebarHeaderComponent", () => {
showBeta: true,
showLegacy: false,
isInputFocused: true,
- filterType: {
- source: "input",
- sourceHandle: "input",
- target: undefined,
- targetHandle: undefined,
- type: "input",
- color: "#123456",
- },
+ filterName: "Integration Filter",
+ filterDescription: "Testing integration",
};
render(
);
@@ -601,8 +603,12 @@ describe("SidebarHeaderComponent", () => {
"integration test",
);
expect(screen.getByTestId("sidebar-filter")).toHaveAttribute(
- "data-type",
- "input",
+ "data-name",
+ "Integration Filter",
+ );
+ expect(screen.getByTestId("sidebar-filter")).toHaveAttribute(
+ "data-description",
+ "Testing integration",
);
expect(screen.getByTestId("disclosure")).toHaveAttribute(
"data-open",
diff --git a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarFilterComponent.tsx b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarFilterComponent.tsx
index 410cb4525319..f9fe51e02ec8 100644
--- a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarFilterComponent.tsx
+++ b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarFilterComponent.tsx
@@ -3,34 +3,29 @@ import ShadTooltip from "@/components/common/shadTooltipComponent";
import { Button } from "@/components/ui/button";
export function SidebarFilterComponent({
- isInput,
- type,
- color,
+ name,
+ description,
resetFilters,
}: {
- isInput: boolean;
- type: string;
- color: string;
+ name: string;
+ description: string;
resetFilters: () => void;
}) {
- const tooltips = type.split("\n");
+ const tooltips = description.split("\n");
const plural = tooltips.length > 1 ? "s" : "";
return (
-
+
-
- {isInput ? "Input" : "Output"}
+
+ {name}
{plural}:{" "}
-
diff --git a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarHeader.tsx b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarHeader.tsx
index 06c582ccfd46..ad9828c35622 100644
--- a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarHeader.tsx
+++ b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/components/sidebarHeader.tsx
@@ -28,10 +28,9 @@ export const SidebarHeaderComponent = memo(function SidebarHeaderComponent({
handleInputFocus,
handleInputBlur,
handleInputChange,
- filterType,
- setFilterEdge,
- setFilterData,
- data,
+ filterName,
+ filterDescription,
+ resetFilters,
}: SidebarHeaderComponentProps) {
return (
@@ -79,15 +78,11 @@ export const SidebarHeaderComponent = memo(function SidebarHeaderComponent({
handleInputBlur={handleInputBlur}
handleInputChange={handleInputChange}
/>
- {filterType && (
+ {filterName !== "" && filterDescription !== "" && (
{
- setFilterEdge([]);
- setFilterData(data);
- }}
+ name={filterName}
+ description={filterDescription}
+ resetFilters={resetFilters}
/>
)}
{ENABLE_NEW_SIDEBAR && (
diff --git a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-component-filter.ts b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-component-filter.ts
new file mode 100644
index 000000000000..7bc9a35daa37
--- /dev/null
+++ b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/helpers/apply-component-filter.ts
@@ -0,0 +1,18 @@
+import type { APIDataType } from "@/types/api";
+
+export const applyComponentFilter = (
+ filteredData: APIDataType,
+ getFilterComponent,
+) => {
+ const [category, component] = getFilterComponent.split(".");
+ return Object.fromEntries(
+ Object.entries(filteredData).map(([cat, items]) => [
+ cat,
+ Object.fromEntries(
+ Object.entries(items).filter(
+ ([name, _]) => name === component && cat === category,
+ ),
+ ),
+ ]),
+ );
+};
diff --git a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/index.tsx
index ed5acf8f87da..60dcf6148ad3 100644
--- a/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/index.tsx
+++ b/src/frontend/src/pages/FlowPage/components/flowSidebarComponent/index.tsx
@@ -42,6 +42,7 @@ import SidebarMenuButtons from "./components/sidebarFooterButtons";
import { SidebarHeaderComponent } from "./components/sidebarHeader";
import SidebarSegmentedNav from "./components/sidebarSegmentedNav";
import { applyBetaFilter } from "./helpers/apply-beta-filter";
+import { applyComponentFilter } from "./helpers/apply-component-filter";
import { applyEdgeFilter } from "./helpers/apply-edge-filter";
import { applyLegacyFilter } from "./helpers/apply-legacy-filter";
import { combinedResultsFn } from "./helpers/combined-results";
@@ -145,11 +146,19 @@ interface FlowSidebarComponentProps {
export function FlowSidebarComponent({ isLoading }: FlowSidebarComponentProps) {
const data = useTypesStore((state) => state.data);
- const { getFilterEdge, setFilterEdge, filterType } = useFlowStore(
+ const {
+ getFilterEdge,
+ setFilterEdge,
+ filterType,
+ getFilterComponent,
+ setFilterComponent,
+ } = useFlowStore(
useShallow((state) => ({
getFilterEdge: state.getFilterEdge,
setFilterEdge: state.setFilterEdge,
filterType: state.filterType,
+ getFilterComponent: state.getFilterComponent,
+ setFilterComponent: state.setFilterComponent,
})),
);
@@ -299,6 +308,10 @@ export function FlowSidebarComponent({ isLoading }: FlowSidebarComponentProps) {
filteredData = applyEdgeFilter(filteredData, getFilterEdge);
}
+ if (getFilterComponent !== "") {
+ filteredData = applyComponentFilter(filteredData, getFilterComponent);
+ }
+
if (!showBeta) {
filteredData = applyBetaFilter(filteredData);
}
@@ -308,7 +321,13 @@ export function FlowSidebarComponent({ isLoading }: FlowSidebarComponentProps) {
}
return filteredData;
- }, [searchFilteredData, getFilterEdge, showBeta, showLegacy]);
+ }, [
+ searchFilteredData,
+ getFilterEdge,
+ getFilterComponent,
+ showBeta,
+ showLegacy,
+ ]);
const hasResults = useMemo(() => {
return Object.entries(dataFilter).some(
@@ -341,22 +360,34 @@ export function FlowSidebarComponent({ isLoading }: FlowSidebarComponentProps) {
}, [baseData, setSearch]);
useEffect(() => {
- if (filterType) {
+ if (filterType || getFilterComponent !== "") {
setOpen(true);
setActiveSection("search");
}
- }, [filterType, setOpen]);
+ }, [filterType, getFilterComponent, setOpen]);
useEffect(() => {
setFilterData(finalFilteredData);
- if (search !== "" || filterType || getFilterEdge.length > 0) {
+ if (
+ search !== "" ||
+ filterType ||
+ getFilterEdge.length > 0 ||
+ getFilterComponent !== ""
+ ) {
const newOpenCategories = Object.keys(finalFilteredData).filter(
(cat) => Object.keys(finalFilteredData[cat]).length > 0,
);
setOpenCategories(newOpenCategories);
}
- }, [finalFilteredData, search, filterType, getFilterEdge]);
+ }, [
+ finalFilteredData,
+ search,
+ filterType,
+ getFilterEdge,
+ setFilterComponent,
+ getFilterComponent,
+ ]);
useEffect(() => {
const options = {
@@ -405,16 +436,20 @@ export function FlowSidebarComponent({ isLoading }: FlowSidebarComponentProps) {
}, [baseData, mcpSuccess, mcpServers]);
useEffect(() => {
- if (getFilterEdge.length !== 0) {
+ if (getFilterEdge.length !== 0 || getFilterComponent !== "") {
setSearch("");
}
- }, [getFilterEdge, baseData]);
+ }, [getFilterEdge, getFilterComponent, baseData]);
useEffect(() => {
- if (search === "" && getFilterEdge.length === 0) {
+ if (
+ search === "" &&
+ getFilterEdge.length === 0 &&
+ getFilterComponent === ""
+ ) {
setOpenCategories([]);
}
- }, [search, getFilterEdge]);
+ }, [search, getFilterEdge, getFilterComponent]);
const searchComponentsSidebar = useShortcutsStore(
(state) => state.searchComponentsSidebar,
@@ -487,7 +522,8 @@ export function FlowSidebarComponent({ isLoading }: FlowSidebarComponentProps) {
const hasMcpServers = Boolean(mcpServers && mcpServers.length > 0);
- const hasSearchInput = search !== "" || filterType !== undefined;
+ const hasSearchInput =
+ search !== "" || filterType !== undefined || getFilterComponent !== "";
const showComponents =
(ENABLE_NEW_SIDEBAR &&
@@ -503,6 +539,28 @@ export function FlowSidebarComponent({ isLoading }: FlowSidebarComponentProps) {
(ENABLE_NEW_SIDEBAR && activeSection === "mcp") ||
(hasSearchInput && hasMcpComponents && ENABLE_NEW_SIDEBAR);
+ const [category, component] = getFilterComponent?.split(".") ?? ["", ""];
+
+ const filterDescription =
+ getFilterComponent !== ""
+ ? (baseData[category][component]?.display_name ?? "")
+ : (filterType?.type ?? "");
+
+ const filterName =
+ getFilterComponent !== ""
+ ? "Component"
+ : filterType
+ ? filterType.source
+ ? "Input"
+ : "Output"
+ : "";
+
+ const resetFilters = useCallback(() => {
+ setFilterEdge([]);
+ setFilterComponent("");
+ setFilterData(baseData);
+ }, [setFilterEdge, setFilterComponent, setFilterData, baseData]);
+
return (
) => void;
handleInputBlur: (event: React.FocusEvent) => void;
handleInputChange: (event: React.ChangeEvent) => void;
- filterType:
- | {
- source: string | undefined;
- sourceHandle: string | undefined;
- target: string | undefined;
- targetHandle: string | undefined;
- type: string;
- color: string;
- }
- | undefined;
- setFilterEdge: (edge: any[]) => void;
- setFilterData: Dispatch>;
- data: APIDataType;
+ filterName: string;
+ filterDescription: string;
+ resetFilters: () => void;
}
export interface UniqueInputsComponents {
diff --git a/src/frontend/src/stores/flowStore.ts b/src/frontend/src/stores/flowStore.ts
index 48aa0ac0b32c..786257dde495 100644
--- a/src/frontend/src/stores/flowStore.ts
+++ b/src/frontend/src/stores/flowStore.ts
@@ -226,6 +226,9 @@ const useFlowStore = create((set, get) => ({
dismissedNodes: JSON.parse(
localStorage.getItem(`dismiss_${flow?.id}`) ?? "[]",
) as string[],
+ dismissedNodesLegacy: JSON.parse(
+ localStorage.getItem(`dismiss_legacy_${flow?.id}`) ?? "[]",
+ ) as string[],
});
unselectAllNodesEdges(nodes, newEdges);
if (flow?.id) {
@@ -567,6 +570,10 @@ const useFlowStore = create((set, get) => ({
set({ getFilterEdge: newState });
},
getFilterEdge: [],
+ setFilterComponent: (newState) => {
+ set({ getFilterComponent: newState });
+ },
+ getFilterComponent: "",
onConnect: (connection) => {
const _dark = useDarkStore.getState().dark;
// const commonMarkerProps = {
@@ -1068,6 +1075,17 @@ const useFlowStore = create((set, get) => ({
);
set({ dismissedNodes: newDismissedNodes });
},
+ dismissedNodesLegacy: [],
+ addDismissedNodesLegacy: (dismissedNodes: string[]) => {
+ const newDismissedNodes = Array.from(
+ new Set([...get().dismissedNodesLegacy, ...dismissedNodes]),
+ );
+ localStorage.setItem(
+ `dismiss_legacy_${get().currentFlow?.id}`,
+ JSON.stringify(newDismissedNodes),
+ );
+ set({ dismissedNodesLegacy: newDismissedNodes });
+ },
helperLineEnabled: false,
setHelperLineEnabled: (helperLineEnabled: boolean) => {
set({ helperLineEnabled });
diff --git a/src/frontend/src/types/api/index.ts b/src/frontend/src/types/api/index.ts
index 05fa7cdf9cf5..13e3013f3722 100644
--- a/src/frontend/src/types/api/index.ts
+++ b/src/frontend/src/types/api/index.ts
@@ -43,6 +43,7 @@ export type APIClassType = {
custom_fields?: CustomFieldsType;
beta?: boolean;
legacy?: boolean;
+ replacement?: string[];
documentation: string;
error?: string;
official?: boolean;
diff --git a/src/frontend/src/types/zustand/flow/index.ts b/src/frontend/src/types/zustand/flow/index.ts
index 990cb223d970..bf74ff6d6063 100644
--- a/src/frontend/src/types/zustand/flow/index.ts
+++ b/src/frontend/src/types/zustand/flow/index.ts
@@ -65,6 +65,8 @@ export type FlowStoreType = {
dismissedNodes: string[];
addDismissedNodes: (dismissedNodes: string[]) => void;
removeDismissedNodes: (dismissedNodes: string[]) => void;
+ dismissedNodesLegacy: string[];
+ addDismissedNodesLegacy: (dismissedNodes: string[]) => void;
//key x, y
positionDictionary: { [key: number]: number };
isPositionAvailable: (position: { x: number; y: number }) => boolean;
@@ -152,6 +154,8 @@ export type FlowStoreType = {
cleanFlow: () => void;
setFilterEdge: (newState) => void;
getFilterEdge: any[];
+ setFilterComponent: (newState) => void;
+ getFilterComponent: string;
onConnect: (connection: Connection) => void;
unselectAll: () => void;
playgroundPage: boolean;
diff --git a/src/frontend/tests/core/features/filterSidebar.spec.ts b/src/frontend/tests/core/features/filterSidebar.spec.ts
index 1f3cdd604b75..f20b9be34538 100644
--- a/src/frontend/tests/core/features/filterSidebar.spec.ts
+++ b/src/frontend/tests/core/features/filterSidebar.spec.ts
@@ -122,7 +122,7 @@ test(
await expect(page.getByTestId("dataAPI Request")).toBeVisible();
await expect(page.getByTestId("datastaxAstra DB")).toBeVisible();
- await expect(page.getByTestId("logicSub Flow [Deprecated]")).toBeVisible();
+ await expect(page.getByTestId("logicSub Flow")).toBeVisible();
await page.getByTestId("sidebar-options-trigger").click();
await page.getByTestId("sidebar-beta-switch").isVisible({ timeout: 5000 });
@@ -130,7 +130,7 @@ test(
await expect(page.getByTestId("sidebar-beta-switch")).toBeChecked();
await page.getByTestId("sidebar-options-trigger").click();
- await expect(page.getByTestId("logicSub Flow [Deprecated]")).toBeVisible();
+ await expect(page.getByTestId("logicSub Flow")).toBeVisible();
await expect(page.getByTestId("processingData Operations")).toBeVisible();
@@ -138,9 +138,7 @@ test(
await expect(page.getByTestId("dataAPI Request")).not.toBeVisible();
await expect(page.getByTestId("datastaxAstra DB")).not.toBeVisible();
- await expect(
- page.getByTestId("logicSub Flow [Deprecated]"),
- ).not.toBeVisible();
+ await expect(page.getByTestId("logicSub Flow")).not.toBeVisible();
await expect(page.getByTestId("processingSplit Text")).not.toBeVisible();
},