From 45bac3714a3ad7899834ed719b0cbee05e670c63 Mon Sep 17 00:00:00 2001 From: Jordan Frazier <122494242+jordanrfrazier@users.noreply.github.com> Date: Tue, 18 Feb 2025 09:03:07 -0800 Subject: [PATCH 1/9] feat: add db driver env variable (#5967) * Add option to disable prepared statements * [autofix.ci] apply automated fixes * use generic connection driver env var * simplify and allow usage for sqlite * allow pool class to be set to kwargs * simplify pool class selection * [autofix.ci] apply automated fixes * use reflection to get poolclass * rebase fixes * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../Financial Report Parser.json | 6 ++-- .../starter_projects/Meeting Summary.json | 6 ++-- .../starter_projects/Youtube Analysis.json | 28 +++++++++++++++---- .../langflow/services/database/service.py | 27 ++++++++++++------ .../base/langflow/services/settings/base.py | 3 ++ 5 files changed, 53 insertions(+), 17 deletions(-) diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Financial Report Parser.json b/src/backend/base/langflow/initial_setup/starter_projects/Financial Report Parser.json index 2c281e27a3..0fe4a2f82d 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Financial Report Parser.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Financial Report Parser.json @@ -960,7 +960,9 @@ "key": "ParseData", "legacy": false, "lf_version": "1.1.5", - "metadata": {}, + "metadata": { + "legacy_name": "Parse Data" + }, "minimized": false, "output_types": [], "outputs": [ @@ -1011,7 +1013,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n\n inputs = [\n DataInput(name=\"data\", display_name=\"Data\", info=\"The data to convert to text.\", is_list=True, required=True),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n" + "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n metadata = {\n \"legacy_name\": \"Parse Data\",\n }\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The data to convert to text.\",\n is_list=True,\n required=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n" }, "data": { "_input_type": "DataInput", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json b/src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json index b2812a8741..3ef9aa89fe 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Meeting Summary.json @@ -452,7 +452,9 @@ "icon": "message-square", "legacy": false, "lf_version": "1.1.5", - "metadata": {}, + "metadata": { + "legacy_name": "Parse Data" + }, "minimized": false, "output_types": [], "outputs": [ @@ -502,7 +504,7 @@ "show": true, "title_case": false, "type": "code", - "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n\n inputs = [\n DataInput(name=\"data\", display_name=\"Data\", info=\"The data to convert to text.\", is_list=True, required=True),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n" + "value": "from langflow.custom import Component\nfrom langflow.helpers.data import data_to_text, data_to_text_list\nfrom langflow.io import DataInput, MultilineInput, Output, StrInput\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass ParseDataComponent(Component):\n display_name = \"Data to Message\"\n description = \"Convert Data objects into Messages using any {field_name} from input data.\"\n icon = \"message-square\"\n name = \"ParseData\"\n metadata = {\n \"legacy_name\": \"Parse Data\",\n }\n\n inputs = [\n DataInput(\n name=\"data\",\n display_name=\"Data\",\n info=\"The data to convert to text.\",\n is_list=True,\n required=True,\n ),\n MultilineInput(\n name=\"template\",\n display_name=\"Template\",\n info=\"The template to use for formatting the data. \"\n \"It can contain the keys {text}, {data} or any other key in the Data.\",\n value=\"{text}\",\n required=True,\n ),\n StrInput(name=\"sep\", display_name=\"Separator\", advanced=True, value=\"\\n\"),\n ]\n\n outputs = [\n Output(\n display_name=\"Message\",\n name=\"text\",\n info=\"Data as a single Message, with each input Data separated by Separator\",\n method=\"parse_data\",\n ),\n Output(\n display_name=\"Data List\",\n name=\"data_list\",\n info=\"Data as a list of new Data, each having `text` formatted by Template\",\n method=\"parse_data_as_list\",\n ),\n ]\n\n def _clean_args(self) -> tuple[list[Data], str, str]:\n data = self.data if isinstance(self.data, list) else [self.data]\n template = self.template\n sep = self.sep\n return data, template, sep\n\n def parse_data(self) -> Message:\n data, template, sep = self._clean_args()\n result_string = data_to_text(template, data, sep)\n self.status = result_string\n return Message(text=result_string)\n\n def parse_data_as_list(self) -> list[Data]:\n data, template, _ = self._clean_args()\n text_list, data_list = data_to_text_list(template, data)\n for item, text in zip(data_list, text_list, strict=True):\n item.set_text(text)\n self.status = data_list\n return data_list\n" }, "data": { "_input_type": "DataInput", diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json b/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json index 7441fd1505..e72552f6f2 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json @@ -375,11 +375,11 @@ "show": true, "title_case": false, "type": "code", - "value": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING\n\nfrom langflow.custom import Component\nfrom langflow.io import DataFrameInput, HandleInput, MultilineInput, Output, StrInput\nfrom langflow.schema import DataFrame\n\nif TYPE_CHECKING:\n from langchain_core.runnables import Runnable\n\n\nclass BatchRunComponent(Component):\n display_name = \"Batch Run\"\n description = (\n \"Runs a language model over each row of a DataFrame's text column and returns a new \"\n \"DataFrame with two columns: 'text_input' (the original text) and 'model_response' \"\n \"containing the model's response.\"\n )\n icon = \"List\"\n beta = True\n\n inputs = [\n HandleInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Connect the 'Language Model' output from your LLM component here.\",\n input_types=[\"LanguageModel\"],\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"Multi-line system instruction for all rows in the DataFrame.\",\n required=False,\n ),\n DataFrameInput(\n name=\"df\",\n display_name=\"DataFrame\",\n info=\"The DataFrame whose column (specified by 'column_name') we'll treat as text messages.\",\n ),\n StrInput(\n name=\"column_name\",\n display_name=\"Column Name\",\n info=\"The name of the DataFrame column to treat as text messages. Default='text'.\",\n value=\"text\",\n ),\n ]\n\n outputs = [\n Output(\n display_name=\"Batch Results\",\n name=\"batch_results\",\n method=\"run_batch\",\n info=\"A DataFrame with two columns: 'text_input' and 'model_response'.\",\n ),\n ]\n\n async def run_batch(self) -> DataFrame:\n \"\"\"For each row in df[column_name], combine that text with system_message, then invoke the model asynchronously.\n\n Returns a new DataFrame of the same length, with columns 'text_input' and 'model_response'.\n \"\"\"\n model: Runnable = self.model\n system_msg = self.system_message or \"\"\n df: DataFrame = self.df\n col_name = self.column_name or \"text\"\n\n if col_name not in df.columns:\n msg = f\"Column '{col_name}' not found in the DataFrame.\"\n raise ValueError(msg)\n\n # Convert the specified column to a list of strings\n user_texts = df[col_name].astype(str).tolist()\n\n # Prepare the batch of conversations\n conversations = [\n [{\"role\": \"system\", \"content\": system_msg}, {\"role\": \"user\", \"content\": text}]\n if system_msg\n else [{\"role\": \"user\", \"content\": text}]\n for text in user_texts\n ]\n model = model.with_config(\n {\n \"run_name\": self.display_name,\n \"project_name\": self.get_project_name(),\n \"callbacks\": self.get_langchain_callbacks(),\n }\n )\n\n responses = await model.abatch(conversations)\n\n # Build the final data, each row has 'text_input' + 'model_response'\n rows = []\n for original_text, response in zip(user_texts, responses, strict=False):\n resp_text = response.content if hasattr(response, \"content\") else str(response)\n\n row = {\"text_input\": original_text, \"model_response\": resp_text}\n rows.append(row)\n\n # Convert to a new DataFrame\n return DataFrame(rows) # Langflow DataFrame from a list of dicts\n" + "value": "from __future__ import annotations\n\nfrom typing import TYPE_CHECKING, Any\n\nfrom loguru import logger\n\nfrom langflow.custom import Component\nfrom langflow.io import (\n BoolInput,\n DataFrameInput,\n HandleInput,\n MessageTextInput,\n MultilineInput,\n Output,\n)\nfrom langflow.schema import DataFrame\n\nif TYPE_CHECKING:\n from langchain_core.runnables import Runnable\n\n\nclass BatchRunComponent(Component):\n display_name = \"Batch Run\"\n description = (\n \"Runs a language model over each row of a DataFrame's text column and returns a new \"\n \"DataFrame with three columns: '**text_input**' (the original text), \"\n \"'**model_response**' (the model's response),and '**batch_index**' (the processing order).\"\n )\n icon = \"List\"\n beta = True\n\n inputs = [\n HandleInput(\n name=\"model\",\n display_name=\"Language Model\",\n info=\"Connect the 'Language Model' output from your LLM component here.\",\n input_types=[\"LanguageModel\"],\n required=True,\n ),\n MultilineInput(\n name=\"system_message\",\n display_name=\"System Message\",\n info=\"Multi-line system instruction for all rows in the DataFrame.\",\n required=False,\n ),\n DataFrameInput(\n name=\"df\",\n display_name=\"DataFrame\",\n info=\"The DataFrame whose column (specified by 'column_name') we'll treat as text messages.\",\n required=True,\n ),\n MessageTextInput(\n name=\"column_name\",\n display_name=\"Column Name\",\n info=\"The name of the DataFrame column to treat as text messages. Default='text'.\",\n value=\"text\",\n required=True,\n advanced=True,\n ),\n BoolInput(\n name=\"enable_metadata\",\n display_name=\"Enable Metadata\",\n info=\"If True, add metadata to the output DataFrame.\",\n value=True,\n required=False,\n advanced=True,\n ),\n ]\n\n outputs = [\n Output(\n display_name=\"Batch Results\",\n name=\"batch_results\",\n method=\"run_batch\",\n info=\"A DataFrame with columns: 'text_input', 'model_response', 'batch_index', and 'metadata'.\",\n ),\n ]\n\n def _create_base_row(self, text_input: str = \"\", model_response: str = \"\", batch_index: int = -1) -> dict[str, Any]:\n \"\"\"Create a base row with optional metadata.\"\"\"\n return {\n \"text_input\": text_input,\n \"model_response\": model_response,\n \"batch_index\": batch_index,\n }\n\n def _add_metadata(\n self, row: dict[str, Any], *, success: bool = True, system_msg: str = \"\", error: str | None = None\n ) -> None:\n \"\"\"Add metadata to a row if enabled.\"\"\"\n if not self.enable_metadata:\n return\n\n if success:\n row[\"metadata\"] = {\n \"has_system_message\": bool(system_msg),\n \"input_length\": len(row[\"text_input\"]),\n \"response_length\": len(row[\"model_response\"]),\n \"processing_status\": \"success\",\n }\n else:\n row[\"metadata\"] = {\n \"error\": error,\n \"processing_status\": \"failed\",\n }\n\n async def run_batch(self) -> DataFrame:\n \"\"\"Process each row in df[column_name] with the language model asynchronously.\n\n Returns:\n DataFrame: A new DataFrame containing:\n - text_input: The original input text\n - model_response: The model's response\n - batch_index: The processing order\n - metadata: Additional processing information\n\n Raises:\n ValueError: If the specified column is not found in the DataFrame\n TypeError: If the model is not compatible or input types are wrong\n \"\"\"\n model: Runnable = self.model\n system_msg = self.system_message or \"\"\n df: DataFrame = self.df\n col_name = self.column_name or \"text\"\n\n # Validate inputs first\n if not isinstance(df, DataFrame):\n msg = f\"Expected DataFrame input, got {type(df)}\"\n raise TypeError(msg)\n\n if col_name not in df.columns:\n msg = f\"Column '{col_name}' not found in the DataFrame. Available columns: {', '.join(df.columns)}\"\n raise ValueError(msg)\n\n try:\n # Convert the specified column to a list of strings\n user_texts = df[col_name].astype(str).tolist()\n total_rows = len(user_texts)\n\n logger.info(f\"Processing {total_rows} rows with batch run\")\n\n # Prepare the batch of conversations\n conversations = [\n [{\"role\": \"system\", \"content\": system_msg}, {\"role\": \"user\", \"content\": text}]\n if system_msg\n else [{\"role\": \"user\", \"content\": text}]\n for text in user_texts\n ]\n\n # Configure the model with project info and callbacks\n model = model.with_config(\n {\n \"run_name\": self.display_name,\n \"project_name\": self.get_project_name(),\n \"callbacks\": self.get_langchain_callbacks(),\n }\n )\n\n # Process batches and track progress\n responses_with_idx = [\n (idx, response)\n for idx, response in zip(\n range(len(conversations)), await model.abatch(list(conversations)), strict=True\n )\n ]\n\n # Sort by index to maintain order\n responses_with_idx.sort(key=lambda x: x[0])\n\n # Build the final data with enhanced metadata\n rows: list[dict[str, Any]] = []\n for idx, response in responses_with_idx:\n resp_text = response.content if hasattr(response, \"content\") else str(response)\n row = self._create_base_row(\n text_input=user_texts[idx],\n model_response=resp_text,\n batch_index=idx,\n )\n self._add_metadata(row, success=True, system_msg=system_msg)\n rows.append(row)\n\n # Log progress\n if (idx + 1) % max(1, total_rows // 10) == 0:\n logger.info(f\"Processed {idx + 1}/{total_rows} rows\")\n\n logger.info(\"Batch processing completed successfully\")\n return DataFrame(rows)\n\n except (KeyError, AttributeError) as e:\n # Handle data structure and attribute access errors\n logger.error(f\"Data processing error: {e!s}\")\n error_row = self._create_base_row()\n self._add_metadata(error_row, success=False, error=str(e))\n return DataFrame([error_row])\n" }, "column_name": { "_input_type": "StrInput", - "advanced": false, + "advanced": true, "display_name": "Column Name", "dynamic": false, "info": "The name of the DataFrame column to treat as text messages. Default='text'.", @@ -388,7 +388,7 @@ "load_from_db": false, "name": "column_name", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "tool_mode": false, @@ -409,7 +409,7 @@ "list_add_label": "Add More", "name": "df", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "tool_mode": false, @@ -418,6 +418,24 @@ "type": "other", "value": "" }, + "enable_metadata": { + "_input_type": "BoolInput", + "advanced": true, + "display_name": "Enable Metadata", + "dynamic": false, + "info": "If True, add metadata to the output DataFrame.", + "list": false, + "list_add_label": "Add More", + "name": "enable_metadata", + "placeholder": "", + "required": false, + "show": true, + "title_case": false, + "tool_mode": false, + "trace_as_metadata": true, + "type": "bool", + "value": true + }, "model": { "_input_type": "HandleInput", "advanced": false, @@ -431,7 +449,7 @@ "list_add_label": "Add More", "name": "model", "placeholder": "", - "required": false, + "required": true, "show": true, "title_case": false, "trace_as_metadata": true, diff --git a/src/backend/base/langflow/services/database/service.py b/src/backend/base/langflow/services/database/service.py index 32140c3618..2facacf7a4 100644 --- a/src/backend/base/langflow/services/database/service.py +++ b/src/backend/base/langflow/services/database/service.py @@ -124,6 +124,15 @@ class DatabaseService(Service): # if the user specifies an empty dict, we allow it. kwargs = self._build_connection_kwargs() + poolclass_key = kwargs.get("poolclass") + if poolclass_key is not None: + pool_class = getattr(sa, poolclass_key, None) + if pool_class and isinstance(pool_class(), sa.pool.Pool): + logger.debug(f"Using poolclass: {poolclass_key}.") + kwargs["poolclass"] = pool_class + else: + logger.error(f"Invalid poolclass '{poolclass_key}' specified. Using default pool class.") + return create_async_engine( self.database_url, connect_args=self._get_connect_args(), @@ -136,16 +145,18 @@ class DatabaseService(Service): return self._create_engine() def _get_connect_args(self): - if self.settings_service.settings.database_url and self.settings_service.settings.database_url.startswith( - "sqlite" - ): - connect_args = { + settings = self.settings_service.settings + + if settings.db_driver_connection_settings is not None: + return settings.db_driver_connection_settings + + if settings.database_url and settings.database_url.startswith("sqlite"): + return { "check_same_thread": False, - "timeout": self.settings_service.settings.db_connect_timeout, + "timeout": settings.db_connect_timeout, } - else: - connect_args = {} - return connect_args + + return {} def on_connection(self, dbapi_connection, _connection_record) -> None: if isinstance(dbapi_connection, sqlite3.Connection | dialect_sqlite.aiosqlite.AsyncAdapt_aiosqlite_connection): diff --git a/src/backend/base/langflow/services/settings/base.py b/src/backend/base/langflow/services/settings/base.py index bb023775f1..e716b3cf61 100644 --- a/src/backend/base/langflow/services/settings/base.py +++ b/src/backend/base/langflow/services/settings/base.py @@ -90,6 +90,9 @@ class Settings(BaseSettings): sqlite_pragmas: dict | None = {"synchronous": "NORMAL", "journal_mode": "WAL"} """SQLite pragmas to use when connecting to the database.""" + db_driver_connection_settings: dict | None = None + """Database driver connection settings.""" + db_connection_settings: dict | None = { "pool_size": 20, # Match the pool_size above "max_overflow": 30, # Match the max_overflow above From b8346a0afa2e095f3634ef29d3c1130fb5b9f821 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Tue, 18 Feb 2025 16:51:38 -0300 Subject: [PATCH 2/9] Refactor: Remove unused imports across multiple frontend components (#6232) * refactor: Remove unused imports across multiple frontend components * update outdated components --------- Co-authored-by: cristhianzl --- .../starter_projects/Youtube Analysis.json | 283 +++++++++--------- .../components/NodeOutputParameter/index.tsx | 2 +- .../CustomNodes/hooks/use-icons-status.tsx | 3 - .../common/storeCardComponent/index.tsx | 1 - .../common/timeoutErrorComponent/index.tsx | 1 - .../components/AccountMenu/index.tsx | 1 - .../components/dragCardComponent/index.tsx | 1 - .../cardComponent/hooks/use-on-drag-start.tsx | 1 - .../components/core/cardComponent/index.tsx | 1 - .../ChatCodeTabComponent.tsx | 1 - .../components/inputListComponent/index.tsx | 3 +- .../components/keypairListComponent/index.tsx | 2 +- .../components/multiselectComponent/index.tsx | 1 - src/frontend/src/components/ui/disclosure.tsx | 1 - .../src/components/ui/textAnimation.tsx | 2 +- src/frontend/src/controllers/API/index.ts | 2 - .../queries/auth/use-patch-reset-password.ts | 6 +- .../API/queries/auth/use-post-login-user.ts | 2 +- .../API/queries/files/use-download-files.ts | 7 +- .../use-get-mutation-global-variables.ts | 2 +- .../variables/use-patch-global-variables.ts | 1 - .../src/hooks/flows/use-upload-flow.ts | 1 - src/frontend/src/icons/ArXiv/ArXivIcon.jsx | 2 - .../src/icons/Upstash/UpstashIcon.jsx | 2 - src/frontend/src/icons/Youtube/youtube.jsx | 2 - .../chatView/chatInput/chat-input.tsx | 2 +- .../fileComponent/components/file-preview.tsx | 1 - .../src/modals/dictAreaModal/index.tsx | 1 - .../hooks/use-redirect-flow-card-click.tsx | 1 - .../components/undrawCards/index.tsx | 1 - .../extraSidebarComponent/index.tsx | 5 +- .../components/emptySearchComponent/index.tsx | 1 - .../featureTogglesComponent/index.tsx | 1 - .../components/sidebarFooterButtons/index.tsx | 1 - .../helpers/filtered-data.ts | 1 - .../helpers/traditional-search-metadata.ts | 1 - .../components/toolbar-modals.tsx | 2 +- .../toolbarSelectItem/index.tsx | 1 - .../myCollectionComponent/index.tsx | 2 +- .../SettingsPage/pages/ApiKeysPage/index.tsx | 2 +- src/frontend/src/pages/ViewPage/index.tsx | 2 - src/frontend/src/types/utils/functions.ts | 2 - .../src/types/zustand/folders/index.ts | 1 - .../src/types/zustand/messages/index.ts | 1 - .../tests/core/features/freeze-path.spec.ts | 1 - .../tests/core/unit/floatComponent.spec.ts | 1 - .../extended/features/edit-flow-name.spec.ts | 2 - .../features/langflowShortcuts.spec.ts | 2 +- .../tests/extended/features/lock-flow.spec.ts | 2 +- .../chatInputOutputUser-shard-2.spec.ts | 2 +- 50 files changed, 161 insertions(+), 209 deletions(-) diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json b/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json index e72552f6f2..52d5e0746d 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Youtube Analysis.json @@ -7,7 +7,7 @@ "data": { "sourceHandle": { "dataType": "YouTubeCommentsComponent", - "id": "YouTubeCommentsComponent-10bJT", + "id": "YouTubeCommentsComponent-5DgSV", "name": "comments", "output_types": [ "DataFrame" @@ -15,19 +15,19 @@ }, "targetHandle": { "fieldName": "df", - "id": "BatchRunComponent-Y6Aec", + "id": "BatchRunComponent-s2QTv", "inputTypes": [ "DataFrame" ], "type": "other" } }, - "id": "reactflow__edge-YouTubeCommentsComponent-10bJT{œdataTypeœ:œYouTubeCommentsComponentœ,œidœ:œYouTubeCommentsComponent-10bJTœ,œnameœ:œcommentsœ,œoutput_typesœ:[œDataFrameœ]}-BatchRunComponent-Y6Aec{œfieldNameœ:œdfœ,œidœ:œBatchRunComponent-Y6Aecœ,œinputTypesœ:[œDataFrameœ],œtypeœ:œotherœ}", + "id": "reactflow__edge-YouTubeCommentsComponent-5DgSV{œdataTypeœ:œYouTubeCommentsComponentœ,œidœ:œYouTubeCommentsComponent-5DgSVœ,œnameœ:œcommentsœ,œoutput_typesœ:[œDataFrameœ]}-BatchRunComponent-s2QTv{œfieldNameœ:œdfœ,œidœ:œBatchRunComponent-s2QTvœ,œinputTypesœ:[œDataFrameœ],œtypeœ:œotherœ}", "selected": false, - "source": "YouTubeCommentsComponent-10bJT", - "sourceHandle": "{œdataTypeœ: œYouTubeCommentsComponentœ, œidœ: œYouTubeCommentsComponent-10bJTœ, œnameœ: œcommentsœ, œoutput_typesœ: [œDataFrameœ]}", - "target": "BatchRunComponent-Y6Aec", - "targetHandle": "{œfieldNameœ: œdfœ, œidœ: œBatchRunComponent-Y6Aecœ, œinputTypesœ: [œDataFrameœ], œtypeœ: œotherœ}" + "source": "YouTubeCommentsComponent-5DgSV", + "sourceHandle": "{œdataTypeœ: œYouTubeCommentsComponentœ, œidœ: œYouTubeCommentsComponent-5DgSVœ, œnameœ: œcommentsœ, œoutput_typesœ: [œDataFrameœ]}", + "target": "BatchRunComponent-s2QTv", + "targetHandle": "{œfieldNameœ: œdfœ, œidœ: œBatchRunComponent-s2QTvœ, œinputTypesœ: [œDataFrameœ], œtypeœ: œotherœ}" }, { "animated": false, @@ -35,7 +35,7 @@ "data": { "sourceHandle": { "dataType": "OpenAIModel", - "id": "OpenAIModel-QgyC5", + "id": "OpenAIModel-ZVATe", "name": "model_output", "output_types": [ "LanguageModel" @@ -43,19 +43,19 @@ }, "targetHandle": { "fieldName": "model", - "id": "BatchRunComponent-Y6Aec", + "id": "BatchRunComponent-s2QTv", "inputTypes": [ "LanguageModel" ], "type": "other" } }, - "id": "reactflow__edge-OpenAIModel-QgyC5{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-QgyC5œ,œnameœ:œmodel_outputœ,œoutput_typesœ:[œLanguageModelœ]}-BatchRunComponent-Y6Aec{œfieldNameœ:œmodelœ,œidœ:œBatchRunComponent-Y6Aecœ,œinputTypesœ:[œLanguageModelœ],œtypeœ:œotherœ}", + "id": "reactflow__edge-OpenAIModel-ZVATe{œdataTypeœ:œOpenAIModelœ,œidœ:œOpenAIModel-ZVATeœ,œnameœ:œmodel_outputœ,œoutput_typesœ:[œLanguageModelœ]}-BatchRunComponent-s2QTv{œfieldNameœ:œmodelœ,œidœ:œBatchRunComponent-s2QTvœ,œinputTypesœ:[œLanguageModelœ],œtypeœ:œotherœ}", "selected": false, - "source": "OpenAIModel-QgyC5", - "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-QgyC5œ, œnameœ: œmodel_outputœ, œoutput_typesœ: [œLanguageModelœ]}", - "target": "BatchRunComponent-Y6Aec", - "targetHandle": "{œfieldNameœ: œmodelœ, œidœ: œBatchRunComponent-Y6Aecœ, œinputTypesœ: [œLanguageModelœ], œtypeœ: œotherœ}" + "source": "OpenAIModel-ZVATe", + "sourceHandle": "{œdataTypeœ: œOpenAIModelœ, œidœ: œOpenAIModel-ZVATeœ, œnameœ: œmodel_outputœ, œoutput_typesœ: [œLanguageModelœ]}", + "target": "BatchRunComponent-s2QTv", + "targetHandle": "{œfieldNameœ: œmodelœ, œidœ: œBatchRunComponent-s2QTvœ, œinputTypesœ: [œLanguageModelœ], œtypeœ: œotherœ}" }, { "animated": false, @@ -63,7 +63,7 @@ "data": { "sourceHandle": { "dataType": "BatchRunComponent", - "id": "BatchRunComponent-Y6Aec", + "id": "BatchRunComponent-s2QTv", "name": "batch_results", "output_types": [ "DataFrame" @@ -71,19 +71,19 @@ }, "targetHandle": { "fieldName": "df", - "id": "ParseDataFrame-Ni6HW", + "id": "ParseDataFrame-pJJ7Z", "inputTypes": [ "DataFrame" ], "type": "other" } }, - "id": "reactflow__edge-BatchRunComponent-Y6Aec{œdataTypeœ:œBatchRunComponentœ,œidœ:œBatchRunComponent-Y6Aecœ,œnameœ:œbatch_resultsœ,œoutput_typesœ:[œDataFrameœ]}-ParseDataFrame-Ni6HW{œfieldNameœ:œdfœ,œidœ:œParseDataFrame-Ni6HWœ,œinputTypesœ:[œDataFrameœ],œtypeœ:œotherœ}", + "id": "reactflow__edge-BatchRunComponent-s2QTv{œdataTypeœ:œBatchRunComponentœ,œidœ:œBatchRunComponent-s2QTvœ,œnameœ:œbatch_resultsœ,œoutput_typesœ:[œDataFrameœ]}-ParseDataFrame-pJJ7Z{œfieldNameœ:œdfœ,œidœ:œParseDataFrame-pJJ7Zœ,œinputTypesœ:[œDataFrameœ],œtypeœ:œotherœ}", "selected": false, - "source": "BatchRunComponent-Y6Aec", - "sourceHandle": "{œdataTypeœ: œBatchRunComponentœ, œidœ: œBatchRunComponent-Y6Aecœ, œnameœ: œbatch_resultsœ, œoutput_typesœ: [œDataFrameœ]}", - "target": "ParseDataFrame-Ni6HW", - "targetHandle": "{œfieldNameœ: œdfœ, œidœ: œParseDataFrame-Ni6HWœ, œinputTypesœ: [œDataFrameœ], œtypeœ: œotherœ}" + "source": "BatchRunComponent-s2QTv", + "sourceHandle": "{œdataTypeœ: œBatchRunComponentœ, œidœ: œBatchRunComponent-s2QTvœ, œnameœ: œbatch_resultsœ, œoutput_typesœ: [œDataFrameœ]}", + "target": "ParseDataFrame-pJJ7Z", + "targetHandle": "{œfieldNameœ: œdfœ, œidœ: œParseDataFrame-pJJ7Zœ, œinputTypesœ: [œDataFrameœ], œtypeœ: œotherœ}" }, { "animated": false, @@ -91,7 +91,7 @@ "data": { "sourceHandle": { "dataType": "ParseDataFrame", - "id": "ParseDataFrame-Ni6HW", + "id": "ParseDataFrame-pJJ7Z", "name": "text", "output_types": [ "Message" @@ -99,19 +99,19 @@ }, "targetHandle": { "fieldName": "analysis", - "id": "Prompt-ozK70", + "id": "Prompt-Kn7x9", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "reactflow__edge-ParseDataFrame-Ni6HW{œdataTypeœ:œParseDataFrameœ,œidœ:œParseDataFrame-Ni6HWœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-ozK70{œfieldNameœ:œanalysisœ,œidœ:œPrompt-ozK70œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "id": "reactflow__edge-ParseDataFrame-pJJ7Z{œdataTypeœ:œParseDataFrameœ,œidœ:œParseDataFrame-pJJ7Zœ,œnameœ:œtextœ,œoutput_typesœ:[œMessageœ]}-Prompt-Kn7x9{œfieldNameœ:œanalysisœ,œidœ:œPrompt-Kn7x9œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", "selected": false, - "source": "ParseDataFrame-Ni6HW", - "sourceHandle": "{œdataTypeœ: œParseDataFrameœ, œidœ: œParseDataFrame-Ni6HWœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", - "target": "Prompt-ozK70", - "targetHandle": "{œfieldNameœ: œanalysisœ, œidœ: œPrompt-ozK70œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "source": "ParseDataFrame-pJJ7Z", + "sourceHandle": "{œdataTypeœ: œParseDataFrameœ, œidœ: œParseDataFrame-pJJ7Zœ, œnameœ: œtextœ, œoutput_typesœ: [œMessageœ]}", + "target": "Prompt-Kn7x9", + "targetHandle": "{œfieldNameœ: œanalysisœ, œidœ: œPrompt-Kn7x9œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -119,7 +119,7 @@ "data": { "sourceHandle": { "dataType": "Prompt", - "id": "Prompt-ozK70", + "id": "Prompt-Kn7x9", "name": "prompt", "output_types": [ "Message" @@ -127,19 +127,19 @@ }, "targetHandle": { "fieldName": "input_value", - "id": "Agent-U1bxR", + "id": "Agent-Px7Zt", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "reactflow__edge-Prompt-ozK70{œdataTypeœ:œPromptœ,œidœ:œPrompt-ozK70œ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-Agent-U1bxR{œfieldNameœ:œinput_valueœ,œidœ:œAgent-U1bxRœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "id": "reactflow__edge-Prompt-Kn7x9{œdataTypeœ:œPromptœ,œidœ:œPrompt-Kn7x9œ,œnameœ:œpromptœ,œoutput_typesœ:[œMessageœ]}-Agent-Px7Zt{œfieldNameœ:œinput_valueœ,œidœ:œAgent-Px7Ztœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", "selected": false, - "source": "Prompt-ozK70", - "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-ozK70œ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}", - "target": "Agent-U1bxR", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAgent-U1bxRœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "source": "Prompt-Kn7x9", + "sourceHandle": "{œdataTypeœ: œPromptœ, œidœ: œPrompt-Kn7x9œ, œnameœ: œpromptœ, œoutput_typesœ: [œMessageœ]}", + "target": "Agent-Px7Zt", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œAgent-Px7Ztœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -147,7 +147,7 @@ "data": { "sourceHandle": { "dataType": "Agent", - "id": "Agent-U1bxR", + "id": "Agent-Px7Zt", "name": "response", "output_types": [ "Message" @@ -155,19 +155,19 @@ }, "targetHandle": { "fieldName": "input_value", - "id": "ChatOutput-BILzx", + "id": "ChatOutput-W5R97", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "reactflow__edge-Agent-U1bxR{œdataTypeœ:œAgentœ,œidœ:œAgent-U1bxRœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-BILzx{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-BILzxœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "id": "reactflow__edge-Agent-Px7Zt{œdataTypeœ:œAgentœ,œidœ:œAgent-Px7Ztœ,œnameœ:œresponseœ,œoutput_typesœ:[œMessageœ]}-ChatOutput-W5R97{œfieldNameœ:œinput_valueœ,œidœ:œChatOutput-W5R97œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", "selected": false, - "source": "Agent-U1bxR", - "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-U1bxRœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", - "target": "ChatOutput-BILzx", - "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-BILzxœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "source": "Agent-Px7Zt", + "sourceHandle": "{œdataTypeœ: œAgentœ, œidœ: œAgent-Px7Ztœ, œnameœ: œresponseœ, œoutput_typesœ: [œMessageœ]}", + "target": "ChatOutput-W5R97", + "targetHandle": "{œfieldNameœ: œinput_valueœ, œidœ: œChatOutput-W5R97œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -175,7 +175,7 @@ "data": { "sourceHandle": { "dataType": "YouTubeTranscripts", - "id": "YouTubeTranscripts-wPf1w", + "id": "YouTubeTranscripts-ppAJD", "name": "component_as_tool", "output_types": [ "Tool" @@ -183,19 +183,19 @@ }, "targetHandle": { "fieldName": "tools", - "id": "Agent-U1bxR", + "id": "Agent-Px7Zt", "inputTypes": [ "Tool" ], "type": "other" } }, - "id": "reactflow__edge-YouTubeTranscripts-wPf1w{œdataTypeœ:œYouTubeTranscriptsœ,œidœ:œYouTubeTranscripts-wPf1wœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-U1bxR{œfieldNameœ:œtoolsœ,œidœ:œAgent-U1bxRœ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}", + "id": "reactflow__edge-YouTubeTranscripts-ppAJD{œdataTypeœ:œYouTubeTranscriptsœ,œidœ:œYouTubeTranscripts-ppAJDœ,œnameœ:œcomponent_as_toolœ,œoutput_typesœ:[œToolœ]}-Agent-Px7Zt{œfieldNameœ:œtoolsœ,œidœ:œAgent-Px7Ztœ,œinputTypesœ:[œToolœ],œtypeœ:œotherœ}", "selected": false, - "source": "YouTubeTranscripts-wPf1w", - "sourceHandle": "{œdataTypeœ: œYouTubeTranscriptsœ, œidœ: œYouTubeTranscripts-wPf1wœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}", - "target": "Agent-U1bxR", - "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-U1bxRœ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}" + "source": "YouTubeTranscripts-ppAJD", + "sourceHandle": "{œdataTypeœ: œYouTubeTranscriptsœ, œidœ: œYouTubeTranscripts-ppAJDœ, œnameœ: œcomponent_as_toolœ, œoutput_typesœ: [œToolœ]}", + "target": "Agent-Px7Zt", + "targetHandle": "{œfieldNameœ: œtoolsœ, œidœ: œAgent-Px7Ztœ, œinputTypesœ: [œToolœ], œtypeœ: œotherœ}" }, { "animated": false, @@ -203,7 +203,7 @@ "data": { "sourceHandle": { "dataType": "ChatInput", - "id": "ChatInput-VrWjf", + "id": "ChatInput-Aprv5", "name": "message", "output_types": [ "Message" @@ -211,19 +211,19 @@ }, "targetHandle": { "fieldName": "input_text", - "id": "ConditionalRouter-CfANV", + "id": "ConditionalRouter-NmX80", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "xy-edge__ChatInput-VrWjf{œdataTypeœ:œChatInputœ,œidœ:œChatInput-VrWjfœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-ConditionalRouter-CfANV{œfieldNameœ:œinput_textœ,œidœ:œConditionalRouter-CfANVœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "id": "reactflow__edge-ChatInput-Aprv5{œdataTypeœ:œChatInputœ,œidœ:œChatInput-Aprv5œ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-ConditionalRouter-NmX80{œfieldNameœ:œinput_textœ,œidœ:œConditionalRouter-NmX80œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", "selected": false, - "source": "ChatInput-VrWjf", - "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-VrWjfœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", - "target": "ConditionalRouter-CfANV", - "targetHandle": "{œfieldNameœ: œinput_textœ, œidœ: œConditionalRouter-CfANVœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "source": "ChatInput-Aprv5", + "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-Aprv5œ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", + "target": "ConditionalRouter-NmX80", + "targetHandle": "{œfieldNameœ: œinput_textœ, œidœ: œConditionalRouter-NmX80œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -231,7 +231,7 @@ "data": { "sourceHandle": { "dataType": "ChatInput", - "id": "ChatInput-VrWjf", + "id": "ChatInput-Aprv5", "name": "message", "output_types": [ "Message" @@ -239,19 +239,19 @@ }, "targetHandle": { "fieldName": "message", - "id": "ConditionalRouter-CfANV", + "id": "ConditionalRouter-NmX80", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "xy-edge__ChatInput-VrWjf{œdataTypeœ:œChatInputœ,œidœ:œChatInput-VrWjfœ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-ConditionalRouter-CfANV{œfieldNameœ:œmessageœ,œidœ:œConditionalRouter-CfANVœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "id": "reactflow__edge-ChatInput-Aprv5{œdataTypeœ:œChatInputœ,œidœ:œChatInput-Aprv5œ,œnameœ:œmessageœ,œoutput_typesœ:[œMessageœ]}-ConditionalRouter-NmX80{œfieldNameœ:œmessageœ,œidœ:œConditionalRouter-NmX80œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", "selected": false, - "source": "ChatInput-VrWjf", - "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-VrWjfœ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", - "target": "ConditionalRouter-CfANV", - "targetHandle": "{œfieldNameœ: œmessageœ, œidœ: œConditionalRouter-CfANVœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "source": "ChatInput-Aprv5", + "sourceHandle": "{œdataTypeœ: œChatInputœ, œidœ: œChatInput-Aprv5œ, œnameœ: œmessageœ, œoutput_typesœ: [œMessageœ]}", + "target": "ConditionalRouter-NmX80", + "targetHandle": "{œfieldNameœ: œmessageœ, œidœ: œConditionalRouter-NmX80œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -259,7 +259,7 @@ "data": { "sourceHandle": { "dataType": "ConditionalRouter", - "id": "ConditionalRouter-CfANV", + "id": "ConditionalRouter-NmX80", "name": "true_result", "output_types": [ "Message" @@ -267,19 +267,19 @@ }, "targetHandle": { "fieldName": "video_url", - "id": "YouTubeCommentsComponent-10bJT", + "id": "YouTubeCommentsComponent-5DgSV", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "xy-edge__ConditionalRouter-CfANV{œdataTypeœ:œConditionalRouterœ,œidœ:œConditionalRouter-CfANVœ,œnameœ:œtrue_resultœ,œoutput_typesœ:[œMessageœ]}-YouTubeCommentsComponent-10bJT{œfieldNameœ:œvideo_urlœ,œidœ:œYouTubeCommentsComponent-10bJTœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "id": "reactflow__edge-ConditionalRouter-NmX80{œdataTypeœ:œConditionalRouterœ,œidœ:œConditionalRouter-NmX80œ,œnameœ:œtrue_resultœ,œoutput_typesœ:[œMessageœ]}-YouTubeCommentsComponent-5DgSV{œfieldNameœ:œvideo_urlœ,œidœ:œYouTubeCommentsComponent-5DgSVœ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", "selected": false, - "source": "ConditionalRouter-CfANV", - "sourceHandle": "{œdataTypeœ: œConditionalRouterœ, œidœ: œConditionalRouter-CfANVœ, œnameœ: œtrue_resultœ, œoutput_typesœ: [œMessageœ]}", - "target": "YouTubeCommentsComponent-10bJT", - "targetHandle": "{œfieldNameœ: œvideo_urlœ, œidœ: œYouTubeCommentsComponent-10bJTœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "source": "ConditionalRouter-NmX80", + "sourceHandle": "{œdataTypeœ: œConditionalRouterœ, œidœ: œConditionalRouter-NmX80œ, œnameœ: œtrue_resultœ, œoutput_typesœ: [œMessageœ]}", + "target": "YouTubeCommentsComponent-5DgSV", + "targetHandle": "{œfieldNameœ: œvideo_urlœ, œidœ: œYouTubeCommentsComponent-5DgSVœ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" }, { "animated": false, @@ -287,7 +287,7 @@ "data": { "sourceHandle": { "dataType": "ConditionalRouter", - "id": "ConditionalRouter-CfANV", + "id": "ConditionalRouter-NmX80", "name": "true_result", "output_types": [ "Message" @@ -295,25 +295,25 @@ }, "targetHandle": { "fieldName": "url", - "id": "Prompt-ozK70", + "id": "Prompt-Kn7x9", "inputTypes": [ "Message" ], "type": "str" } }, - "id": "xy-edge__ConditionalRouter-CfANV{œdataTypeœ:œConditionalRouterœ,œidœ:œConditionalRouter-CfANVœ,œnameœ:œtrue_resultœ,œoutput_typesœ:[œMessageœ]}-Prompt-ozK70{œfieldNameœ:œurlœ,œidœ:œPrompt-ozK70œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", + "id": "reactflow__edge-ConditionalRouter-NmX80{œdataTypeœ:œConditionalRouterœ,œidœ:œConditionalRouter-NmX80œ,œnameœ:œtrue_resultœ,œoutput_typesœ:[œMessageœ]}-Prompt-Kn7x9{œfieldNameœ:œurlœ,œidœ:œPrompt-Kn7x9œ,œinputTypesœ:[œMessageœ],œtypeœ:œstrœ}", "selected": false, - "source": "ConditionalRouter-CfANV", - "sourceHandle": "{œdataTypeœ: œConditionalRouterœ, œidœ: œConditionalRouter-CfANVœ, œnameœ: œtrue_resultœ, œoutput_typesœ: [œMessageœ]}", - "target": "Prompt-ozK70", - "targetHandle": "{œfieldNameœ: œurlœ, œidœ: œPrompt-ozK70œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" + "source": "ConditionalRouter-NmX80", + "sourceHandle": "{œdataTypeœ: œConditionalRouterœ, œidœ: œConditionalRouter-NmX80œ, œnameœ: œtrue_resultœ, œoutput_typesœ: [œMessageœ]}", + "target": "Prompt-Kn7x9", + "targetHandle": "{œfieldNameœ: œurlœ, œidœ: œPrompt-Kn7x9œ, œinputTypesœ: [œMessageœ], œtypeœ: œstrœ}" } ], "nodes": [ { "data": { - "id": "BatchRunComponent-Y6Aec", + "id": "BatchRunComponent-s2QTv", "node": { "base_classes": [ "DataFrame" @@ -487,10 +487,10 @@ "type": "BatchRunComponent" }, "dragging": false, - "id": "BatchRunComponent-Y6Aec", + "id": "BatchRunComponent-s2QTv", "measured": { - "height": 477, - "width": 320 + "height": 446, + "width": 360 }, "position": { "x": 635.0665302273813, @@ -501,7 +501,7 @@ }, { "data": { - "id": "YouTubeCommentsComponent-10bJT", + "id": "YouTubeCommentsComponent-5DgSV", "node": { "base_classes": [ "DataFrame" @@ -566,7 +566,7 @@ "show": true, "title_case": false, "type": "str", - "value": "" + "value": "YOUTUBE_API_KEY" }, "code": { "advanced": true, @@ -693,10 +693,10 @@ "type": "YouTubeCommentsComponent" }, "dragging": false, - "id": "YouTubeCommentsComponent-10bJT", + "id": "YouTubeCommentsComponent-5DgSV", "measured": { - "height": 493, - "width": 320 + "height": 556, + "width": 360 }, "position": { "x": 191.60600144515274, @@ -707,7 +707,7 @@ }, { "data": { - "id": "OpenAIModel-QgyC5", + "id": "OpenAIModel-ZVATe", "node": { "base_classes": [ "LanguageModel", @@ -1076,10 +1076,10 @@ "type": "OpenAIModel" }, "dragging": false, - "id": "OpenAIModel-QgyC5", + "id": "OpenAIModel-ZVATe", "measured": { - "height": 651, - "width": 320 + "height": 734, + "width": 360 }, "position": { "x": 192.79820011992825, @@ -1090,7 +1090,7 @@ }, { "data": { - "id": "ParseDataFrame-Ni6HW", + "id": "ParseDataFrame-pJJ7Z", "node": { "base_classes": [ "Message" @@ -1225,10 +1225,10 @@ "type": "ParseDataFrame" }, "dragging": false, - "id": "ParseDataFrame-Ni6HW", + "id": "ParseDataFrame-pJJ7Z", "measured": { - "height": 331, - "width": 320 + "height": 352, + "width": 360 }, "position": { "x": 993.5819211529395, @@ -1239,7 +1239,7 @@ }, { "data": { - "id": "Agent-U1bxR", + "id": "Agent-Px7Zt", "node": { "base_classes": [ "Message" @@ -1889,10 +1889,10 @@ "type": "Agent" }, "dragging": false, - "id": "Agent-U1bxR", + "id": "Agent-Px7Zt", "measured": { - "height": 618, - "width": 320 + "height": 698, + "width": 360 }, "position": { "x": 1982.1085644220088, @@ -1903,7 +1903,7 @@ }, { "data": { - "id": "Prompt-ozK70", + "id": "Prompt-Kn7x9", "node": { "base_classes": [ "Message" @@ -2065,10 +2065,10 @@ "type": "Prompt" }, "dragging": false, - "id": "Prompt-ozK70", + "id": "Prompt-Kn7x9", "measured": { - "height": 417, - "width": 320 + "height": 448, + "width": 360 }, "position": { "x": 1575.3649919098807, @@ -2079,7 +2079,7 @@ }, { "data": { - "id": "ChatOutput-BILzx", + "id": "ChatOutput-W5R97", "node": { "base_classes": [ "Message" @@ -2357,10 +2357,10 @@ "type": "ChatOutput" }, "dragging": false, - "id": "ChatOutput-BILzx", + "id": "ChatOutput-W5R97", "measured": { - "height": 228, - "width": 320 + "height": 257, + "width": 360 }, "position": { "x": 2365.8487393880428, @@ -2371,17 +2371,17 @@ }, { "data": { - "id": "YouTubeTranscripts-wPf1w", + "id": "YouTubeTranscripts-ppAJD", "node": { "base_classes": [ + "Data", "DataFrame", "Message" ], "beta": false, - "category": "youtube", "conditional_paths": [], "custom_fields": {}, - "description": "Extracts spoken content from YouTube videos with both DataFrame and text output options.", + "description": "Extracts spoken content from YouTube videos with multiple output options.", "display_name": "YouTube Transcripts", "documentation": "", "edited": false, @@ -2392,9 +2392,7 @@ ], "frozen": false, "icon": "YouTube", - "key": "YouTubeTranscripts", "legacy": false, - "lf_version": "1.1.3", "metadata": {}, "minimized": false, "output_types": [], @@ -2416,7 +2414,6 @@ } ], "pinned": false, - "score": 7.568328950209746e-6, "template": { "_type": "Component", "chunk_size_seconds": { @@ -2453,7 +2450,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import pandas as pd\nimport youtube_transcript_api\nfrom langchain_community.document_loaders import YoutubeLoader\nfrom langchain_community.document_loaders.youtube import TranscriptFormat\n\nfrom langflow.custom import Component\nfrom langflow.inputs import DropdownInput, IntInput, MultilineInput\nfrom langflow.schema import DataFrame, Message\nfrom langflow.template import Output\n\n\nclass YouTubeTranscriptsComponent(Component):\n \"\"\"A component that extracts spoken content from YouTube videos as transcripts.\"\"\"\n\n display_name: str = \"YouTube Transcripts\"\n description: str = \"Extracts spoken content from YouTube videos with both DataFrame and text output options.\"\n icon: str = \"YouTube\"\n name = \"YouTubeTranscripts\"\n\n inputs = [\n MultilineInput(\n name=\"url\",\n display_name=\"Video URL\",\n info=\"Enter the YouTube video URL to get transcripts from.\",\n tool_mode=True,\n required=True,\n ),\n IntInput(\n name=\"chunk_size_seconds\",\n display_name=\"Chunk Size (seconds)\",\n value=60,\n info=\"The size of each transcript chunk in seconds.\",\n ),\n DropdownInput(\n name=\"translation\",\n display_name=\"Translation Language\",\n advanced=True,\n options=[\"\", \"en\", \"es\", \"fr\", \"de\", \"it\", \"pt\", \"ru\", \"ja\", \"ko\", \"hi\", \"ar\", \"id\"],\n info=\"Translate the transcripts to the specified language. Leave empty for no translation.\",\n ),\n ]\n\n outputs = [\n Output(name=\"dataframe\", display_name=\"Chunks\", method=\"get_dataframe_output\"),\n Output(name=\"message\", display_name=\"Transcript\", method=\"get_message_output\"),\n ]\n\n def _load_transcripts(self, *, as_chunks: bool = True):\n \"\"\"Internal method to load transcripts from YouTube.\"\"\"\n loader = YoutubeLoader.from_youtube_url(\n self.url,\n transcript_format=TranscriptFormat.CHUNKS if as_chunks else TranscriptFormat.TEXT,\n chunk_size_seconds=self.chunk_size_seconds,\n translation=self.translation or None,\n )\n return loader.load()\n\n def get_dataframe_output(self) -> DataFrame:\n \"\"\"Provides transcript output as a DataFrame with timestamp and text columns.\"\"\"\n try:\n transcripts = self._load_transcripts(as_chunks=True)\n\n # Create DataFrame with timestamp and text columns\n data = []\n for doc in transcripts:\n start_seconds = int(doc.metadata[\"start_seconds\"])\n start_minutes = start_seconds // 60\n start_seconds %= 60\n timestamp = f\"{start_minutes:02d}:{start_seconds:02d}\"\n data.append({\"timestamp\": timestamp, \"text\": doc.page_content})\n return DataFrame(pd.DataFrame(data))\n\n except (youtube_transcript_api.TranscriptsDisabled, youtube_transcript_api.NoTranscriptFound) as exc:\n return DataFrame(pd.DataFrame({\"error\": [f\"Failed to get YouTube transcripts: {exc!s}\"]}))\n\n def get_message_output(self) -> Message:\n \"\"\"Provides transcript output as continuous text.\"\"\"\n try:\n transcripts = self._load_transcripts(as_chunks=False)\n result = transcripts[0].page_content\n return Message(text=result)\n\n except (youtube_transcript_api.TranscriptsDisabled, youtube_transcript_api.NoTranscriptFound) as exc:\n error_msg = f\"Failed to get YouTube transcripts: {exc!s}\"\n return Message(text=error_msg)\n" + "value": "import pandas as pd\nimport youtube_transcript_api\nfrom langchain_community.document_loaders import YoutubeLoader\nfrom langchain_community.document_loaders.youtube import TranscriptFormat\n\nfrom langflow.custom import Component\nfrom langflow.inputs import DropdownInput, IntInput, MultilineInput\nfrom langflow.schema import Data, DataFrame, Message\nfrom langflow.template import Output\n\n\nclass YouTubeTranscriptsComponent(Component):\n \"\"\"A component that extracts spoken content from YouTube videos as transcripts.\"\"\"\n\n display_name: str = \"YouTube Transcripts\"\n description: str = \"Extracts spoken content from YouTube videos with multiple output options.\"\n icon: str = \"YouTube\"\n name = \"YouTubeTranscripts\"\n\n inputs = [\n MultilineInput(\n name=\"url\",\n display_name=\"Video URL\",\n info=\"Enter the YouTube video URL to get transcripts from.\",\n tool_mode=True,\n required=True,\n ),\n IntInput(\n name=\"chunk_size_seconds\",\n display_name=\"Chunk Size (seconds)\",\n value=60,\n info=\"The size of each transcript chunk in seconds.\",\n ),\n DropdownInput(\n name=\"translation\",\n display_name=\"Translation Language\",\n advanced=True,\n options=[\"\", \"en\", \"es\", \"fr\", \"de\", \"it\", \"pt\", \"ru\", \"ja\", \"ko\", \"hi\", \"ar\", \"id\"],\n info=\"Translate the transcripts to the specified language. Leave empty for no translation.\",\n ),\n ]\n\n outputs = [\n Output(name=\"dataframe\", display_name=\"Chunks\", method=\"get_dataframe_output\"),\n Output(name=\"message\", display_name=\"Transcript\", method=\"get_message_output\"),\n Output(name=\"data_output\", display_name=\"Transcript + Source\", method=\"get_data_output\"),\n ]\n\n def _load_transcripts(self, *, as_chunks: bool = True):\n \"\"\"Internal method to load transcripts from YouTube.\"\"\"\n loader = YoutubeLoader.from_youtube_url(\n self.url,\n transcript_format=TranscriptFormat.CHUNKS if as_chunks else TranscriptFormat.TEXT,\n chunk_size_seconds=self.chunk_size_seconds,\n translation=self.translation or None,\n )\n return loader.load()\n\n def get_dataframe_output(self) -> DataFrame:\n \"\"\"Provides transcript output as a DataFrame with timestamp and text columns.\"\"\"\n try:\n transcripts = self._load_transcripts(as_chunks=True)\n\n # Create DataFrame with timestamp and text columns\n data = []\n for doc in transcripts:\n start_seconds = int(doc.metadata[\"start_seconds\"])\n start_minutes = start_seconds // 60\n start_seconds %= 60\n timestamp = f\"{start_minutes:02d}:{start_seconds:02d}\"\n data.append({\"timestamp\": timestamp, \"text\": doc.page_content})\n\n return DataFrame(pd.DataFrame(data))\n\n except (youtube_transcript_api.TranscriptsDisabled, youtube_transcript_api.NoTranscriptFound) as exc:\n return DataFrame(pd.DataFrame({\"error\": [f\"Failed to get YouTube transcripts: {exc!s}\"]}))\n\n def get_message_output(self) -> Message:\n \"\"\"Provides transcript output as continuous text.\"\"\"\n try:\n transcripts = self._load_transcripts(as_chunks=False)\n result = transcripts[0].page_content\n return Message(text=result)\n\n except (youtube_transcript_api.TranscriptsDisabled, youtube_transcript_api.NoTranscriptFound) as exc:\n error_msg = f\"Failed to get YouTube transcripts: {exc!s}\"\n return Message(text=error_msg)\n\n def get_data_output(self) -> Data:\n \"\"\"Creates a structured data object with transcript and metadata.\n\n Returns a Data object containing transcript text, video URL, and any error\n messages that occurred during processing. The object includes:\n - 'transcript': continuous text from the entire video (concatenated if multiple parts)\n - 'video_url': the input YouTube URL\n - 'error': error message if an exception occurs\n \"\"\"\n default_data = {\"transcript\": \"\", \"video_url\": self.url, \"error\": None}\n\n try:\n transcripts = self._load_transcripts(as_chunks=False)\n if not transcripts:\n default_data[\"error\"] = \"No transcripts found.\"\n return Data(data=default_data)\n\n # Combine all transcript parts\n full_transcript = \" \".join(doc.page_content for doc in transcripts)\n return Data(data={\"transcript\": full_transcript, \"video_url\": self.url})\n\n except (\n youtube_transcript_api.TranscriptsDisabled,\n youtube_transcript_api.NoTranscriptFound,\n youtube_transcript_api.CouldNotRetrieveTranscript,\n ) as exc:\n default_data[\"error\"] = str(exc)\n return Data(data=default_data)\n" }, "tools_metadata": { "_input_type": "TableInput", @@ -2496,6 +2493,7 @@ "edit_mode": "inline", "filterable": false, "formatter": "text", + "hidden": false, "name": "name", "sortable": false, "type": "text" @@ -2507,6 +2505,7 @@ "edit_mode": "popover", "filterable": false, "formatter": "text", + "hidden": false, "name": "description", "sortable": false, "type": "text" @@ -2518,6 +2517,7 @@ "edit_mode": "inline", "filterable": false, "formatter": "text", + "hidden": true, "name": "tags", "sortable": false, "type": "text" @@ -2532,18 +2532,25 @@ "type": "table", "value": [ { - "description": "get_dataframe_output(url: Message) - Extracts spoken content from YouTube videos with both DataFrame and text output options.", + "description": "get_dataframe_output(url: Message) - Extracts spoken content from YouTube videos with multiple output options.", "name": "YouTubeTranscripts-get_dataframe_output", "tags": [ "YouTubeTranscripts-get_dataframe_output" ] }, { - "description": "get_message_output(url: Message) - Extracts spoken content from YouTube videos with both DataFrame and text output options.", + "description": "get_message_output(url: Message) - Extracts spoken content from YouTube videos with multiple output options.", "name": "YouTubeTranscripts-get_message_output", "tags": [ "YouTubeTranscripts-get_message_output" ] + }, + { + "description": "get_data_output(url: Message) - Extracts spoken content from YouTube videos with multiple output options.", + "name": "YouTubeTranscripts-get_data_output", + "tags": [ + "YouTubeTranscripts-get_data_output" + ] } ] }, @@ -2612,10 +2619,10 @@ "type": "YouTubeTranscripts" }, "dragging": false, - "id": "YouTubeTranscripts-wPf1w", + "id": "YouTubeTranscripts-ppAJD", "measured": { - "height": 413, - "width": 320 + "height": 466, + "width": 360 }, "position": { "x": 1577.7800211610804, @@ -2626,7 +2633,7 @@ }, { "data": { - "id": "ChatInput-VrWjf", + "id": "ChatInput-Aprv5", "node": { "base_classes": [ "Message" @@ -2923,10 +2930,10 @@ "type": "ChatInput" }, "dragging": false, - "id": "ChatInput-VrWjf", + "id": "ChatInput-Aprv5", "measured": { - "height": 227, - "width": 320 + "height": 257, + "width": 360 }, "position": { "x": -894.6710448870117, @@ -2937,7 +2944,7 @@ }, { "data": { - "id": "note-n9LmZ", + "id": "note-fVy2q", "node": { "description": "# Batch Run component\n\nThis component processes a DataFrame by running each row through a Language Model (LLM). Perfect for batch analysis, sentiment scoring, or content generation at scale.\n\n## How It Works\n1. Accepts a DataFrame with text data.\n2. Routes each row through your chosen LLM.\n3. Returns new DataFrame with `text_input` and `model_response`.\n\n", "display_name": "", @@ -2950,10 +2957,10 @@ }, "dragging": false, "height": 522, - "id": "note-n9LmZ", + "id": "note-fVy2q", "measured": { "height": 522, - "width": 325 + "width": 328 }, "position": { "x": 631.7137680312561, @@ -2966,7 +2973,7 @@ }, { "data": { - "id": "note-piUDG", + "id": "note-ib6pc", "node": { "description": "## Set up the YouTube API\n1. Go to [Google Cloud Console](https://console.cloud.google.com).\n2. Create a new project or select existing one.\n3. Enable YouTube Data API v3:\n - Navigate to APIs & Services > Library.\n - Search \"YouTube Data API v3\".\n - Click Enable.\n4. Create credentials:\n - Go to APIs & Services > Credentials.\n - Click Create Credentials > API Key.\n5. Copy your new API key for use in the component.\n\n⚠️ Remember to:\n- Restrict the API key to YouTube Data API v3 only.\n- Set appropriate quotas and restrictions.\n", "display_name": "", @@ -2979,10 +2986,10 @@ }, "dragging": false, "height": 486, - "id": "note-piUDG", + "id": "note-ib6pc", "measured": { "height": 486, - "width": 325 + "width": 328 }, "position": { "x": 1579.119903578572, @@ -2995,7 +3002,7 @@ }, { "data": { - "id": "note-qq8Uo", + "id": "note-dhEu8", "node": { "description": "# 🎥 YouTube Video Analysis\nThis flow performs comprehensive analysis of YouTube videos.\n1. Extract video comments and transcripts.\n2. Run sentiment analysis on comments using LLM.\n3. Combine transcript content and comment sentiment for comprehensive video analysis.\n\n## Prerequisites\n- OpenAI API Key\n- YouTube Data API v3 key", "display_name": "", @@ -3008,10 +3015,10 @@ }, "dragging": false, "height": 454, - "id": "note-qq8Uo", + "id": "note-dhEu8", "measured": { "height": 454, - "width": 433 + "width": 436 }, "position": { "x": -1366.105596485301, @@ -3024,7 +3031,7 @@ }, { "data": { - "id": "ConditionalRouter-CfANV", + "id": "ConditionalRouter-NmX80", "node": { "base_classes": [ "Message" @@ -3267,10 +3274,10 @@ "type": "ConditionalRouter" }, "dragging": false, - "id": "ConditionalRouter-CfANV", + "id": "ConditionalRouter-NmX80", "measured": { - "height": 541, - "width": 320 + "height": 658, + "width": 360 }, "position": { "x": -352.80314888328695, @@ -3281,9 +3288,9 @@ } ], "viewport": { - "x": 437.31759577518767, - "y": -4009.9990846628743, - "zoom": 0.6745353230252618 + "x": 505.76519965138317, + "y": -1259.3934784241562, + "zoom": 0.301928683991489 } }, "description": "The YouTube Analysis flow extracts video comments and transcripts, analyzing sentiment patterns and content themes.", diff --git a/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputParameter/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputParameter/index.tsx index 9bf3c4f9e6..8a77913417 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputParameter/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/NodeOutputParameter/index.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from "react"; +import { useMemo } from "react"; import { getNodeOutputColors } from "../../../helpers/get-node-output-colors"; import { getNodeOutputColorsName } from "../../../helpers/get-node-output-colors-name"; diff --git a/src/frontend/src/CustomNodes/hooks/use-icons-status.tsx b/src/frontend/src/CustomNodes/hooks/use-icons-status.tsx index 5c8464c63c..ebf3e6cf99 100644 --- a/src/frontend/src/CustomNodes/hooks/use-icons-status.tsx +++ b/src/frontend/src/CustomNodes/hooks/use-icons-status.tsx @@ -1,9 +1,6 @@ import ForwardedIconComponent from "../../components/common/genericIconComponent"; -import Checkmark from "../../components/ui/checkmark"; -import Loading from "../../components/ui/loading"; import Xmark from "../../components/ui/xmark"; import { BuildStatus } from "../../constants/enums"; -import { VertexBuildTypeAPI } from "../../types/api"; const useIconStatus = (buildStatus: BuildStatus | undefined) => { const conditionError = buildStatus === BuildStatus.ERROR; diff --git a/src/frontend/src/components/common/storeCardComponent/index.tsx b/src/frontend/src/components/common/storeCardComponent/index.tsx index 2ff73d811a..962c2b4d4d 100644 --- a/src/frontend/src/components/common/storeCardComponent/index.tsx +++ b/src/frontend/src/components/common/storeCardComponent/index.tsx @@ -18,7 +18,6 @@ import { CardHeader, CardTitle, } from "../../ui/card"; -import Loading from "../../ui/loading"; import IconComponent from "../genericIconComponent"; import ShadTooltip from "../shadTooltipComponent"; import useDataEffect from "./hooks/use-data-effect"; diff --git a/src/frontend/src/components/common/timeoutErrorComponent/index.tsx b/src/frontend/src/components/common/timeoutErrorComponent/index.tsx index ffc58a046c..856e3df399 100644 --- a/src/frontend/src/components/common/timeoutErrorComponent/index.tsx +++ b/src/frontend/src/components/common/timeoutErrorComponent/index.tsx @@ -1,7 +1,6 @@ import BaseModal from "../../../modals/baseModal"; import { fetchErrorComponentType } from "../../../types/components"; import Loading from "../../ui/loading"; -import IconComponent from "../genericIconComponent"; export default function TimeoutErrorComponent({ message, diff --git a/src/frontend/src/components/core/appHeaderComponent/components/AccountMenu/index.tsx b/src/frontend/src/components/core/appHeaderComponent/components/AccountMenu/index.tsx index 28063e3a1d..1767acd4fb 100644 --- a/src/frontend/src/components/core/appHeaderComponent/components/AccountMenu/index.tsx +++ b/src/frontend/src/components/core/appHeaderComponent/components/AccountMenu/index.tsx @@ -1,4 +1,3 @@ -import ForwardedIconComponent from "@/components/common/genericIconComponent"; import { useLogout } from "@/controllers/API/queries/auth"; import { CustomFeedbackDialog } from "@/customization/components/custom-feedback-dialog"; import { CustomHeaderMenuItemsTitle } from "@/customization/components/custom-header-menu-items-title"; diff --git a/src/frontend/src/components/core/cardComponent/components/dragCardComponent/index.tsx b/src/frontend/src/components/core/cardComponent/components/dragCardComponent/index.tsx index f7bc5095f6..22ff60aefd 100644 --- a/src/frontend/src/components/core/cardComponent/components/dragCardComponent/index.tsx +++ b/src/frontend/src/components/core/cardComponent/components/dragCardComponent/index.tsx @@ -1,5 +1,4 @@ import { FlowType } from "@/types/flow"; -import { storeComponent } from "../../../../../types/store"; import { cn } from "../../../../../utils/utils"; import ForwardedIconComponent from "../../../../common/genericIconComponent"; import { Card, CardHeader, CardTitle } from "../../../../ui/card"; diff --git a/src/frontend/src/components/core/cardComponent/hooks/use-on-drag-start.tsx b/src/frontend/src/components/core/cardComponent/hooks/use-on-drag-start.tsx index 2beb534e2f..4b47d6a403 100644 --- a/src/frontend/src/components/core/cardComponent/hooks/use-on-drag-start.tsx +++ b/src/frontend/src/components/core/cardComponent/hooks/use-on-drag-start.tsx @@ -2,7 +2,6 @@ import { FlowType } from "@/types/flow"; import { useCallback } from "react"; import { createRoot } from "react-dom/client"; import useFlowsManagerStore from "../../../../stores/flowsManagerStore"; -import { storeComponent } from "../../../../types/store"; import DragCardComponent from "../components/dragCardComponent"; const useDragStart = (data: FlowType) => { diff --git a/src/frontend/src/components/core/cardComponent/index.tsx b/src/frontend/src/components/core/cardComponent/index.tsx index 6e446fd6ce..69e5111b86 100644 --- a/src/frontend/src/components/core/cardComponent/index.tsx +++ b/src/frontend/src/components/core/cardComponent/index.tsx @@ -8,7 +8,6 @@ import { getInputsAndOutputs } from "../../../utils/storeUtils"; import { cn } from "../../../utils/utils"; import IconComponent from "../../common/genericIconComponent"; import ShadTooltip from "../../common/shadTooltipComponent"; -import { Button } from "../../ui/button"; import { Card, CardDescription, diff --git a/src/frontend/src/components/core/codeTabsComponent/ChatCodeTabComponent.tsx b/src/frontend/src/components/core/codeTabsComponent/ChatCodeTabComponent.tsx index 945c6a53ac..7f9ea7665d 100644 --- a/src/frontend/src/components/core/codeTabsComponent/ChatCodeTabComponent.tsx +++ b/src/frontend/src/components/core/codeTabsComponent/ChatCodeTabComponent.tsx @@ -1,7 +1,6 @@ import { useState } from "react"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { tomorrow } from "react-syntax-highlighter/dist/cjs/styles/prism"; -import { useDarkStore } from "../../../stores/darkStore"; import IconComponent from "../../common/genericIconComponent"; import { Button } from "../../ui/button"; diff --git a/src/frontend/src/components/core/parameterRenderComponent/components/inputListComponent/index.tsx b/src/frontend/src/components/core/parameterRenderComponent/components/inputListComponent/index.tsx index b7c90b773a..7624ff310e 100644 --- a/src/frontend/src/components/core/parameterRenderComponent/components/inputListComponent/index.tsx +++ b/src/frontend/src/components/core/parameterRenderComponent/components/inputListComponent/index.tsx @@ -4,7 +4,6 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "../../../../ui/input"; import { ButtonInputList } from "./components/button-input-list"; -import { DropdownMenuInputList } from "./components/dropdown-menu"; import { GRADIENT_CLASS } from "@/constants/constants"; import { cn } from "../../../../../utils/utils"; @@ -139,7 +138,7 @@ export default function InputListComponent({ )} - {/* + {/* We will add this back in a future release {!disabled && ( { return ( ( ( Date: Tue, 18 Feb 2025 17:50:27 -0300 Subject: [PATCH 3/9] fix: added save button to table component to improve consistency (#6663) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added onSave and onCancel to tableModal, in order for it to preserve state * Changed TableNodeComponent to handle temp values and only change value when saved * 🔧 (tableModal/index.tsx): refactor handleSetOpen and handleOnEscapeKeyDown functions to improve code readability and maintainability * Fix test that use tableInput --------- Co-authored-by: cristhianzl --- .../components/TableNodeComponent/index.tsx | 28 ++++++++++--- src/frontend/src/modals/tableModal/index.tsx | 42 +++++++++++++++---- .../core/unit/tableInputComponent.spec.ts | 6 ++- 3 files changed, 62 insertions(+), 14 deletions(-) diff --git a/src/frontend/src/components/core/parameterRenderComponent/components/TableNodeComponent/index.tsx b/src/frontend/src/components/core/parameterRenderComponent/components/TableNodeComponent/index.tsx index db3db4147d..5c6c42471d 100644 --- a/src/frontend/src/components/core/parameterRenderComponent/components/TableNodeComponent/index.tsx +++ b/src/frontend/src/components/core/parameterRenderComponent/components/TableNodeComponent/index.tsx @@ -65,10 +65,12 @@ export default function TableNodeComponent({ }; }, []); const [selectedNodes, setSelectedNodes] = useState>([]); + const [tempValue, setTempValue] = useState(cloneDeep(value)); + const [isModalOpen, setIsModalOpen] = useState(false); const agGrid = useRef(null); const componentColumns = columns ? columns - : generateBackendColumnsFromValue(value ?? [], table_options); + : generateBackendColumnsFromValue(tempValue ?? [], table_options); let AgColumns = FormatColumns(componentColumns); // add info to each column AgColumns = AgColumns.map((col) => { @@ -93,7 +95,7 @@ export default function TableNodeComponent({ if (agGrid.current && !agGrid.current.api.isDestroyed()) { const rows: any = []; agGrid.current.api.forEachNode((node) => rows.push(node.data)); - handleOnNewValue({ value: rows }); + setTempValue(rows); } } function deleteRow() { @@ -109,8 +111,7 @@ export default function TableNodeComponent({ if (agGrid.current && selectedNodes.length > 0) { const toDuplicate = selectedNodes.map((node) => cloneDeep(node.data)); setSelectedNodes([]); - const rows: any = []; - handleOnNewValue({ value: [...value, ...toDuplicate] }); + setTempValue([...tempValue, ...toDuplicate]); } } function addRow() { @@ -118,12 +119,23 @@ export default function TableNodeComponent({ componentColumns.forEach((column) => { newRow[column.name] = column.default ?? null; // Use the default value if available }); - handleOnNewValue({ value: [...value, newRow] }); + setTempValue([...tempValue, newRow]); } function updateComponent() { setAllRows(); } + + function handleSave() { + handleOnNewValue({ value: tempValue }); + setIsModalOpen(false); + } + + function handleCancel() { + setTempValue(cloneDeep(value)); + setIsModalOpen(false); + } + const editable = componentColumns .map((column) => { const isCustomEdit = @@ -149,6 +161,8 @@ export default function TableNodeComponent({ >