From 0b91ee5481732b5e856bb60c50c82e9294027bec Mon Sep 17 00:00:00 2001 From: nafer Date: Thu, 26 Feb 2026 15:57:24 +0300 Subject: [PATCH] refactor(docker): simplify Dockerfile and remove custom browser component - Simplified Dockerfile by removing system dependencies, Playwright installation, and unnecessary Python packages - Removed custom browser_use_component.py (now using custom_components directory) - Updated docker-compose to build from Dockerfile and mount custom_components - Retained only essential dependencies: browser-use and langchain-openai --- Dockerfile | 47 +--- browser_use_component.py | 258 ------------------ custom_components/ai/__init__.py | 3 + custom_components/ai/browser_use_component.py | 75 +++++ docker-compose.yml | 8 +- 5 files changed, 87 insertions(+), 304 deletions(-) delete mode 100644 browser_use_component.py create mode 100644 custom_components/ai/__init__.py create mode 100644 custom_components/ai/browser_use_component.py diff --git a/Dockerfile b/Dockerfile index e51a542..a930d41 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,48 +1,7 @@ -# Используем официальный образ Langflow как базу FROM langflowai/langflow:1.7.3 -# Работаем от root -USER root -WORKDIR /app +RUN pip install --no-cache-dir browser-use langchain-openai -# Устанавливаем системные зависимости для Playwright и browser-use -RUN apt-get update && apt-get install -y \ - wget \ - gnupg \ - libglib2.0-0 \ - libnss3 \ - libnspr4 \ - libdbus-1-3 \ - libatk1.0-0 \ - libatk-bridge2.0-0 \ - libcups2 \ - libdrm2 \ - libxkbcommon0 \ - libxcomposite1 \ - libxdamage1 \ - libxfixes3 \ - libxrandr2 \ - libgbm1 \ - libasound2 \ - libpango-1.0-0 \ - libcairo2 \ - && rm -rf /var/lib/apt/lists/* +EXPOSE 7860 -# Устанавливаем Python зависимости -RUN pip install --no-cache-dir \ - browser-use \ - langchain-community \ - playwright \ - httpx \ - langchain-openai \ - openai \ - python-dotenv - -# Устанавливаем Playwright браузеры -RUN playwright install chromium --with-deps - -# Переменные окружения для browserless -ENV BROWSERLESS_API_URL=http://browserless.home - -# По умолчанию запускаем Langflow -CMD ["langflow", "run"] +CMD ["python", "-m", "langflow", "run", "--host", "0.0.0.0", "--port", "7860"] diff --git a/browser_use_component.py b/browser_use_component.py deleted file mode 100644 index 50f0894..0000000 --- a/browser_use_component.py +++ /dev/null @@ -1,258 +0,0 @@ -""" -Browser Use Custom Component для Langflow -Интеграция с browserless + OpenRouter - -Основано на документации из Context7 -""" -import os -from typing import Optional, Dict, Any, List - -from langflow.custom import Component -from langflow.inputs import SecretStrInput, StrInput, DictInput -from langflow.template import Output - - -class BrowserUseComponent(Component): - """ - Browser Use компонент с интеграцией browserless + OpenRouter - - Подключение к browserless через CDP URL. - Использует OpenRouter API для LLM. - """ - display_name = "Browser Use" - description = "browser-use с browserless + OpenRouter" - icon = "Globe" - - inputs = [ - SecretStrInput( - name="browserless_url", - display_name="Browserless URL", - value=os.getenv("BROWSERLESS_API_URL", "http://browserless.home"), - required=True, - helper_text="CDP URL browserless сервера", - ), - StrInput( - name="task", - display_name="Задача", - required=True, - placeholder="Например: найди информацию о...", - ), - StrInput( - name="model", - display_name="OpenRouter Модель", - value="arcee-ai/trinity-large-preview:free", - advanced=True, - helper_text="Модель: anthropic/claude-3-opus, openai/gpt-4o, google/gemini-pro и др.", - ), - SecretStrInput( - name="openrouter_api_key", - display_name="OpenRouter API Key", - value=os.getenv("OPENROUTER_API_KEY", ""), - required=True, - ), - ] - - outputs = [ - Output(name="result", display_name="Результат", method="run_browser"), - ] - - def run_browser(self) -> str: - """ - Запускает browser-use агент с browserless + OpenRouter - """ - try: - from browser_use import Agent, Browser - from langchain_openai import ChatOpenAI - - # URL browserless - browserless_url = self.browserless_url - - # Формируем WebSocket URL для browserless - cdp_url = browserless_url.replace("http://", "ws://").replace("https://", "wss://") - cdp_url = f"{cdp_url}/playwright" - - # Конфигурация браузера с CDP подключением - browser = Browser( - cdp_url=cdp_url, - headless=True, - ) - - # Инициализация LLM через OpenRouter - api_key = self.openrouter_api_key or os.getenv("OPENROUTER_API_KEY") - - llm = ChatOpenAI( - model=self.model, - api_key=api_key, - base_url="https://openrouter.ai/api/v1", - default_headers={ - "HTTP-Referer": "http://langflow.home", - "X-Title": "Langflow", - }, - ) - - # Создание агента - agent = Agent( - task=self.task, - llm=llm, - browser=browser, - ) - - # Запуск агента - result = agent.run() - - return str(result) - - except ImportError as e: - return f"Ошибка импорта: {e}. Убедитесь, что browser-use установлен." - except Exception as e: - return f"Ошибка выполнения: {e}" - - -class BrowserUsePlaywrightComponent(Component): - """ - Альтернативный компонент - Playwright напрямую с browserless - """ - display_name = "Browser Use (Playwright)" - description = "Playwright напрямую с browserless" - icon = "Globe" - - inputs = [ - SecretStrInput( - name="browserless_url", - display_name="Browserless URL", - value=os.getenv("BROWSERLESS_API_URL", "http://browserless.home"), - required=True, - ), - StrInput( - name="url", - display_name="URL", - required=True, - placeholder="https://example.com", - ), - StrInput( - name="action", - display_name="Действие", - value="navigate", - advanced=True, - options=["navigate", "click", "fill", "screenshot", "get_content"], - ), - StrInput( - name="selector", - display_name="CSS Selector", - advanced=True, - ), - StrInput( - name="fill_text", - display_name="Текст для заполнения", - advanced=True, - ), - ] - - outputs = [ - Output(name="result", display_name="Результат", method="run_playwright"), - ] - - async def run_playwright(self) -> Dict[str, Any]: - """Запускает Playwright с browserless""" - from playwright.async_api import async_playwright - - browserless_url = self.browserless_url - ws_endpoint = browserless_url.replace("http://", "ws://").replace("https://", "wss://") - ws_endpoint = f"{ws_endpoint}/playwright?launch=%s" % str({"headless": True}) - - async with async_playwright() as p: - try: - browser = await p.chromium.connect_over_cdp(ws_endpoint) - page = await browser.new_page() - - try: - if self.action == "navigate": - await page.goto(self.url) - content = await page.content() - return {"status": "success", "content": content[:5000]} - - elif self.action == "click": - await page.click(self.selector or "body") - return {"status": "success", "action": "clicked"} - - elif self.action == "fill": - await page.fill(self.selector or "body", self.fill_text or "") - return {"status": "success", "action": "filled"} - - elif self.action == "screenshot": - await page.goto(self.url) - screenshot_bytes = await page.screenshot() - import base64 - return {"status": "success", "screenshot": base64.b64encode(screenshot_bytes).decode()} - - elif self.action == "get_content": - await page.goto(self.url) - content = await page.content() - return {"status": "success", "content": content} - - finally: - await page.close() - await browser.close() - - except Exception as e: - return {"status": "error", "message": str(e)} - - -class SimpleBrowserAgentComponent(Component): - """ - Простой browser-use агент с OpenRouter (локальный браузер) - """ - display_name = "Simple Browser Agent + OpenRouter" - description = "browser-use агент с OpenRouter (локальный браузер)" - icon = "Globe" - - inputs = [ - StrInput( - name="task", - display_name="Задача", - required=True, - ), - StrInput( - name="model", - display_name="Модель", - value="arcee-ai/trinity-large-preview:free", - advanced=True, - ), - SecretStrInput( - name="openrouter_api_key", - display_name="OpenRouter API Key", - value=os.getenv("OPENROUTER_API_KEY", ""), - required=True, - ), - ] - - outputs = [ - Output(name="result", display_name="Результат", method="run_agent"), - ] - - def run_agent(self) -> str: - try: - from browser_use import Agent, Browser - from langchain_openai import ChatOpenAI - - # Локальный браузер - browser = Browser(headless=True) - - api_key = self.openrouter_api_key or os.getenv("OPENROUTER_API_KEY") - - llm = ChatOpenAI( - model=self.model, - api_key=api_key, - base_url="https://openrouter.ai/api/v1", - default_headers={ - "HTTP-Referer": "http://langflow.home", - "X-Title": "Langflow", - }, - ) - - agent = Agent(task=self.task, llm=llm, browser=browser) - result = agent.run() - - return str(result) - except Exception as e: - return f"Ошибка: {e}" diff --git a/custom_components/ai/__init__.py b/custom_components/ai/__init__.py new file mode 100644 index 0000000..ae889f0 --- /dev/null +++ b/custom_components/ai/__init__.py @@ -0,0 +1,3 @@ +from .browser_use_component import BrowserUseComponent + +__all__ = ["BrowserUseComponent"] diff --git a/custom_components/ai/browser_use_component.py b/custom_components/ai/browser_use_component.py new file mode 100644 index 0000000..38f4b83 --- /dev/null +++ b/custom_components/ai/browser_use_component.py @@ -0,0 +1,75 @@ +""" +Browser Use Custom Component для Langflow +""" +import os +from typing import Dict, Any + +from lfx.custom import Component +from lfx.io import SecretStrInput, StrInput +from lfx.template import Output + + +class BrowserUseComponent(Component): + """Browser Use компонент с browserless + OpenRouter""" + display_name = "Browser Use" + description = "browser-use с browserless + OpenRouter" + icon = "Globe" + + inputs = [ + SecretStrInput( + name="browserless_url", + display_name="Browserless URL", + value=os.getenv("BROWSERLESS_API_URL", "http://browserless.home"), + required=True, + ), + StrInput( + name="task", + display_name="Задача", + required=True, + placeholder="Например: найди информацию о...", + ), + StrInput( + name="model", + display_name="OpenRouter Модель", + value="arcee-ai/trinity-large-preview:free", + advanced=True, + ), + ] + + outputs = [ + Output(name="result", display_name="Результат", method="run_browser"), + ] + + def run_browser(self) -> str: + """Запускает browser-use агент""" + try: + from browser_use import Agent, Browser + from langchain_openai import ChatOpenAI + + browserless_url = self.browserless_url or "http://browserless.home" + cdp_url = browserless_url.replace("http://", "ws://").replace("https://", "wss://") + cdp_url = f"{cdp_url}/playwright" + + browser = Browser(cdp_url=cdp_url, headless=True) + + api_key = os.getenv("OPENROUTER_API_KEY", "") + + llm = ChatOpenAI( + model=self.model, + api_key=api_key, + base_url="https://openrouter.ai/api/v1", + default_headers={ + "HTTP-Referer": "http://langflow.home", + "X-Title": "Langflow", + }, + ) + + agent = Agent(task=self.task, llm=llm, browser=browser) + result = agent.run() + + return str(result) + + except ImportError as e: + return f"Ошибка импорта: {e}. Установите browser-use" + except Exception as e: + return f"Ошибка: {e}" diff --git a/docker-compose.yml b/docker-compose.yml index 6828707..0b95afa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,21 +2,25 @@ version: '3.8' services: langflow: - image: langflowai/langflow:1.7.3 + build: + context: . + dockerfile: Dockerfile restart: unless-stopped ports: - "7860:7860" networks: - dokploy-network + volumes: + - ./custom_components:/app/custom_components environment: LANGFLOW_SECRET_KEY: ${LANGFLOW_SECRET_KEY:-changeme} LANGFLOW_HOST: 0.0.0.0 LANGFLOW_PORT: 7860 + LANGFLOW_COMPONENTS_PATH: /app/custom_components OPENROUTER_API_KEY: ${OPENROUTER_API_KEY} OPENROUTER_BASE_URL: https://openrouter.ai/api/v1 BROWSERLESS_API_URL: http://browserless.home BROWSER_HEADLESS: "true" - command: ["python", "-m", "langflow", "run", "--host", "0.0.0.0", "--port", "7860"] healthcheck: test: ["CMD", "curl", "-f", "http://localhost:7860/"] interval: 30s