From 5bb266cbc125378c74f33b259afb52130ba2ff2e Mon Sep 17 00:00:00 2001 From: Lucas Oliveira <62335616+lucaseduoli@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:20:50 -0300 Subject: [PATCH] fix: textarea with password visual bug (#3739) * Fix textarea with password visual bug * Changed password field to default to none * updated sanitizedHTMLWrapper to receive ref * fixed sanitizedhtmlwrapper type * Added back to scroll position and cursor position on Chrome * [autofix.ci] apply automated fixes * Fix position of password * Fixed tests * Fixed examples * Fixed test schema --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: anovazzi1 --- .../starter_projects/Blog Writer.json | 2 +- .../base/langflow/template/field/base.py | 14 ++++-- src/backend/tests/unit/test_frontend_nodes.py | 2 +- src/backend/tests/unit/test_schema.py | 1 - .../components/sanitizedHTMLWrapper/index.tsx | 19 ++++---- .../components/textAreaComponent/index.tsx | 18 ++----- src/frontend/src/modals/promptModal/index.tsx | 47 ++++++++++++++++--- src/frontend/src/types/components/index.ts | 2 - 8 files changed, 69 insertions(+), 36 deletions(-) diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json b/src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json index 0e03175b0..811d71a72 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Blog Writer.json @@ -200,7 +200,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import re\n\nfrom langchain_community.document_loaders.web_base import WebBaseLoader\n\nfrom langflow.helpers.data import data_to_text\nfrom langflow.custom import Component\nfrom langflow.io import MessageTextInput, Output\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass URLComponent(Component):\n display_name = \"URL\"\n description = \"Fetch content from one or more URLs.\"\n icon = \"layout-template\"\n name = \"URL\"\n\n inputs = [\n MessageTextInput(\n name=\"urls\",\n display_name=\"URLs\",\n info=\"Enter one or more URLs, by clicking the '+' button.\",\n is_list=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"fetch_content\"),\n Output(display_name=\"Text\", name=\"text\", method=\"fetch_content_text\"),\n ]\n\n def ensure_url(self, string: str) -> str:\n \"\"\"\n Ensures the given string is a URL by adding 'http://' if it doesn't start with 'http://' or 'https://'.\n Raises an error if the string is not a valid URL.\n\n Parameters:\n string (str): The string to be checked and possibly modified.\n\n Returns:\n str: The modified string that is ensured to be a URL.\n\n Raises:\n ValueError: If the string is not a valid URL.\n \"\"\"\n if not string.startswith((\"http://\", \"https://\")):\n string = \"http://\" + string\n\n # Basic URL validation regex\n url_regex = re.compile(\n r\"^(https?:\\/\\/)?\" # optional protocol\n r\"(www\\.)?\" # optional www\n r\"([a-zA-Z0-9.-]+)\" # domain\n r\"(\\.[a-zA-Z]{2,})?\" # top-level domain\n r\"(:\\d+)?\" # optional port\n r\"(\\/[^\\s]*)?$\", # optional path\n re.IGNORECASE,\n )\n\n if not url_regex.match(string):\n raise ValueError(f\"Invalid URL: {string}\")\n\n return string\n\n def fetch_content(self) -> list[Data]:\n urls = [self.ensure_url(url.strip()) for url in self.urls if url.strip()]\n loader = WebBaseLoader(web_paths=urls, encoding=\"utf-8\")\n docs = loader.load()\n data = [Data(text=doc.page_content, **doc.metadata) for doc in docs]\n self.status = data\n return data\n\n def fetch_content_text(self) -> Message:\n content = self.fetch_content()\n data = content if isinstance(content, list) else [content]\n\n result_string = data_to_text(\"{text}\", data)\n self.status = result_string\n return Message(text=result_string)\n" + "value": "import re\n\nfrom langchain_community.document_loaders.web_base import WebBaseLoader\n\nfrom langflow.helpers.data import data_to_text\nfrom langflow.custom import Component\nfrom langflow.io import MessageTextInput, Output\nfrom langflow.schema import Data\nfrom langflow.schema.message import Message\n\n\nclass URLComponent(Component):\n display_name = \"URL\"\n description = \"Fetch content from one or more URLs.\"\n icon = \"layout-template\"\n name = \"URL\"\n\n inputs = [\n MessageTextInput(\n name=\"urls\",\n display_name=\"URLs\",\n info=\"Enter one or more URLs, by clicking the '+' button.\",\n is_list=True,\n ),\n ]\n\n outputs = [\n Output(display_name=\"Data\", name=\"data\", method=\"fetch_content\"),\n Output(display_name=\"Text\", name=\"text\", method=\"fetch_content_text\"),\n ]\n\n def ensure_url(self, string: str) -> str:\n \"\"\"\n Ensures the given string is a URL by adding 'http://' if it doesn't start with 'http://' or 'https://'.\n Raises an error if the string is not a valid URL.\n\n Parameters:\n string (str): The string to be checked and possibly modified.\n\n Returns:\n str: The modified string that is ensured to be a URL.\n\n Raises:\n ValueError: If the string is not a valid URL.\n \"\"\"\n if not string.startswith((\"http://\", \"https://\")):\n string = \"http://\" + string\n\n # Basic URL validation regex\n url_regex = re.compile(\n r\"^(https?:\\/\\/)?\" # optional protocol\n r\"(www\\.)?\" # optional www\n r\"([a-zA-Z0-9.-]+)\" # domain\n r\"(\\.[a-zA-Z]{2,})?\" # top-level domain\n r\"(:\\d+)?\" # optional port\n r\"(\\/[^\\s]*)?$\", # optional path\n re.IGNORECASE,\n )\n\n if not url_regex.match(string):\n raise ValueError(f\"Invalid URL: {string}\")\n\n return string\n\n def fetch_content(self) -> list[Data]:\n urls = [self.ensure_url(url.strip()) for url in self.urls if url.strip()]\n loader = WebBaseLoader(web_paths=urls, encoding=\"utf-8\")\n docs = loader.load()\n data = [Data(text=doc.page_content, **doc.metadata) for doc in docs]\n self.status = data\n return data\n\n def fetch_content_text(self) -> Message:\n data = self.fetch_content()\n\n result_string = data_to_text(\"{text}\", data)\n self.status = result_string\n return Message(text=result_string)\n" }, "urls": { "advanced": false, diff --git a/src/backend/base/langflow/template/field/base.py b/src/backend/base/langflow/template/field/base.py index 569183a44..7360702fb 100644 --- a/src/backend/base/langflow/template/field/base.py +++ b/src/backend/base/langflow/template/field/base.py @@ -5,7 +5,15 @@ from typing import _UnionGenericAlias # type: ignore from typing import Any from collections.abc import Callable -from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator, model_serializer, model_validator +from pydantic import ( + BaseModel, + ConfigDict, + Field, + field_serializer, + field_validator, + model_serializer, + model_validator, +) from langflow.field_typing import Text from langflow.field_typing.range_spec import RangeSpec @@ -50,8 +58,8 @@ class Input(BaseModel): file_path: str | None = "" """The file path of the field if it is a file. Defaults to None.""" - password: bool = False - """Specifies if the field is a password. Defaults to False.""" + password: bool | None = None + """Specifies if the field is a password. Defaults to None.""" options: list[str] | Callable | None = None """List of options for the field. Only used when is_list=True. Default is an empty list.""" diff --git a/src/backend/tests/unit/test_frontend_nodes.py b/src/backend/tests/unit/test_frontend_nodes.py index ca8c37af9..fe9d71cc6 100644 --- a/src/backend/tests/unit/test_frontend_nodes.py +++ b/src/backend/tests/unit/test_frontend_nodes.py @@ -40,8 +40,8 @@ def test_template_field_defaults(sample_template_field: Input): assert sample_template_field.value is None assert sample_template_field.file_types == [] assert sample_template_field.file_path == "" - assert sample_template_field.password is False assert sample_template_field.name == "test_field" + assert sample_template_field.password is None def test_template_to_dict(sample_template: Template, sample_template_field: Input): diff --git a/src/backend/tests/unit/test_schema.py b/src/backend/tests/unit/test_schema.py index c474cb02e..378d451b0 100644 --- a/src/backend/tests/unit/test_schema.py +++ b/src/backend/tests/unit/test_schema.py @@ -57,7 +57,6 @@ class TestInput: "multiline": False, "fileTypes": [], "file_path": "", - "password": False, "advanced": False, "title_case": False, "dynamic": False, diff --git a/src/frontend/src/components/sanitizedHTMLWrapper/index.tsx b/src/frontend/src/components/sanitizedHTMLWrapper/index.tsx index 832ea8505..59291b5c6 100644 --- a/src/frontend/src/components/sanitizedHTMLWrapper/index.tsx +++ b/src/frontend/src/components/sanitizedHTMLWrapper/index.tsx @@ -1,23 +1,24 @@ import DOMPurify from "dompurify"; +import { forwardRef } from "react"; import { SanitizedHTMLWrapperType } from "../../types/components"; -const SanitizedHTMLWrapper = ({ - className, - content, - onClick, - suppressWarning = false, -}: SanitizedHTMLWrapperType): JSX.Element => { +const SanitizedHTMLWrapper = forwardRef< + HTMLDivElement, + React.HTMLAttributes & SanitizedHTMLWrapperType +>(({ content, suppressWarning = false, ...props }, ref) => { const sanitizedHTML = DOMPurify.sanitize(content); return (
); -}; +}); + +SanitizedHTMLWrapper.displayName = "SanitizedHTMLWrapper"; export default SanitizedHTMLWrapper; diff --git a/src/frontend/src/components/textAreaComponent/index.tsx b/src/frontend/src/components/textAreaComponent/index.tsx index b4e749753..2f3a859d7 100644 --- a/src/frontend/src/components/textAreaComponent/index.tsx +++ b/src/frontend/src/components/textAreaComponent/index.tsx @@ -50,12 +50,7 @@ export default function TextAreaComponent({ disabled={disabled} password={password} > -
+