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 && ( +
+
+ 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}:{" "} -
+
{tooltips.join(", ")}
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(); },