diff --git a/src/backend/base/langflow/components/__init__.py b/src/backend/base/langflow/components/__init__.py index 7dfd9bf278b6..528057c85845 100644 --- a/src/backend/base/langflow/components/__init__.py +++ b/src/backend/base/langflow/components/__init__.py @@ -63,7 +63,6 @@ openrouter, perplexity, processing, - prototypes, redis, sambanova, scrapegraph, @@ -95,7 +94,6 @@ "input_output": "langflow.components.input_output", "logic": "langflow.components.logic", "custom_component": "langflow.components.custom_component", - "prototypes": "langflow.components.prototypes", "openai": "langflow.components.openai", "anthropic": "langflow.components.anthropic", "google": "langflow.components.google", @@ -215,7 +213,6 @@ "openrouter", "perplexity", "processing", - "prototypes", "redis", "sambanova", "scrapegraph", diff --git a/src/backend/base/langflow/components/prototypes/__init__.py b/src/backend/base/langflow/components/prototypes/__init__.py deleted file mode 100644 index 4f17dddb6f27..000000000000 --- a/src/backend/base/langflow/components/prototypes/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -from __future__ import annotations - -from typing import TYPE_CHECKING, Any - -from langflow.components._importing import import_mod - -if TYPE_CHECKING: - from .python_function import PythonFunctionComponent - -_dynamic_imports = { - "PythonFunctionComponent": "python_function", -} - -__all__ = [ - "PythonFunctionComponent", -] - - -def __getattr__(attr_name: str) -> Any: - """Lazily import prototype components on attribute access.""" - if attr_name not in _dynamic_imports: - msg = f"module '{__name__}' has no attribute '{attr_name}'" - raise AttributeError(msg) - try: - result = import_mod(attr_name, _dynamic_imports[attr_name], __spec__.parent) - except (ModuleNotFoundError, ImportError, AttributeError) as e: - msg = f"Could not import '{attr_name}' from '{__name__}': {e}" - raise AttributeError(msg) from e - globals()[attr_name] = result - return result - - -def __dir__() -> list[str]: - return list(__all__) diff --git a/src/backend/base/langflow/components/prototypes/python_function.py b/src/backend/base/langflow/components/prototypes/python_function.py deleted file mode 100644 index a9fb33244bb7..000000000000 --- a/src/backend/base/langflow/components/prototypes/python_function.py +++ /dev/null @@ -1,73 +0,0 @@ -from collections.abc import Callable - -from langflow.custom.custom_component.component import Component -from langflow.custom.utils import get_function -from langflow.io import CodeInput, Output -from langflow.logging.logger import logger -from langflow.schema.data import Data -from langflow.schema.dotdict import dotdict -from langflow.schema.message import Message - - -class PythonFunctionComponent(Component): - display_name = "Python Function" - description = "Define and execute a Python function that returns a Data object or a Message." - icon = "Python" - name = "PythonFunction" - legacy = True - - inputs = [ - CodeInput( - name="function_code", - display_name="Function Code", - info="The code for the function.", - ), - ] - - outputs = [ - Output( - name="function_output", - display_name="Function Callable", - method="get_function_callable", - ), - Output( - name="function_output_data", - display_name="Function Output (Data)", - method="execute_function_data", - ), - Output( - name="function_output_str", - display_name="Function Output (Message)", - method="execute_function_message", - ), - ] - - def get_function_callable(self) -> Callable: - function_code = self.function_code - self.status = function_code - return get_function(function_code) - - def execute_function(self) -> list[dotdict | str] | dotdict | str: - function_code = self.function_code - - if not function_code: - return "No function code provided." - - try: - func = get_function(function_code) - return func() - except Exception as e: # noqa: BLE001 - logger.debug("Error executing function", exc_info=True) - return f"Error executing function: {e}" - - def execute_function_data(self) -> list[Data]: - results = self.execute_function() - results = results if isinstance(results, list) else [results] - return [(Data(text=x) if isinstance(x, str) else Data(**x)) for x in results] - - def execute_function_message(self) -> Message: - results = self.execute_function() - results = results if isinstance(results, list) else [results] - results_list = [str(x) for x in results] - results_str = "\n".join(results_list) - return Message(text=results_str) diff --git a/src/backend/tests/unit/test_experimental_components.py b/src/backend/tests/unit/test_experimental_components.py deleted file mode 100644 index e4f6593f2fbb..000000000000 --- a/src/backend/tests/unit/test_experimental_components.py +++ /dev/null @@ -1,20 +0,0 @@ -from langflow.components import prototypes - - -def test_python_function_component(): - # Arrange - python_function_component = prototypes.PythonFunctionComponent() - - # Act - # function must be a string representation - function = "def function():\n return 'Hello, World!'" - python_function_component.function_code = function - # result is the callable function - result = python_function_component.get_function_callable() - result_message = python_function_component.execute_function_message() - result_data = python_function_component.execute_function_data() - - # Assert - assert result() == "Hello, World!" - assert result_message.text == "Hello, World!" - assert result_data[0].text == "Hello, World!" diff --git a/src/frontend/src/components/core/parameterRenderComponent/helpers/get-placeholder-disabled.ts b/src/frontend/src/components/core/parameterRenderComponent/helpers/get-placeholder-disabled.ts index d60b8364bc3f..7dcf2689b98c 100644 --- a/src/frontend/src/components/core/parameterRenderComponent/helpers/get-placeholder-disabled.ts +++ b/src/frontend/src/components/core/parameterRenderComponent/helpers/get-placeholder-disabled.ts @@ -8,5 +8,6 @@ export const getPlaceholder = ( returnMessage: string = DEFAULT_PLACEHOLDER, ) => { if (disabled) return RECEIVING_INPUT_VALUE; - return returnMessage; + + return returnMessage || DEFAULT_PLACEHOLDER; }; diff --git a/src/frontend/tests/core/features/filterSidebar.spec.ts b/src/frontend/tests/core/features/filterSidebar.spec.ts index 1f3cdd604b75..eb941c46dd54 100644 --- a/src/frontend/tests/core/features/filterSidebar.spec.ts +++ b/src/frontend/tests/core/features/filterSidebar.spec.ts @@ -68,8 +68,6 @@ test( await page.getByTestId("sidebar-legacy-switch").click(); await page.getByTestId("sidebar-options-trigger").click(); - await expect(page.getByTestId("disclosure-prototypes")).toBeVisible(); - await expect(page.getByTestId("input_outputChat Input")).toBeVisible(); await expect(page.getByTestId("input_outputChat Output")).toBeVisible(); await expect(page.getByTestId("processingPrompt Template")).toBeVisible(); @@ -117,7 +115,6 @@ test( await expect(page.getByTestId("disclosure-data")).toBeVisible(); await expect(page.getByTestId("disclosure-helpers")).toBeVisible(); - await expect(page.getByTestId("disclosure-prototypes")).toBeVisible(); await expect(page.getByTestId("disclosure-tools")).toBeVisible(); await expect(page.getByTestId("dataAPI Request")).toBeVisible(); diff --git a/src/frontend/tests/core/unit/codeAreaModalComponent.spec.ts b/src/frontend/tests/core/unit/codeAreaModalComponent.spec.ts index acdf42e94bb1..f52f4b4da99a 100644 --- a/src/frontend/tests/core/unit/codeAreaModalComponent.spec.ts +++ b/src/frontend/tests/core/unit/codeAreaModalComponent.spec.ts @@ -1,4 +1,4 @@ -import { test } from "@playwright/test"; +import { expect, test } from "@playwright/test"; import { addLegacyComponents } from "../../utils/add-legacy-components"; import { awaitBootstrapTest } from "../../utils/await-bootstrap-test"; @@ -9,36 +9,68 @@ test( await awaitBootstrapTest(page); await page.waitForSelector('[data-testid="blank-flow"]', { - timeout: 30000, + timeout: 3000, }); await page.getByTestId("blank-flow").click(); - await page.getByTestId("sidebar-search-input").click(); - await page.getByTestId("sidebar-search-input").fill("python function"); - await page.waitForSelector('[data-testid="sidebar-options-trigger"]', { + await page.getByTestId("canvas_controls_dropdown").click(); + + await page.waitForSelector('[data-testid="zoom_out"]', { timeout: 3000, }); + await page.getByTestId("canvas_controls_dropdown").click(); - await addLegacyComponents(page); + await page.getByTestId("sidebar-custom-component-button").click(); - await page.waitForSelector('[data-testid="prototypesPython Function"]', { + await expect(page.getByTestId("code-button-modal")).toBeVisible({ timeout: 3000, }); - await page.getByTestId("prototypesPython Function").hover(); - await page - .getByTestId("prototypesPython Function") - .getByTestId("icon-Plus") - .click(); - await page.getByTestId("canvas_controls_dropdown").click(); - await page.getByTestId("fit_view").click(); - await page.getByTestId("zoom_out").click(); - await page.getByTestId("canvas_controls_dropdown").click(); + await page.getByTestId("code-button-modal").last().click(); + + const codeInputCode = ` +# from langflow.field_typing import Data +from langflow.custom import Component +from langflow.io import CodeInput, Output +from langflow.schema import Data +from time import sleep +from langflow.schema.message import Message + +class CustomComponent(Component): + display_name = "Custom Component" + description = "Use as a template to create your own component." + documentation: str = "https://docs.langflow.org/components-custom-components" + icon = "custom_components" + name = "CustomComponent" + + inputs = [ + CodeInput( + name="function_code", + display_name="Function Code", + info="The code for the function.", + ), + ] + + outputs = [ + Output(display_name="Output", name="output", method="build_output"), + ] + + def build_output(self) -> Message: + data = Data(value=self.function_code) + self.status = data + sleep(60) + return data`; + + await page.locator(".ace_content").click(); + await page.keyboard.press(`ControlOrMeta+A`); + await page.locator("textarea").fill(codeInputCode); + + await page.getByText("Check & Save").last().click(); await page.getByTestId("div-generic-node").click(); - await page.getByTestId("code-button-modal").click(); + await page.getByTestId("codearea_code_function_code").click(); const wCode = 'def python_function(text: str) -> st: """This is a default python function that returns the input text""" return text'; @@ -53,17 +85,19 @@ class PythonFunctionComponent(CustomComponent): """This is a default python function that returns the input text""" return text`; - await page - .locator("#CodeEditor div") - .filter({ hasText: "PythonFunctionComponent" }) - .nth(1) - .click(); - await page.locator("textarea").press("Control+a"); + await page.locator(".ace_content").click(); + await page.locator("textarea").press("ControlOrMeta+a"); await page.locator("textarea").fill(wCode); await page.locator('//*[@id="checkAndSaveBtn"]').click(); - await page.locator("textarea").press("Control+a"); - await page.locator("textarea").fill(wCode); + await expect( + page.getByText("invalid syntax (, line 1)"), + ).toBeVisible({ timeout: 3000 }); + await page.locator("textarea").press("ControlOrMeta+a"); await page.locator("textarea").fill(customComponentCode); await page.locator('//*[@id="checkAndSaveBtn"]').click(); + await expect(page.getByTestId("codearea_code_function_code")).toHaveText( + customComponentCode, + { timeout: 3000 }, + ); }, );