diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 000000000..fc12c18c2 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,90 @@ +{ + "extends": [ + "eslint:recommended", + "plugin:react/recommended", + "plugin:prettier/recommended" + ], + "plugins": [ + "react", + "import-helpers", + "prettier" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": [ + "./tsconfig.node.json", + "./tsconfig.json" + ], + "extraFileExtensions:": [ + ".mdx" + ], + "extensions:": [ + ".mdx" + ] + }, + "env": { + "browser": true, + "es2021": true + }, + "settings": { + "react": { + "version": "detect" + } + }, + "rules": { + "no-console": "warn", + "no-self-assign": "warn", + "no-self-compare": "warn", + "complexity": [ + "error", + { + "max": 15 + } + ], + "indent": [ + "error", + 2, + { + "SwitchCase": 1 + } + ], + "no-dupe-keys": "error", + "no-invalid-regexp": "error", + "no-undef": "error", + "no-return-assign": "error", + "no-redeclare": "error", + "no-empty": "error", + "no-await-in-loop": "error", + "react/react-in-jsx-scope": 0, + "node/exports-style": [ + "error", + "module.exports" + ], + "node/file-extension-in-import": [ + "error", + "always" + ], + "node/prefer-global/buffer": [ + "error", + "always" + ], + "node/prefer-global/console": [ + "error", + "always" + ], + "node/prefer-global/process": [ + "error", + "always" + ], + "node/prefer-global/url-search-params": [ + "error", + "always" + ], + "node/prefer-global/url": [ + "error", + "always" + ], + "node/prefer-promises/dns": "error", + "node/prefer-promises/fs": "error" + } +} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..60505515a --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,44 @@ +fail_fast: true +repos: + - repo: https://github.com/pre-commit/mirrors-eslint + rev: "v9.1.1" + hooks: + - id: eslint + files: \.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx + types: [file] + args: ["--fix", "--no-warn-ignored"] + additional_dependencies: + - eslint@9.1.1 + - eslint-plugin-prettier + - eslint-config-prettier + - prettier + - eslint-plugin-react@latest + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.1.0 + hooks: + - id: check-case-conflict + - id: end-of-file-fixer + - id: mixed-line-ending + args: + - --fix=lf + - id: trailing-whitespace + - id: pretty-format-json + exclude: ^tsconfig.*.json + args: + - --autofix + - --indent=4 + - --no-sort-keys + - id: check-merge-conflict + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.4.2 + hooks: + # Run the linter. + - id: ruff + # Python + files: \.py$ + types: [file] + # Run the formatter. + - id: ruff-format + files: \.py$ + types: [file] diff --git a/Makefile b/Makefile index 6d279e62f..7053ef8cb 100644 --- a/Makefile +++ b/Makefile @@ -135,6 +135,7 @@ frontendc: install_backend: @echo 'Installing backend dependencies' @poetry install + @poetry run pre-commit install backend: @echo 'Setting up the environment' diff --git a/src/frontend/src/modals/NodeModal/components/ModalField/index.tsx b/eslint.config.js similarity index 100% rename from src/frontend/src/modals/NodeModal/components/ModalField/index.tsx rename to eslint.config.js diff --git a/poetry.lock b/poetry.lock index 2b124cfe9..358fa10cb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -828,6 +828,17 @@ files = [ [package.dependencies] pycparser = "*" +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + [[package]] name = "chardet" version = "5.2.0" @@ -1604,6 +1615,17 @@ files = [ {file = "diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc"}, ] +[[package]] +name = "distlib" +version = "0.3.8" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, +] + [[package]] name = "distro" version = "1.9.0" @@ -3143,6 +3165,20 @@ files = [ {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"}, ] +[[package]] +name = "identify" +version = "2.5.36" +description = "File identification library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"}, + {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"}, +] + +[package.extras] +license = ["ukkonen"] + [[package]] name = "idna" version = "3.7" @@ -5229,6 +5265,20 @@ plot = ["matplotlib"] tgrep = ["pyparsing"] twitter = ["twython"] +[[package]] +name = "nodeenv" +version = "1.8.0" +description = "Node.js virtual environment builder" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] + +[package.dependencies] +setuptools = "*" + [[package]] name = "numexpr" version = "2.10.0" @@ -6215,6 +6265,24 @@ dev = ["black", "flake8", "flake8-print", "isort", "pre-commit"] sentry = ["django", "sentry-sdk"] test = ["coverage", "flake8", "freezegun (==0.3.15)", "mock (>=2.0.0)", "pylint", "pytest", "pytest-timeout"] +[[package]] +name = "pre-commit" +version = "3.7.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pre_commit-3.7.0-py2.py3-none-any.whl", hash = "sha256:5eae9e10c2b5ac51577c3452ec0a490455c45a0533f7960f993a0d01e59decab"}, + {file = "pre_commit-3.7.0.tar.gz", hash = "sha256:e209d61b8acdcf742404408531f0c37d49d2c734fd7cff2d6076083d191cb060"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + [[package]] name = "prometheus-client" version = "0.20.0" @@ -9561,6 +9629,26 @@ files = [ {file = "vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0"}, ] +[[package]] +name = "virtualenv" +version = "20.26.1" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.26.1-py3-none-any.whl", hash = "sha256:7aa9982a728ae5892558bff6a2839c00b9ed145523ece2274fad6f414690ae75"}, + {file = "virtualenv-20.26.1.tar.gz", hash = "sha256:604bfdceaeece392802e6ae48e69cec49168b9c5f4a44e483963f9242eb0e78b"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + [[package]] name = "watchfiles" version = "0.21.0" @@ -10253,4 +10341,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<3.12" -content-hash = "bec34397b534f882551511558c76785c7cd67e6a1eefc1d45f6a64d97175d886" +content-hash = "b3d424bc8e83a9f10a8e71f95e2499b3018711d8edf7a594814b9388e5393a84" diff --git a/pyproject.toml b/pyproject.toml index f49df7a35..611a8d350 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -108,6 +108,7 @@ respx = "^0.21.1" pytest-instafail = "^0.5.0" pytest-asyncio = "^0.23.0" pytest-profiling = "^1.7.0" +pre-commit = "^3.7.0" [tool.poetry.extras] deploy = ["celery", "redis", "flower"] diff --git a/scripts/setup/update_poetry.sh b/scripts/setup/update_poetry.sh index 94c51fd56..a8825a116 100644 --- a/scripts/setup/update_poetry.sh +++ b/scripts/setup/update_poetry.sh @@ -32,90 +32,6 @@ case "$OS" in ;; esac -echo "Detected Operating System: $OS" - -# Installation of pipx based on the detected OS -install_pipx() { - case $1 in - macOS) - # macOS installation using Homebrew - command -v brew >/dev/null 2>&1 || exit_with_message "Homebrew is not installed. Please install Homebrew first." - echo "Installing pipx using Homebrew..." - brew install pipx - pipx ensurepath - ;; - Linux) - # Linux installation. Further checks are needed to distinguish between distributions - if grep -qEi "(ubuntu|debian)" /etc/*release; then - echo "Installing pipx on Ubuntu/Debian..." - sudo apt update - sudo apt install pipx -y - elif grep -qEi "fedora" /etc/*release; then - echo "Installing pipx on Fedora..." - sudo dnf install pipx -y - else - echo "Installing pipx using pip (other Linux distributions)..." - python3 -m pip install --user pipx - fi - pipx ensurepath - ;; - *) - exit_with_message "Unsupported operating system for pipx installation." - ;; - esac -} - -# Function to fetch the latest version of pipx from GitHub and compare with the installed version -check_for_pipx_update() { - echo "Checking for updates to pipx..." - # Fetch the latest version of pipx, ensuring only to capture the numeric version without 'v' prefix. - local latest_version=$(curl -s https://api.github.com/repos/pypa/pipx/releases/latest | grep '"tag_name":' | sed -E 's/.*"tag_name": "v?([^"]+)".*/\1/') - # Extract the current installed version of pipx. - local current_version=$(pipx --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') - - if [[ "$latest_version" == "$current_version" ]]; then - echo "You have the latest version of pipx ($current_version)." - else - echo "A newer version of pipx ($latest_version) is available. You have $current_version. Do you want to update? (yes/no)" - read -r user_input - if [[ "$user_input" == "yes" ]]; then - echo "Updating pipx..." - case "$OS" in - macOS) - brew upgrade pipx - ;; - Linux) - if grep -qEi "(ubuntu|debian)" /etc/*release; then - sudo apt update - sudo apt install --only-upgrade pipx -y - elif grep -qEi "fedora" /etc/*release; then - sudo dnf upgrade pipx -y - else - python3 -m pip install --user --upgrade pipx - fi - ;; - *) - exit_with_message "Unsupported operating system for pipx update." - ;; - esac - pipx ensurepath - echo "pipx updated to version $latest_version" - else - echo "Not updating pipx at this time." - fi - fi -} - -# Now, modify the existing check to call check_for_pipx_update even if pipx is installed -if ! command -v pipx &> /dev/null; then - echo "Pipx is not installed. Installing..." - install_pipx "$OS" - echo "Pipx installed successfully." -else - echo "Pipx is already installed." - check_for_pipx_update -fi - echo "Checking Poetry installation..." @@ -124,7 +40,7 @@ if ! command -v poetry &> /dev/null then echo "Poetry is not installed. Installing..." # Also install python 3.10 and use - pipx install poetry --python python3.10 --fetch-missing-python + curl -sSL https://install.python-poetry.org | python3 - echo "Poetry installed successfully." else echo "Poetry is already installed." @@ -146,3 +62,5 @@ else echo "Poetry version is $1 or higher. No need to update." fi + + diff --git a/src/backend/base/langflow/api/utils.py b/src/backend/base/langflow/api/utils.py index b9f10d2c4..e2f6e07e7 100644 --- a/src/backend/base/langflow/api/utils.py +++ b/src/backend/base/langflow/api/utils.py @@ -205,17 +205,12 @@ async def build_and_cache_graph_from_db( flow_id: str, session: Session, chat_service: "ChatService", - graph: Optional[Graph] = None, ): """Build and cache the graph.""" flow: Optional[Flow] = session.get(Flow, flow_id) if not flow or not flow.data: raise ValueError("Invalid flow ID") - other_graph = Graph.from_payload(flow.data, flow_id) - if graph is None: - graph = other_graph - else: - graph = graph.update(other_graph) + graph = Graph.from_payload(flow.data, flow_id) await chat_service.set_cache(flow_id, graph) return graph diff --git a/src/backend/base/langflow/api/v1/chat.py b/src/backend/base/langflow/api/v1/chat.py index d44d96e72..30e709664 100644 --- a/src/backend/base/langflow/api/v1/chat.py +++ b/src/backend/base/langflow/api/v1/chat.py @@ -79,13 +79,8 @@ async def retrieve_vertices_order( """ try: # First, we need to check if the flow_id is in the cache - graph = None if not data: - if cache := await chat_service.get_cache(flow_id): - graph = cache.get("result") - graph = await build_and_cache_graph_from_db( - flow_id=flow_id, session=session, chat_service=chat_service, graph=graph - ) + graph = await build_and_cache_graph_from_db(flow_id=flow_id, session=session, chat_service=chat_service) else: graph = await build_and_cache_graph_from_data( flow_id=flow_id, graph_data=data.model_dump(), chat_service=chat_service diff --git a/src/backend/base/langflow/components/data/APIRequest.py b/src/backend/base/langflow/components/data/APIRequest.py index 83a1edb9d..9f1ca703c 100644 --- a/src/backend/base/langflow/components/data/APIRequest.py +++ b/src/backend/base/langflow/components/data/APIRequest.py @@ -93,14 +93,14 @@ class APIRequest(CustomComponent): self, method: str, urls: List[str], - _headers: Optional[Record] = None, + headers: Optional[Record] = None, body: Optional[Record] = None, timeout: int = 5, ) -> List[Record]: - if _headers is None: - headers = {} + if headers is None: + headers_dict = {} else: - headers = _headers.data + headers_dict = headers.data bodies = [] if body: @@ -114,7 +114,7 @@ class APIRequest(CustomComponent): bodies += [None] * (len(urls) - len(bodies)) # type: ignore async with httpx.AsyncClient() as client: results = await asyncio.gather( - *[self.make_request(client, method, u, headers, rec, timeout) for u, rec in zip(urls, bodies)] + *[self.make_request(client, method, u, headers_dict, rec, timeout) for u, rec in zip(urls, bodies)] ) self.status = results return results diff --git a/src/backend/base/langflow/components/inputs/FileInput.py b/src/backend/base/langflow/components/inputs/FileInput.py deleted file mode 100644 index 0cd7c12b1..000000000 --- a/src/backend/base/langflow/components/inputs/FileInput.py +++ /dev/null @@ -1,48 +0,0 @@ -from pathlib import Path -from typing import Any, Dict - -from langflow.base.data.utils import TEXT_FILE_TYPES, parse_text_file_to_record -from langflow.interface.custom.custom_component import CustomComponent -from langflow.schema import Record - - -class FileInput(CustomComponent): - display_name = "File Input" - description = "A generic file input." - icon = "file-text" - - def build_config(self) -> Dict[str, Any]: - return { - "path": { - "display_name": "Path", - "field_type": "file", - "file_types": TEXT_FILE_TYPES, - "info": f"Supported file types: {', '.join(TEXT_FILE_TYPES)}", - }, - "silent_errors": { - "display_name": "Silent Errors", - "advanced": True, - "info": "If true, errors will not raise an exception.", - }, - } - - def load_file(self, path: str, silent_errors: bool = False) -> Record: - resolved_path = self.resolve_path(path) - path_obj = Path(resolved_path) - extension = path_obj.suffix[1:].lower() - if extension == "doc": - raise ValueError("doc files are not supported. Please save as .docx") - if extension not in TEXT_FILE_TYPES: - raise ValueError(f"Unsupported file type: {extension}") - record = parse_text_file_to_record(resolved_path, silent_errors) - self.status = record if record else "No data" - return record or Record() - - def build( - self, - path: str, - silent_errors: bool = False, - ) -> Record: - record = self.load_file(path, silent_errors) - self.status = record - return record diff --git a/src/backend/base/langflow/components/inputs/JsonInput.py b/src/backend/base/langflow/components/inputs/JsonInput.py deleted file mode 100644 index 713868147..000000000 --- a/src/backend/base/langflow/components/inputs/JsonInput.py +++ /dev/null @@ -1,17 +0,0 @@ -from langflow.base.io.text import TextComponent -from langflow.field_typing.constants import Data, NestedDict - -class JsonInput(TextComponent): - display_name = "JSON Input" - description = "JSON Input." - - def build_config(self): - return { - "input_value": { - "display_name": "JSON", - "field_type": "NestedDict" - } - } - - def build(self, input_value: NestedDict) -> NestedDict: - return input_value diff --git a/src/backend/base/langflow/components/inputs/KeyPairInput.py b/src/backend/base/langflow/components/inputs/KeyPairInput.py deleted file mode 100644 index c48d0a0e1..000000000 --- a/src/backend/base/langflow/components/inputs/KeyPairInput.py +++ /dev/null @@ -1,19 +0,0 @@ -from langflow.base.io.text import TextComponent -from langflow.field_typing.constants import Data - - -class KeyPairInput(TextComponent): - display_name = "Dictionary Input" - description = "Dictionary Input." - - def build_config(self): - return { - "input_value": { - "display_name": "Dictionaries", - "field_type": "dict", - "list": True - } - } - - def build(self, input_value: dict) -> dict: - return input_value diff --git a/src/backend/base/langflow/components/inputs/StringListInput.py b/src/backend/base/langflow/components/inputs/StringListInput.py deleted file mode 100644 index 5eadce9bc..000000000 --- a/src/backend/base/langflow/components/inputs/StringListInput.py +++ /dev/null @@ -1,13 +0,0 @@ -# from langflow.field_typing import Data -from langflow.schema import Record -from langflow.interface.custom.custom_component import CustomComponent - - -class StringListInput(CustomComponent): - display_name = "String List Input" - - def build_config(self): - return {"input_value": {"display_name": "String List Input", "field_type": "str", "list": True}} - - def build(self, input_value: list) -> Record: - return Record(data=input_value) diff --git a/src/backend/base/langflow/components/outputs/CSVOutput.py b/src/backend/base/langflow/components/outputs/CSVOutput.py deleted file mode 100644 index 711a6ab96..000000000 --- a/src/backend/base/langflow/components/outputs/CSVOutput.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing import Optional - -from langflow.base.io.text import TextComponent -from langflow.field_typing import Text, Data - - -class CSVOutput(TextComponent): - display_name = "CSV Output" - description = "Used view csv files" - - field_config = { - "input_value": {"display_name": "csv","info":"A csv blob","input_types":["Data"]}, - "separator": {"display_name": "separator","info":"The separator used in the csv file","input_types":["Text"], "field_type":"Text","default_value":";","options":[";", ",", "|"]}, - } - - def build(self, input_value: Data, separator) -> Data: - return {"data": input_value, "separator": separator} diff --git a/src/backend/base/langflow/components/outputs/ImageOutput.py b/src/backend/base/langflow/components/outputs/ImageOutput.py deleted file mode 100644 index f50fb7bf7..000000000 --- a/src/backend/base/langflow/components/outputs/ImageOutput.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import Optional - -from langflow.base.io.text import TextComponent -from langflow.field_typing import Text - -class ImageOutput(TextComponent): - display_name = "Image Output" - description = "Used view image files" - - field_config = { - "input_value": {"display_name": "image","info":"A image url","input_types":["Text"]}, - } - - def build(self, input_value: Text) -> Text: - return input_value \ No newline at end of file diff --git a/src/backend/base/langflow/components/outputs/JsonOutput.py b/src/backend/base/langflow/components/outputs/JsonOutput.py deleted file mode 100644 index cbbc10ddc..000000000 --- a/src/backend/base/langflow/components/outputs/JsonOutput.py +++ /dev/null @@ -1,17 +0,0 @@ -from langflow.base.io.text import TextComponent -from langflow.field_typing.constants import Data, NestedDict - -class JsonOutput(TextComponent): - display_name = "JSON Output" - description = "JSON Output." - - def build_config(self): - return { - "input_value": { - "display_name": "JSON", - "field_type": "NestedDict" - } - } - - def build(self, input_value: NestedDict) -> NestedDict: - return input_value diff --git a/src/backend/base/langflow/components/outputs/KeyPairOutput.py b/src/backend/base/langflow/components/outputs/KeyPairOutput.py deleted file mode 100644 index f2204bb83..000000000 --- a/src/backend/base/langflow/components/outputs/KeyPairOutput.py +++ /dev/null @@ -1,19 +0,0 @@ -from langflow.base.io.text import TextComponent -from langflow.field_typing.constants import Data - - -class KeyPairOutput(TextComponent): - display_name = "Dictionary Output" - description = "Dictionary Output." - - def build_config(self): - return { - "input_value": { - "display_name": "Dictionaries", - "field_type": "dict", - "list": True - } - } - - def build(self, input_value: dict) -> dict: - return input_value diff --git a/src/backend/base/langflow/components/outputs/PDFOutput.py b/src/backend/base/langflow/components/outputs/PDFOutput.py deleted file mode 100644 index 75e1fabf4..000000000 --- a/src/backend/base/langflow/components/outputs/PDFOutput.py +++ /dev/null @@ -1,16 +0,0 @@ -from typing import Optional - -from langflow.base.io.text import TextComponent -from langflow.field_typing import Text - - -class PDFOutput(TextComponent): - display_name = "PDF Output" - description = "Used view pdf files" - - field_config = { - "input_value": {"display_name": "pdf","info":"A pdf url","input_types":["Text"]}, - } - - def build(self, input_value: Text) -> Text: - return input_value diff --git a/src/backend/base/langflow/components/outputs/StringListOutput.py b/src/backend/base/langflow/components/outputs/StringListOutput.py deleted file mode 100644 index 3b1ff6088..000000000 --- a/src/backend/base/langflow/components/outputs/StringListOutput.py +++ /dev/null @@ -1,13 +0,0 @@ -# from langflow.field_typing import Data -from langflow.schema import Record -from langflow.interface.custom.custom_component import CustomComponent - - -class StringListOutput(CustomComponent): - display_name = "String List Output" - - def build_config(self): - return {"input_value": {"display_name": "String List Output", "field_type": "str", "list": True}} - - def build(self, input_value: list) -> Record: - return Record(data=input_value) \ No newline at end of file diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index bddfd6795..1744ac687 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -445,9 +445,16 @@ class Graph: vertex = self.get_vertex(vertex_id) vertex.set_state(state) - def mark_branch(self, vertex_id: str, state: str): + def mark_branch(self, vertex_id: str, state: str, visited: Optional[set] = None): """Marks a branch of the graph.""" + if visited is None: + visited = set() + visited.add(vertex_id) + if vertex_id in visited: + return + self.mark_vertex(vertex_id, state) + for child_id in self.parent_child_map[vertex_id]: self.mark_branch(child_id, state) diff --git a/src/backend/base/langflow/interface/initialize/loading.py b/src/backend/base/langflow/interface/initialize/loading.py index 15045cd06..fcec59cae 100644 --- a/src/backend/base/langflow/interface/initialize/loading.py +++ b/src/backend/base/langflow/interface/initialize/loading.py @@ -185,7 +185,7 @@ async def instantiate_custom_component(params, user_id, vertex): # Call the build method directly if it's sync build_result = custom_component.build(**params_copy) custom_repr = custom_component.custom_repr() - if not custom_repr and isinstance(build_result, (dict, Record, str)): + if custom_repr is None and isinstance(build_result, (dict, Record, str)): custom_repr = build_result if not isinstance(custom_repr, str): custom_repr = str(custom_repr) diff --git a/src/backend/base/langflow/services/store/service.py b/src/backend/base/langflow/services/store/service.py index c6205269a..4fabd435c 100644 --- a/src/backend/base/langflow/services/store/service.py +++ b/src/backend/base/langflow/services/store/service.py @@ -59,7 +59,7 @@ def get_id_from_search_string(search_string: str) -> Optional[str]: Returns: Optional[str]: The extracted ID, or None if no ID is found. """ - possible_id = search_string + possible_id: Optional[str] = search_string if "www.langflow.store/store/" in search_string: possible_id = search_string.split("/")[-1] diff --git a/src/frontend/.eslintrc.json b/src/frontend/.eslintrc.json index 682c7fc87..1b09f8c18 100644 --- a/src/frontend/.eslintrc.json +++ b/src/frontend/.eslintrc.json @@ -1,7 +1,24 @@ { - "extends": ["eslint:recommended", "plugin:node/recommended"], + "extends": [ + "eslint:recommended", + "plugin:react/recommended", + "plugin:prettier/recommended" + ], + "plugins": ["react", "import-helpers", "prettier"], + "parser": "@typescript-eslint/parser", "parserOptions": { - "ecmaVersion": 2018 + "project": ["./tsconfig.node.json", "./tsconfig.json"], + "extraFileExtensions:": [".mdx"], + "extensions:": [".mdx"] + }, + "env": { + "browser": true, + "es2021": true + }, + "settings": { + "react": { + "version": "detect" + } }, "rules": { "no-console": "warn", @@ -16,6 +33,7 @@ "no-redeclare": "error", "no-empty": "error", "no-await-in-loop": "error", + "react/react-in-jsx-scope": 0, "node/exports-style": ["error", "module.exports"], "node/file-extension-in-import": ["error", "always"], "node/prefer-global/buffer": ["error", "always"], diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index ae8bdb098..d4d59c3a2 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -99,6 +99,7 @@ "prettier-plugin-organize-imports": "^3.2.3", "prettier-plugin-tailwindcss": "^0.3.0", "pretty-quick": "^3.1.3", + "simple-git-hooks": "^2.11.1", "tailwindcss": "^3.3.3", "tailwindcss-dotted-background": "^1.1.0", "typescript": "^5.2.2", @@ -11917,6 +11918,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/simple-git-hooks": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/simple-git-hooks/-/simple-git-hooks-2.11.1.tgz", + "integrity": "sha512-tgqwPUMDcNDhuf1Xf6KTUsyeqGdgKMhzaH4PAZZuzguOgTl5uuyeYe/8mWgAr6IBxB5V06uqEf6Dy37gIWDtDg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "simple-git-hooks": "cli.js" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", diff --git a/src/frontend/package.json b/src/frontend/package.json index ddef9be04..529f0b2f3 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -78,6 +78,9 @@ "format": "npx prettier --write \"./**/*.{js,jsx,ts,tsx,json,md}\" --ignore-path .prettierignore", "type-check": "tsc --noEmit --pretty --project tsconfig.json && vite" }, + "simple-git-hooks": { + "pre-commit": "npx pretty-quick --staged" + }, "eslintConfig": { "extends": [ "react-app", @@ -121,10 +124,11 @@ "prettier-plugin-organize-imports": "^3.2.3", "prettier-plugin-tailwindcss": "^0.3.0", "pretty-quick": "^3.1.3", + "simple-git-hooks": "^2.11.1", "tailwindcss": "^3.3.3", "tailwindcss-dotted-background": "^1.1.0", "typescript": "^5.2.2", "ua-parser-js": "^1.0.37", "vite": "^4.5.2" } -} +} \ No newline at end of file diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 1a04e08f7..2d8fc3caa 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -6,7 +6,7 @@ import "./App.css"; import ErrorAlert from "./alerts/error"; import NoticeAlert from "./alerts/notice"; import SuccessAlert from "./alerts/success"; -import CrashErrorComponent from "./components/CrashErrorComponent"; +import CrashErrorComponent from "./components/crashErrorComponent"; import FetchErrorComponent from "./components/fetchErrorComponent"; import LoadingComponent from "./components/loadingComponent"; import { diff --git a/src/frontend/src/components/AccordionComponent/index.tsx b/src/frontend/src/components/accordionComponent/index.tsx similarity index 100% rename from src/frontend/src/components/AccordionComponent/index.tsx rename to src/frontend/src/components/accordionComponent/index.tsx diff --git a/src/frontend/src/components/addNewVariableButtonComponent/addNewVariableButton.tsx b/src/frontend/src/components/addNewVariableButtonComponent/addNewVariableButton.tsx index e5e8dd488..8786cdac6 100644 --- a/src/frontend/src/components/addNewVariableButtonComponent/addNewVariableButton.tsx +++ b/src/frontend/src/components/addNewVariableButtonComponent/addNewVariableButton.tsx @@ -5,6 +5,7 @@ import useAlertStore from "../../stores/alertStore"; import { useGlobalVariablesStore } from "../../stores/globalVariables"; import { useTypesStore } from "../../stores/typesStore"; import { ResponseErrorDetailAPI } from "../../types/api"; +import { sortByName } from "../../utils/utils"; import ForwardedIconComponent from "../genericIconComponent"; import InputComponent from "../inputComponent"; import { Button } from "../ui/button"; @@ -22,15 +23,20 @@ export default function AddNewVariableButton({ children }): JSX.Element { const [open, setOpen] = useState(false); const setErrorData = useAlertStore((state) => state.setErrorData); const componentFields = useTypesStore((state) => state.ComponentFields); - const unavaliableFields =new Set(Object.keys(useGlobalVariablesStore( - (state) => state.unavaliableFields - ))); - - const availableFields = Array.from(componentFields).filter( - (field) => !unavaliableFields.has(field) + const unavaliableFields = new Set( + Object.keys(useGlobalVariablesStore((state) => state.unavaliableFields)), ); + + const availableFields = () => { + const fields = Array.from(componentFields).filter( + (field) => !unavaliableFields.has(field), + ); + + return sortByName(fields); + }; + const addGlobalVariable = useGlobalVariablesStore( - (state) => state.addGlobalVariable + (state) => state.addGlobalVariable, ); function handleSaveVariable() { @@ -97,6 +103,7 @@ export default function AddNewVariableButton({ children }): JSX.Element { password={false} options={["Generic", "Credential"]} placeholder="Choose a type for the variable..." + id={"type-global-variables"} >