From d978ae543886b8879006e58058f41aae46c04711 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Wed, 19 Apr 2023 11:02:32 -0300 Subject: [PATCH 01/99] refactor(cache): move cache-related functions to base.py module feat(cache): add support for pandas and PIL Image objects caching fix(interface): import cache-related functions from base.py module test(cache): update import statements in cache-related test file --- src/backend/langflow/api/base.py | 4 ++ src/backend/langflow/cache/__init__.py | 1 + .../langflow/cache/{utils.py => base.py} | 38 ++++++++++++++++++- src/backend/langflow/graph/base.py | 2 +- src/backend/langflow/interface/run.py | 2 +- tests/test_cache.py | 2 +- 6 files changed, 44 insertions(+), 5 deletions(-) rename src/backend/langflow/cache/{utils.py => base.py} (80%) diff --git a/src/backend/langflow/api/base.py b/src/backend/langflow/api/base.py index 084e04d65..8cddc52e4 100644 --- a/src/backend/langflow/api/base.py +++ b/src/backend/langflow/api/base.py @@ -3,6 +3,10 @@ from pydantic import BaseModel, validator from langflow.graph.utils import extract_input_variables_from_prompt +class CacheResponse(BaseModel): + data: dict + + class Code(BaseModel): code: str diff --git a/src/backend/langflow/cache/__init__.py b/src/backend/langflow/cache/__init__.py index e69de29bb..f7aac380b 100644 --- a/src/backend/langflow/cache/__init__.py +++ b/src/backend/langflow/cache/__init__.py @@ -0,0 +1 @@ +from langflow.cache.base import add_pandas, add_image, get # noqa diff --git a/src/backend/langflow/cache/utils.py b/src/backend/langflow/cache/base.py similarity index 80% rename from src/backend/langflow/cache/utils.py rename to src/backend/langflow/cache/base.py index 310f3be80..d96614218 100644 --- a/src/backend/langflow/cache/utils.py +++ b/src/backend/langflow/cache/base.py @@ -1,14 +1,18 @@ -import base64 import contextlib import functools import hashlib + import json import os import tempfile from collections import OrderedDict from pathlib import Path +from typing import Any +from PIL import Image +import dill +import pandas as pd # type: ignore -import dill # type: ignore +CACHE = {} def create_cache_folder(func): @@ -147,3 +151,33 @@ def load_cache(hash_val): with cache_path.open("rb") as cache_file: return dill.load(cache_file) return None + + +def add_pandas(name: str, obj: Any): + if isinstance(obj, (pd.DataFrame, pd.Series)): + CACHE[name] = {"obj": obj, "type": "pandas"} + else: + raise ValueError("Object is not a pandas DataFrame or Series") + + +def add_image(name: str, obj: Any): + if isinstance(obj, Image.Image): + CACHE[name] = {"obj": obj, "type": "image"} + else: + raise ValueError("Object is not a PIL Image") + + +def get(name: str): + return CACHE.get(name, {}).get("obj", None) + + +# get last added item +def get_last(): + obj_dict = list(CACHE.values())[-1] + if obj_dict["type"] == "pandas": + # return a csv string + return obj_dict["obj"].to_csv() + elif obj_dict["type"] == "image": + # return a base64 encoded string + return base64.b64encode(obj_dict["obj"].tobytes()).decode("utf-8") + return obj_dict["obj"] diff --git a/src/backend/langflow/graph/base.py b/src/backend/langflow/graph/base.py index ff586c6da..012250739 100644 --- a/src/backend/langflow/graph/base.py +++ b/src/backend/langflow/graph/base.py @@ -9,7 +9,7 @@ import warnings from copy import deepcopy from typing import Any, Dict, List, Optional -from langflow.cache import utils as cache_utils +from langflow.cache import base as cache_utils from langflow.graph.constants import DIRECT_TYPES from langflow.interface import loading from langflow.interface.listing import ALL_TYPES_DICT diff --git a/src/backend/langflow/interface/run.py b/src/backend/langflow/interface/run.py index 300e09e01..89fd7d784 100644 --- a/src/backend/langflow/interface/run.py +++ b/src/backend/langflow/interface/run.py @@ -2,7 +2,7 @@ import contextlib import io from typing import Any, Dict -from langflow.cache.utils import compute_dict_hash, load_cache, memoize_dict +from langflow.cache.base import compute_dict_hash, load_cache, memoize_dict from langflow.graph.graph import Graph from langflow.interface import loading from langflow.utils.logger import logger diff --git a/tests/test_cache.py b/tests/test_cache.py index 9c6ad30e3..131e015f3 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -3,7 +3,7 @@ import tempfile from pathlib import Path import pytest -from langflow.cache.utils import PREFIX, save_cache +from langflow.cache.base import PREFIX, save_cache from langflow.interface.run import load_langchain_object From e4d0a39b0b5a54f05203c740781ff2e8895ba3e0 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Wed, 19 Apr 2023 11:49:17 -0300 Subject: [PATCH 02/99] refactor: remove unnecessary build and push scripts and Dockerfiles The removed files were unnecessary and were removed to simplify the project. --- build_and_push | 11 -------- src/backend/build.Dockerfile | 52 ----------------------------------- src/backend/build_and_push | 6 ---- src/backend/run | 8 ------ src/frontend/build.Dockerfile | 5 ---- src/frontend/build_and_push | 11 -------- 6 files changed, 93 deletions(-) delete mode 100755 build_and_push delete mode 100644 src/backend/build.Dockerfile delete mode 100755 src/backend/build_and_push delete mode 100755 src/backend/run delete mode 100644 src/frontend/build.Dockerfile delete mode 100755 src/frontend/build_and_push diff --git a/build_and_push b/build_and_push deleted file mode 100755 index e9c9edf14..000000000 --- a/build_and_push +++ /dev/null @@ -1,11 +0,0 @@ -#! /bin/bash - -cd src/frontend -docker build -t logspace/frontend_build -f build.Dockerfile . -cd ../backend -docker build -t logspace/backend_build -f build.Dockerfile . - -cd ../../ -VERSION=$(toml get --toml-path pyproject.toml tool.poetry.version) -docker build --build-arg VERSION=$VERSION -t ibiscp/langflow:$VERSION . -docker push ibiscp/langflow:$VERSION diff --git a/src/backend/build.Dockerfile b/src/backend/build.Dockerfile deleted file mode 100644 index 42452c691..000000000 --- a/src/backend/build.Dockerfile +++ /dev/null @@ -1,52 +0,0 @@ -# `python-base` sets up all our shared environment variables -FROM python:3.10-slim - -# python -ENV PYTHONUNBUFFERED=1 \ - # prevents python creating .pyc files - PYTHONDONTWRITEBYTECODE=1 \ - \ - # pip - PIP_NO_CACHE_DIR=off \ - PIP_DISABLE_PIP_VERSION_CHECK=on \ - PIP_DEFAULT_TIMEOUT=100 \ - \ - # poetry - # https://python-poetry.org/docs/configuration/#using-environment-variables - POETRY_VERSION=1.4.0 \ - # make poetry install to this location - POETRY_HOME="/opt/poetry" \ - # make poetry create the virtual environment in the project's root - # it gets named `.venv` - POETRY_VIRTUALENVS_IN_PROJECT=true \ - # do not ask any interactive question - POETRY_NO_INTERACTION=1 \ - \ - # paths - # this is where our requirements + virtual environment will live - PYSETUP_PATH="/opt/pysetup" \ - VENV_PATH="/opt/pysetup/.venv" - -# prepend poetry and venv to path -ENV PATH="$POETRY_HOME/bin:$VENV_PATH/bin:$PATH" - -RUN apt-get update \ - && apt-get install --no-install-recommends -y \ - # deps for installing poetry - curl \ - # deps for building python deps - build-essential libpq-dev - -# install poetry - respects $POETRY_VERSION & $POETRY_HOME -RUN curl -sSL https://install.python-poetry.org | python3 - - -# copy project requirement files here to ensure they will be cached. -WORKDIR /app -COPY poetry.lock pyproject.toml ./ -COPY langflow/ ./langflow - -# poetry install -RUN poetry install --without dev - -# build wheel -RUN poetry build -f wheel diff --git a/src/backend/build_and_push b/src/backend/build_and_push deleted file mode 100755 index d70366d72..000000000 --- a/src/backend/build_and_push +++ /dev/null @@ -1,6 +0,0 @@ -#! /bin/bash - -docker build -t logspace/backend_build -f build.Dockerfile . -VERSION=$(toml get --toml-path pyproject.toml tool.poetry.version) -docker build --build-arg VERSION=$VERSION -t ibiscp/langflow:$VERSION . -docker push ibiscp/langflow:$VERSION diff --git a/src/backend/run b/src/backend/run deleted file mode 100755 index c9ba24768..000000000 --- a/src/backend/run +++ /dev/null @@ -1,8 +0,0 @@ -#! /bin/bash - -poetry remove langchain -docker build -t logspace/backend_build -f build.Dockerfile . -VERSION=$(toml get --toml-path pyproject.toml tool.poetry.version) -docker build --build-arg VERSION=$VERSION -t ibiscp/langflow:$VERSION . -docker run -p 5003:80 -d ibiscp/langflow:$VERSION -poetry add --editable ../../../langchain diff --git a/src/frontend/build.Dockerfile b/src/frontend/build.Dockerfile deleted file mode 100644 index af2335b5c..000000000 --- a/src/frontend/build.Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM node:14-alpine -WORKDIR /app -COPY . /app -RUN npm install -RUN npm run build \ No newline at end of file diff --git a/src/frontend/build_and_push b/src/frontend/build_and_push deleted file mode 100755 index f223dd343..000000000 --- a/src/frontend/build_and_push +++ /dev/null @@ -1,11 +0,0 @@ -#! /bin/bash - -# Read the contents of the JSON file -json=$(cat package.json) - -# Extract the value of the "version" field using jq -VERSION=$(echo "$json" | jq -r '.version') - -docker build -t logspace/frontend_build -f build.Dockerfile . -docker build --build-arg VERSION=$VERSION -t ibiscp/langflow_frontend:$VERSION . -docker push ibiscp/langflow_frontend:$VERSION From 18b35838504a310c2f06a7c96b401eee854646bc Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Wed, 19 Apr 2023 13:13:58 -0300 Subject: [PATCH 03/99] test(websocket.py): add tests for websocket connection, chat history and sending message --- src/backend/langflow/api/chat.py | 13 ++++ src/backend/langflow/api/chat_manager.py | 99 ++++++++++++++++++++++++ src/backend/langflow/api/schemas.py | 29 +++++++ src/backend/langflow/cache/base.py | 1 + src/backend/langflow/interface/run.py | 6 +- src/backend/langflow/main.py | 2 + tests/conftest.py | 47 +++++++++++ tests/test_graph.py | 36 +-------- tests/test_websocket.py | 30 +++++++ 9 files changed, 226 insertions(+), 37 deletions(-) create mode 100644 src/backend/langflow/api/chat.py create mode 100644 src/backend/langflow/api/chat_manager.py create mode 100644 src/backend/langflow/api/schemas.py create mode 100644 tests/test_websocket.py diff --git a/src/backend/langflow/api/chat.py b/src/backend/langflow/api/chat.py new file mode 100644 index 000000000..e40ac34ca --- /dev/null +++ b/src/backend/langflow/api/chat.py @@ -0,0 +1,13 @@ +from fastapi import APIRouter, WebSocket +from uuid import uuid4 + +from langflow.api.chat_manager import ChatManager + +router = APIRouter() +chat_manager = ChatManager() + + +@router.websocket("/ws") +async def websocket_endpoint(websocket: WebSocket): + client_id = str(uuid4()) + await chat_manager.handle_websocket(client_id, websocket) diff --git a/src/backend/langflow/api/chat_manager.py b/src/backend/langflow/api/chat_manager.py new file mode 100644 index 000000000..17902247b --- /dev/null +++ b/src/backend/langflow/api/chat_manager.py @@ -0,0 +1,99 @@ +from typing import Dict, List +from collections import defaultdict +from fastapi import WebSocket +import json +from langflow.api.schemas import ChatMessage, ChatResponse + +from langflow.interface.run import ( + get_result_and_steps, + load_or_build_langchain_object, +) +from langflow.utils.logger import logger + + +class ChatHistory: + def __init__(self): + self.history: Dict[str, List[ChatMessage]] = defaultdict(list) + + def add_message(self, client_id: str, message: ChatMessage): + self.history[client_id].append(message) + + def get_history(self, client_id: str) -> List[ChatMessage]: + return self.history[client_id] + + +class ChatManager: + def __init__(self): + self.active_connections: Dict[str, WebSocket] = {} + self.chat_history = ChatHistory() + + async def connect(self, client_id: str, websocket: WebSocket): + await websocket.accept() + self.active_connections[client_id] = websocket + + def disconnect(self, client_id: str): + del self.active_connections[client_id] + + async def send_message(self, client_id: str, message: str): + websocket = self.active_connections[client_id] + await websocket.send_text(message) + + async def send_json(self, client_id: str, message: Dict): + websocket = self.active_connections[client_id] + await websocket.send_json(message) + + async def process_message(self, client_id: str, payload: Dict): + # Process the graph data and chat message + + chat_message = payload.pop("message", "") + chat_message = ChatMessage(sender="user", message=chat_message) + graph_data = payload + start_resp = ChatResponse( + sender="bot", message="", type="start", intermediate_steps="" + ) + await self.send_json(client_id, start_resp.dict()) + + is_first_message = len(graph_data.get("chatHistory", [])) == 0 + langchain_object = load_or_build_langchain_object(graph_data, is_first_message) + logger.debug("Loaded langchain object") + + if langchain_object is None: + # Raise user facing error + raise ValueError( + "There was an error loading the langchain_object. Please, check all the nodes and try again." + ) + + # Generate result and thought + logger.debug("Generating result and thought") + result, intermediate_steps = get_result_and_steps( + langchain_object, chat_message.message + ) + + logger.debug("Generated result and intermediate_steps") + # Save the message to chat history + self.chat_history.add_message(client_id, chat_message) + + # Send a response back to the frontend, if needed + response = ChatResponse( + sender="bot", + message=result or "", + intermediate_steps=intermediate_steps or "", + type="end", + ) + await self.send_json(client_id, response.dict()) + + async def handle_websocket(self, client_id: str, websocket: WebSocket): + await self.connect(client_id, websocket) + try: + chat_history = self.chat_history.get_history(client_id) + await websocket.send_text(json.dumps(chat_history)) + + while True: + json_payload = await websocket.receive_text() + payload = json.loads(json_payload) + await self.process_message(client_id, payload) + except Exception as e: + # Handle any exceptions that might occur + print(f"Error: {e}") + finally: + self.disconnect(client_id) diff --git a/src/backend/langflow/api/schemas.py b/src/backend/langflow/api/schemas.py new file mode 100644 index 000000000..588c35287 --- /dev/null +++ b/src/backend/langflow/api/schemas.py @@ -0,0 +1,29 @@ +from typing import Any +from pydantic import BaseModel, validator + + +class ChatMessage(BaseModel): + """Chat message schema.""" + + sender: str + message: str + + @validator("sender") + def sender_must_be_bot_or_you(cls, v): + if v not in ["bot", "you"]: + raise ValueError("sender must be bot or you") + return v + + +class ChatResponse(ChatMessage): + """Chat response schema.""" + + intermediate_steps: str + type: str + data: Any = None + + @validator("type") + def validate_message_type(cls, v): + if v not in ["start", "stream", "end", "error", "info"]: + raise ValueError("type must be start, stream, end, error or info") + return v diff --git a/src/backend/langflow/cache/base.py b/src/backend/langflow/cache/base.py index d96614218..9dd5c1780 100644 --- a/src/backend/langflow/cache/base.py +++ b/src/backend/langflow/cache/base.py @@ -1,3 +1,4 @@ +import base64 import contextlib import functools import hashlib diff --git a/src/backend/langflow/interface/run.py b/src/backend/langflow/interface/run.py index 89fd7d784..f8920724a 100644 --- a/src/backend/langflow/interface/run.py +++ b/src/backend/langflow/interface/run.py @@ -86,7 +86,7 @@ def process_graph(data_graph: Dict[str, Any]): # Generate result and thought logger.debug("Generating result and thought") - result, thought = get_result_and_thought_using_graph(langchain_object, message) + result, thought = get_result_and_steps(langchain_object, message) logger.debug("Generated result and thought") # Save langchain_object to cache @@ -117,7 +117,7 @@ def process_graph_cached(data_graph: Dict[str, Any]): # Generate result and thought logger.debug("Generating result and thought") - result, thought = get_result_and_thought_using_graph(langchain_object, message) + result, thought = get_result_and_steps(langchain_object, message) logger.debug("Generated result and thought") return {"result": str(result), "thought": thought.strip()} @@ -183,7 +183,7 @@ def fix_memory_inputs(langchain_object): update_memory_keys(langchain_object, possible_new_mem_key) -def get_result_and_thought_using_graph(langchain_object, message: str): +def get_result_and_steps(langchain_object, message: str): """Get result and thought from extracted json""" try: if hasattr(langchain_object, "verbose"): diff --git a/src/backend/langflow/main.py b/src/backend/langflow/main.py index 176e46236..c1f2decd5 100644 --- a/src/backend/langflow/main.py +++ b/src/backend/langflow/main.py @@ -3,6 +3,7 @@ from fastapi.middleware.cors import CORSMiddleware from langflow.api.endpoints import router as endpoints_router from langflow.api.validate import router as validate_router +from langflow.api.chat import router as chat_router def create_app(): @@ -23,6 +24,7 @@ def create_app(): app.include_router(endpoints_router) app.include_router(validate_router) + app.include_router(chat_router) return app diff --git a/tests/conftest.py b/tests/conftest.py index e6eb3562f..15da0d1ef 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,8 @@ +import json from pathlib import Path +from typing import AsyncGenerator +from httpx import AsyncClient + import pytest from fastapi.testclient import TestClient @@ -21,6 +25,15 @@ def get_text(): """ +@pytest.fixture() +async def async_client() -> AsyncGenerator: + from langflow.main import create_app + + app = create_app() + async with AsyncClient(app=app, base_url="http://testserver") as client: + yield client + + # Create client fixture for FastAPI @pytest.fixture(scope="module") def client(): @@ -30,3 +43,37 @@ def client(): with TestClient(app) as client: yield client + + +def get_graph(_type="basic"): + """Get a graph from a json file""" + from langflow.graph.graph import Graph + + if _type == "basic": + path = pytest.BASIC_EXAMPLE_PATH + elif _type == "complex": + path = pytest.COMPLEX_EXAMPLE_PATH + elif _type == "openapi": + path = pytest.OPENAPI_EXAMPLE_PATH + + with open(path, "r") as f: + flow_graph = json.load(f) + data_graph = flow_graph["data"] + nodes = data_graph["nodes"] + edges = data_graph["edges"] + return Graph(nodes, edges) + + +@pytest.fixture +def basic_graph(): + return get_graph() + + +@pytest.fixture +def complex_graph(): + return get_graph("complex") + + +@pytest.fixture +def openapi_graph(): + return get_graph("openapi") diff --git a/tests/test_graph.py b/tests/test_graph.py index c007bdd78..76451fe6a 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -15,7 +15,7 @@ from langflow.graph.nodes import ( ToolNode, WrapperNode, ) -from langflow.interface.run import get_result_and_thought_using_graph +from langflow.interface.run import get_result_and_steps from langflow.utils.payload import build_json, get_root_node # Test cases for the graph module @@ -24,38 +24,6 @@ from langflow.utils.payload import build_json, get_root_node # BASIC_EXAMPLE_PATH, COMPLEX_EXAMPLE_PATH, OPENAPI_EXAMPLE_PATH -def get_graph(_type="basic"): - """Get a graph from a json file""" - if _type == "basic": - path = pytest.BASIC_EXAMPLE_PATH - elif _type == "complex": - path = pytest.COMPLEX_EXAMPLE_PATH - elif _type == "openapi": - path = pytest.OPENAPI_EXAMPLE_PATH - - with open(path, "r") as f: - flow_graph = json.load(f) - data_graph = flow_graph["data"] - nodes = data_graph["nodes"] - edges = data_graph["edges"] - return Graph(nodes, edges) - - -@pytest.fixture -def basic_graph(): - return get_graph() - - -@pytest.fixture -def complex_graph(): - return get_graph("complex") - - -@pytest.fixture -def openapi_graph(): - return get_graph("openapi") - - def get_node_by_type(graph, node_type: Type[Node]) -> Union[Node, None]: """Get a node by type""" return next((node for node in graph.nodes if isinstance(node, node_type)), None) @@ -441,7 +409,7 @@ def test_get_result_and_thought(basic_graph): # now build again and check if FakeListLLM was used # Get the result and thought - result, thought = get_result_and_thought_using_graph(langchain_object, message) + result, thought = get_result_and_steps(langchain_object, message) # The result should be a str assert isinstance(result, str) # The thought should be a Thought diff --git a/tests/test_websocket.py b/tests/test_websocket.py new file mode 100644 index 000000000..9ce20bc45 --- /dev/null +++ b/tests/test_websocket.py @@ -0,0 +1,30 @@ +import json + + +def test_websocket_connection(client): + with client.websocket_connect("/ws") as websocket: + assert websocket.client == client + assert websocket.url.path == "/ws" + + +def test_chat_history(client): + chat_history = ["Test message 1", "Test message 2"] + + with client.websocket_connect("/ws") as websocket: + received_history = websocket.receive_text() + received_history = json.loads(received_history) + + assert received_history == chat_history + + +def test_send_message(client, basic_graph): + with client.websocket_connect("/ws") as websocket: + # Send the JSON payload through the WebSocket connection + websocket.send_text(basic_graph) + + # Receive and parse the response from the server + response = websocket.receive_text() + response = json.loads(response) + + # Test that the response is as expected + assert response == "Your response message here" From 9b1f86b68165c689c90924271647ca20f4daa01c Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Wed, 19 Apr 2023 14:20:05 -0300 Subject: [PATCH 04/99] refactor(chatComponent): simplify conditional statement in Chat component's error handling logic --- src/frontend/src/components/chatComponent/index.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/frontend/src/components/chatComponent/index.tsx b/src/frontend/src/components/chatComponent/index.tsx index 11516f004..1febb7539 100644 --- a/src/frontend/src/components/chatComponent/index.tsx +++ b/src/frontend/src/components/chatComponent/index.tsx @@ -93,7 +93,7 @@ export default function Chat({ flow, reactFlowInstance }: ChatType) { (errors: Array, t) => errors.concat( (template[t].required && template[t].show) && - (!template[t].value || template[t].value === "") && + (!template[t].value && template[t].value !== false && template[t].value === "") && !reactFlowInstance .getEdges() .some( @@ -102,12 +102,11 @@ export default function Chat({ flow, reactFlowInstance }: ChatType) { e.targetHandle.split("|")[2] === n.id ) ? [ - `${type} is missing ${ - template.display_name - ? template.display_name - : snakeToNormalCase(template[t].name) - }.`, - ] + `${type} is missing ${template.display_name + ? template.display_name + : snakeToNormalCase(template[t].name) + }.`, + ] : [] ), [] as string[] From 7d183ff57ed1fdc23ee9993a8d563925c6ad52c0 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Wed, 19 Apr 2023 21:28:05 -0300 Subject: [PATCH 05/99] refactor(chat.py, chat_manager.py, schemas.py, run.py): add chat history to ChatManager and ChatMessage schema feat(chat.py, chat_manager.py): add error handling for async_get_result_and_steps feat(chat.py): add client_id to websocket endpoint feat(schemas.py): add data_type field to ChatResponse schema refactor(run.py): memoize build_langchain_object_with_caching function with maxsize of 10 --- src/backend/langflow/api/chat.py | 5 +-- src/backend/langflow/api/chat_manager.py | 44 ++++++++++-------- src/backend/langflow/api/schemas.py | 5 ++- src/backend/langflow/interface/run.py | 57 +++++++++++++++++++++++- 4 files changed, 87 insertions(+), 24 deletions(-) diff --git a/src/backend/langflow/api/chat.py b/src/backend/langflow/api/chat.py index e40ac34ca..11b861c77 100644 --- a/src/backend/langflow/api/chat.py +++ b/src/backend/langflow/api/chat.py @@ -7,7 +7,6 @@ router = APIRouter() chat_manager = ChatManager() -@router.websocket("/ws") -async def websocket_endpoint(websocket: WebSocket): - client_id = str(uuid4()) +@router.websocket("/ws/{client_id}") +async def websocket_endpoint(client_id: str, websocket: WebSocket): await chat_manager.handle_websocket(client_id, websocket) diff --git a/src/backend/langflow/api/chat_manager.py b/src/backend/langflow/api/chat_manager.py index 17902247b..384de4d12 100644 --- a/src/backend/langflow/api/chat_manager.py +++ b/src/backend/langflow/api/chat_manager.py @@ -5,7 +5,7 @@ import json from langflow.api.schemas import ChatMessage, ChatResponse from langflow.interface.run import ( - get_result_and_steps, + async_get_result_and_steps, load_or_build_langchain_object, ) from langflow.utils.logger import logger @@ -38,22 +38,25 @@ class ChatManager: websocket = self.active_connections[client_id] await websocket.send_text(message) - async def send_json(self, client_id: str, message: Dict): + async def send_json(self, client_id: str, message: ChatMessage): websocket = self.active_connections[client_id] - await websocket.send_json(message) + self.chat_history.add_message(client_id, message) + await websocket.send_json(message.dict()) async def process_message(self, client_id: str, payload: Dict): # Process the graph data and chat message chat_message = payload.pop("message", "") chat_message = ChatMessage(sender="user", message=chat_message) + self.chat_history.add_message(client_id, chat_message) + graph_data = payload start_resp = ChatResponse( - sender="bot", message="", type="start", intermediate_steps="" + sender="bot", message=None, type="start", intermediate_steps="" ) - await self.send_json(client_id, start_resp.dict()) + await self.send_json(client_id, start_resp) - is_first_message = len(graph_data.get("chatHistory", [])) == 0 + is_first_message = len(self.chat_history.get_history(client_id=client_id)) == 0 langchain_object = load_or_build_langchain_object(graph_data, is_first_message) logger.debug("Loaded langchain object") @@ -64,15 +67,20 @@ class ChatManager: ) # Generate result and thought - logger.debug("Generating result and thought") - result, intermediate_steps = get_result_and_steps( - langchain_object, chat_message.message - ) - - logger.debug("Generated result and intermediate_steps") - # Save the message to chat history - self.chat_history.add_message(client_id, chat_message) - + try: + logger.debug("Generating result and thought") + result, intermediate_steps = await async_get_result_and_steps( + langchain_object, chat_message.message or "" + ) + logger.debug("Generated result and intermediate_steps") + except Exception as e: + # Log stack trace + logger.exception(e) + error_resp = ChatResponse( + sender="bot", message=str(e), type="error", intermediate_steps="" + ) + await self.send_json(client_id, error_resp) + return # Send a response back to the frontend, if needed response = ChatResponse( sender="bot", @@ -80,16 +88,16 @@ class ChatManager: intermediate_steps=intermediate_steps or "", type="end", ) - await self.send_json(client_id, response.dict()) + await self.send_json(client_id, response) async def handle_websocket(self, client_id: str, websocket: WebSocket): await self.connect(client_id, websocket) try: chat_history = self.chat_history.get_history(client_id) - await websocket.send_text(json.dumps(chat_history)) + await websocket.send_json(json.dumps(chat_history)) while True: - json_payload = await websocket.receive_text() + json_payload = await websocket.receive_json() payload = json.loads(json_payload) await self.process_message(client_id, payload) except Exception as e: diff --git a/src/backend/langflow/api/schemas.py b/src/backend/langflow/api/schemas.py index 588c35287..fd9ef0816 100644 --- a/src/backend/langflow/api/schemas.py +++ b/src/backend/langflow/api/schemas.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, Union from pydantic import BaseModel, validator @@ -6,7 +6,7 @@ class ChatMessage(BaseModel): """Chat message schema.""" sender: str - message: str + message: Union[str, None] = None @validator("sender") def sender_must_be_bot_or_you(cls, v): @@ -21,6 +21,7 @@ class ChatResponse(ChatMessage): intermediate_steps: str type: str data: Any = None + data_type: str = "" @validator("type") def validate_message_type(cls, v): diff --git a/src/backend/langflow/interface/run.py b/src/backend/langflow/interface/run.py index f8920724a..110d3827f 100644 --- a/src/backend/langflow/interface/run.py +++ b/src/backend/langflow/interface/run.py @@ -31,7 +31,7 @@ def load_or_build_langchain_object(data_graph, is_first_message=False): return build_langchain_object_with_caching(data_graph) -@memoize_dict(maxsize=1) +@memoize_dict(maxsize=10) def build_langchain_object_with_caching(data_graph): """ Build langchain object from data_graph. @@ -235,6 +235,61 @@ def get_result_and_steps(langchain_object, message: str): return result, thought +async def async_get_result_and_steps(langchain_object, message: str): + """Get result and thought from extracted json""" + try: + if hasattr(langchain_object, "verbose"): + langchain_object.verbose = True + chat_input = None + memory_key = "" + if hasattr(langchain_object, "memory") and langchain_object.memory is not None: + memory_key = langchain_object.memory.memory_key + + if hasattr(langchain_object, "input_keys"): + for key in langchain_object.input_keys: + if key not in [memory_key, "chat_history"]: + chat_input = {key: message} + else: + chat_input = message # type: ignore + + if hasattr(langchain_object, "return_intermediate_steps"): + # https://github.com/hwchase17/langchain/issues/2068 + # Deactivating until we have a frontend solution + # to display intermediate steps + langchain_object.return_intermediate_steps = False + + fix_memory_inputs(langchain_object) + + with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer): + try: + if hasattr(langchain_object, "acall"): + output = await langchain_object.acall(chat_input) + else: + output = langchain_object(chat_input) + except ValueError as exc: + # make the error message more informative + logger.debug(f"Error: {str(exc)}") + output = langchain_object.run(chat_input) + + intermediate_steps = ( + output.get("intermediate_steps", []) if isinstance(output, dict) else [] + ) + + result = ( + output.get(langchain_object.output_keys[0]) + if isinstance(output, dict) + else output + ) + if intermediate_steps: + thought = format_intermediate_steps(intermediate_steps) + else: + thought = output_buffer.getvalue() + + except Exception as exc: + raise ValueError(f"Error: {str(exc)}") from exc + return result, thought + + def get_result_and_thought(extracted_json: Dict[str, Any], message: str): """Get result and thought from extracted json""" try: From 0a630cd70daa593f74ea3dbe700058dc811d906d Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Wed, 19 Apr 2023 22:23:31 -0300 Subject: [PATCH 06/99] refactor(chat_manager.py): move process_graph function outside of ChatManager class test(websocket.py): add tests for websocket connection, chat history, and sending messages --- src/backend/langflow/api/chat_manager.py | 46 +++++++++++------ tests/test_websocket.py | 63 ++++++++++++++++-------- 2 files changed, 74 insertions(+), 35 deletions(-) diff --git a/src/backend/langflow/api/chat_manager.py b/src/backend/langflow/api/chat_manager.py index 384de4d12..7ca04abf7 100644 --- a/src/backend/langflow/api/chat_manager.py +++ b/src/backend/langflow/api/chat_manager.py @@ -41,13 +41,13 @@ class ChatManager: async def send_json(self, client_id: str, message: ChatMessage): websocket = self.active_connections[client_id] self.chat_history.add_message(client_id, message) - await websocket.send_json(message.dict()) + await websocket.send_json(json.dumps(message.dict())) async def process_message(self, client_id: str, payload: Dict): # Process the graph data and chat message chat_message = payload.pop("message", "") - chat_message = ChatMessage(sender="user", message=chat_message) + chat_message = ChatMessage(sender="you", message=chat_message) self.chat_history.add_message(client_id, chat_message) graph_data = payload @@ -57,22 +57,14 @@ class ChatManager: await self.send_json(client_id, start_resp) is_first_message = len(self.chat_history.get_history(client_id=client_id)) == 0 - langchain_object = load_or_build_langchain_object(graph_data, is_first_message) - logger.debug("Loaded langchain object") - - if langchain_object is None: - # Raise user facing error - raise ValueError( - "There was an error loading the langchain_object. Please, check all the nodes and try again." - ) - # Generate result and thought try: logger.debug("Generating result and thought") - result, intermediate_steps = await async_get_result_and_steps( - langchain_object, chat_message.message or "" + result, intermediate_steps = await process_graph( + graph_data=graph_data, + is_first_message=is_first_message, + chat_message=chat_message, ) - logger.debug("Generated result and intermediate_steps") except Exception as e: # Log stack trace logger.exception(e) @@ -105,3 +97,29 @@ class ChatManager: print(f"Error: {e}") finally: self.disconnect(client_id) + + +async def process_graph( + graph_data: Dict, is_first_message: bool, chat_message: ChatMessage +): + langchain_object = load_or_build_langchain_object(graph_data, is_first_message) + logger.debug("Loaded langchain object") + + if langchain_object is None: + # Raise user facing error + raise ValueError( + "There was an error loading the langchain_object. Please, check all the nodes and try again." + ) + + # Generate result and thought + try: + logger.debug("Generating result and thought") + result, intermediate_steps = await async_get_result_and_steps( + langchain_object, chat_message.message or "" + ) + logger.debug("Generated result and intermediate_steps") + return result, intermediate_steps + except Exception as e: + # Log stack trace + logger.exception(e) + raise e diff --git a/tests/test_websocket.py b/tests/test_websocket.py index 9ce20bc45..41405867f 100644 --- a/tests/test_websocket.py +++ b/tests/test_websocket.py @@ -1,30 +1,51 @@ import json +from unittest.mock import patch +from langflow.api.schemas import ChatMessage +from fastapi.testclient import TestClient -def test_websocket_connection(client): - with client.websocket_connect("/ws") as websocket: - assert websocket.client == client - assert websocket.url.path == "/ws" +def test_websocket_connection(client: TestClient): + with client.websocket_connect("/ws/test_client") as websocket: + assert websocket.scope["client"] == ["testclient", 50000] + assert websocket.scope["path"] == "/ws/test_client" -def test_chat_history(client): - chat_history = ["Test message 1", "Test message 2"] +def test_chat_history(client: TestClient): + chat_history = [] - with client.websocket_connect("/ws") as websocket: - received_history = websocket.receive_text() - received_history = json.loads(received_history) + # Mock the process_graph function to return a specific value + with patch("langflow.api.chat_manager.process_graph") as mock_process_graph: + mock_process_graph.return_value = ("Hello, I'm a mock response!", "") - assert received_history == chat_history + with client.websocket_connect("/ws/test_client") as websocket: + # First message should be the history + history = websocket.receive_json() + assert json.loads(history) == [] # Empty history + # Send a message + payload = {"message": "Hello"} + websocket.send_json(json.dumps(payload)) + # Receive the response from the server + response = websocket.receive_json() + assert json.loads(response) == { + "sender": "bot", + "message": None, + "intermediate_steps": "", + "type": "start", + "data": None, + "data_type": "", + } + # Send another message + payload = {"message": "How are you?"} + websocket.send_json(json.dumps(payload)) -def test_send_message(client, basic_graph): - with client.websocket_connect("/ws") as websocket: - # Send the JSON payload through the WebSocket connection - websocket.send_text(basic_graph) - - # Receive and parse the response from the server - response = websocket.receive_text() - response = json.loads(response) - - # Test that the response is as expected - assert response == "Your response message here" + # Receive the response from the server + response = websocket.receive_json() + assert json.loads(response) == { + "sender": "bot", + "message": "Hello, I'm a mock response!", + "intermediate_steps": "", + "type": "end", + "data": None, + "data_type": "", + } From 3da30cc5bffd0681d9ebb29e424287e45cad6798 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Thu, 20 Apr 2023 11:09:11 -0300 Subject: [PATCH 07/99] refactor(cache): move cache functionality to a separate class feat(cache): add support for multiple clients and context manager to set client_id feat(cache): add observer pattern to notify on cache changes feat(cache): add async observer pattern to notify on cache changes in async functions feat(cache): add methods to add pandas DataFrame or Series and PIL Image to cache feat(cache): add method to get an object from cache by key feat(cache): add method to get the last added item in cache --- src/backend/langflow/cache/__init__.py | 2 +- src/backend/langflow/cache/base.py | 30 ------ src/backend/langflow/cache/manager.py | 126 +++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 31 deletions(-) create mode 100644 src/backend/langflow/cache/manager.py diff --git a/src/backend/langflow/cache/__init__.py b/src/backend/langflow/cache/__init__.py index f7aac380b..583d5ac6d 100644 --- a/src/backend/langflow/cache/__init__.py +++ b/src/backend/langflow/cache/__init__.py @@ -1 +1 @@ -from langflow.cache.base import add_pandas, add_image, get # noqa +from langflow.cache.manager import cache_manager # noqa diff --git a/src/backend/langflow/cache/base.py b/src/backend/langflow/cache/base.py index 9dd5c1780..ba250da6b 100644 --- a/src/backend/langflow/cache/base.py +++ b/src/backend/langflow/cache/base.py @@ -152,33 +152,3 @@ def load_cache(hash_val): with cache_path.open("rb") as cache_file: return dill.load(cache_file) return None - - -def add_pandas(name: str, obj: Any): - if isinstance(obj, (pd.DataFrame, pd.Series)): - CACHE[name] = {"obj": obj, "type": "pandas"} - else: - raise ValueError("Object is not a pandas DataFrame or Series") - - -def add_image(name: str, obj: Any): - if isinstance(obj, Image.Image): - CACHE[name] = {"obj": obj, "type": "image"} - else: - raise ValueError("Object is not a PIL Image") - - -def get(name: str): - return CACHE.get(name, {}).get("obj", None) - - -# get last added item -def get_last(): - obj_dict = list(CACHE.values())[-1] - if obj_dict["type"] == "pandas": - # return a csv string - return obj_dict["obj"].to_csv() - elif obj_dict["type"] == "image": - # return a base64 encoded string - return base64.b64encode(obj_dict["obj"].tobytes()).decode("utf-8") - return obj_dict["obj"] diff --git a/src/backend/langflow/cache/manager.py b/src/backend/langflow/cache/manager.py new file mode 100644 index 000000000..ba34a3a8d --- /dev/null +++ b/src/backend/langflow/cache/manager.py @@ -0,0 +1,126 @@ +from contextlib import contextmanager +from typing import Any, Awaitable, Callable, List +from PIL import Image +import pandas as pd + + +class Subject: + """Base class for implementing the observer pattern.""" + + def __init__(self): + self.observers: List[Callable[[], None]] = [] + + def attach(self, observer: Callable[[], None]): + """Attach an observer to the subject.""" + self.observers.append(observer) + + def detach(self, observer: Callable[[], None]): + """Detach an observer from the subject.""" + self.observers.remove(observer) + + def notify(self): + """Notify all observers about an event.""" + for observer in self.observers: + if observer is None: + continue + observer() + + +class AsyncSubject: + """Base class for implementing the async observer pattern.""" + + def __init__(self): + self.observers: List[Callable[[], Awaitable]] = [] + + def attach(self, observer: Callable[[], Awaitable]): + """Attach an observer to the subject.""" + self.observers.append(observer) + + def detach(self, observer: Callable[[], Awaitable]): + """Detach an observer from the subject.""" + self.observers.remove(observer) + + async def notify(self): + """Notify all observers about an event.""" + for observer in self.observers: + if observer is None: + continue + await observer() + + +class CacheManager(Subject): + """Manages cache for different clients and notifies observers on changes.""" + + def __init__(self): + super().__init__() + self.CACHE = {} + self.current_client_id = None + + @contextmanager + def set_client_id(self, client_id: str): + """ + Context manager to set the current client_id and associated cache. + + Args: + client_id (str): The client identifier. + """ + previous_client_id = self.current_client_id + self.current_client_id = client_id + self.current_cache = self.CACHE.setdefault(client_id, {}) + try: + yield + finally: + self.current_client_id = previous_client_id + self.current_cache = self.CACHE.get(self.current_client_id, {}) + + def add_pandas(self, name: str, obj: Any): + """ + Add a pandas DataFrame or Series to the current client's cache. + + Args: + name (str): The cache key. + obj (Any): The pandas DataFrame or Series object. + """ + if isinstance(obj, (pd.DataFrame, pd.Series)): + self.current_cache[name] = {"obj": obj, "type": "pandas"} + self.notify() + else: + raise ValueError("Object is not a pandas DataFrame or Series") + + def add_image(self, name: str, obj: Any): + """ + Add a PIL Image to the current client's cache. + + Args: + name (str): The cache key. + obj (Any): The PIL Image object. + """ + if isinstance(obj, Image.Image): + self.current_cache[name] = {"obj": obj, "type": "image"} + self.notify() + else: + raise ValueError("Object is not a PIL Image") + + def get(self, name: str): + """ + Get an object from the current client's cache. + + Args: + name (str): The cache key. + + Returns: + The cached object associated with the given cache key. + """ + return self.current_cache[name] + + def get_last(self): + """ + Get the last added item in the current client's cache. + + Returns: + The last added item in the cache. + """ + return list(self.current_cache.values())[-1] + + +cache_manager = CacheManager() From 5169c0bc27960b678961fa94fed066a648c8efa4 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Thu, 20 Apr 2023 11:09:42 -0300 Subject: [PATCH 08/99] feat(chat_manager.py): add support for sending file responses fix(schemas.py): add validation for file response type and data type test(test_websocket.py): remove data and data_type fields from ChatResponse messages in tests --- src/backend/langflow/api/chat_manager.py | 69 +++++++++++++++++++++--- src/backend/langflow/api/schemas.py | 20 +++++-- tests/test_websocket.py | 4 -- 3 files changed, 77 insertions(+), 16 deletions(-) diff --git a/src/backend/langflow/api/chat_manager.py b/src/backend/langflow/api/chat_manager.py index 7ca04abf7..8dcaf05ac 100644 --- a/src/backend/langflow/api/chat_manager.py +++ b/src/backend/langflow/api/chat_manager.py @@ -1,22 +1,30 @@ +import asyncio +import base64 +from io import BytesIO from typing import Dict, List from collections import defaultdict from fastapi import WebSocket import json -from langflow.api.schemas import ChatMessage, ChatResponse +from langflow.api.schemas import ChatMessage, ChatResponse, FileResponse +from langflow.cache.manager import AsyncSubject from langflow.interface.run import ( async_get_result_and_steps, load_or_build_langchain_object, ) from langflow.utils.logger import logger +from langflow.cache import cache_manager +from PIL.Image import Image -class ChatHistory: +class ChatHistory(AsyncSubject): def __init__(self): + super().__init__() self.history: Dict[str, List[ChatMessage]] = defaultdict(list) - def add_message(self, client_id: str, message: ChatMessage): + async def add_message(self, client_id: str, message: ChatMessage): self.history[client_id].append(message) + await self.notify() def get_history(self, client_id: str) -> List[ChatMessage]: return self.history[client_id] @@ -26,6 +34,44 @@ class ChatManager: def __init__(self): self.active_connections: Dict[str, WebSocket] = {} self.chat_history = ChatHistory() + self.chat_history.attach(self.on_chat_history_update) + self.cache_manager = cache_manager + self.cache_manager.attach(self.update) + + async def on_chat_history_update(self): + """Send the last chat message to the client.""" + client_id = self.cache_manager.current_client_id + if client_id in self.active_connections: + chat_response = self.chat_history.get_history(client_id)[-1] + if chat_response.sender == "bot": + # Process FileResponse + if isinstance(chat_response, FileResponse): + # If data_type is pandas, convert to csv + if chat_response.data_type == "pandas": + chat_response.data = chat_response.data.to_csv() + elif chat_response.data_type == "image": + # Base64 encode the image + chat_response.data = pil_to_base64(chat_response.data) + + await self.send_json(client_id, chat_response) + + def update(self): + if self.cache_manager.current_client_id in self.active_connections: + self.last_cached_object_dict = self.cache_manager.get_last() + # Add a new ChatResponse with the data + chat_response = FileResponse( + sender="bot", + message=None, + type="file", + data=self.last_cached_object_dict["obj"], + data_type=self.last_cached_object_dict["type"], + ) + + asyncio.create_task( + self.chat_history.add_message( + self.cache_manager.current_client_id, chat_response + ) + ) async def connect(self, client_id: str, websocket: WebSocket): await websocket.accept() @@ -40,7 +86,6 @@ class ChatManager: async def send_json(self, client_id: str, message: ChatMessage): websocket = self.active_connections[client_id] - self.chat_history.add_message(client_id, message) await websocket.send_json(json.dumps(message.dict())) async def process_message(self, client_id: str, payload: Dict): @@ -48,13 +93,13 @@ class ChatManager: chat_message = payload.pop("message", "") chat_message = ChatMessage(sender="you", message=chat_message) - self.chat_history.add_message(client_id, chat_message) + await self.chat_history.add_message(client_id, chat_message) graph_data = payload start_resp = ChatResponse( sender="bot", message=None, type="start", intermediate_steps="" ) - await self.send_json(client_id, start_resp) + await self.chat_history.add_message(client_id, start_resp) is_first_message = len(self.chat_history.get_history(client_id=client_id)) == 0 # Generate result and thought @@ -80,7 +125,7 @@ class ChatManager: intermediate_steps=intermediate_steps or "", type="end", ) - await self.send_json(client_id, response) + await self.chat_history.add_message(client_id, response) async def handle_websocket(self, client_id: str, websocket: WebSocket): await self.connect(client_id, websocket) @@ -91,7 +136,8 @@ class ChatManager: while True: json_payload = await websocket.receive_json() payload = json.loads(json_payload) - await self.process_message(client_id, payload) + with self.cache_manager.set_client_id(client_id): + await self.process_message(client_id, payload) except Exception as e: # Handle any exceptions that might occur print(f"Error: {e}") @@ -123,3 +169,10 @@ async def process_graph( # Log stack trace logger.exception(e) raise e + + +def pil_to_base64(image: Image) -> str: + buffered = BytesIO() + image.save(buffered, format="PNG") + img_str = base64.b64encode(buffered.getvalue()) + return img_str.decode("utf-8") diff --git a/src/backend/langflow/api/schemas.py b/src/backend/langflow/api/schemas.py index fd9ef0816..1aefe5c8e 100644 --- a/src/backend/langflow/api/schemas.py +++ b/src/backend/langflow/api/schemas.py @@ -20,11 +20,23 @@ class ChatResponse(ChatMessage): intermediate_steps: str type: str - data: Any = None - data_type: str = "" @validator("type") def validate_message_type(cls, v): - if v not in ["start", "stream", "end", "error", "info"]: - raise ValueError("type must be start, stream, end, error or info") + if v not in ["start", "stream", "end", "error", "info", "file"]: + raise ValueError("type must be start, stream, end, error, info, or file") + return v + + +class FileResponse(ChatMessage): + """File response schema.""" + + data: Any + data_type: str + type: str = "file" + + @validator("data_type") + def validate_data_type(cls, v): + if v not in ["image", "csv"]: + raise ValueError("data_type must be image or csv") return v diff --git a/tests/test_websocket.py b/tests/test_websocket.py index 41405867f..74e147075 100644 --- a/tests/test_websocket.py +++ b/tests/test_websocket.py @@ -32,8 +32,6 @@ def test_chat_history(client: TestClient): "message": None, "intermediate_steps": "", "type": "start", - "data": None, - "data_type": "", } # Send another message payload = {"message": "How are you?"} @@ -46,6 +44,4 @@ def test_chat_history(client: TestClient): "message": "Hello, I'm a mock response!", "intermediate_steps": "", "type": "end", - "data": None, - "data_type": "", } From ebc1f6a0dfef42e389c0db01678cb569d6bad809 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Sun, 23 Apr 2023 14:31:21 -0300 Subject: [PATCH 09/99] feat(api): add callback handler for streaming LLM responses Add a new file `callback.py` that contains a new class `StreamingLLMCallbackHandler` that inherits from `AsyncCallbackHandler`. This class handles streaming LLM responses. It has a constructor that takes a `websocket` parameter and sets it as an instance variable. It also has an `on_llm_new_token` method that takes a `token` parameter and sends a `ChatResponse` object to the `websocket` instance variable. Update `chat_manager.py` to import the new `StreamingLLMCallbackHandler` class. Add a new function `try_setting_streaming_options` that takes a `langchain_object` and a `websocket` parameter. This function checks if the `llm` attribute of the `langchain_object` is an instance of `OpenAI`, `ChatOpenAI`, `AzureOpenAI`, or `AzureChatOpenAI`. If it is, it sets the --- src/backend/langflow/api/callback.py | 18 ++++++++++++ src/backend/langflow/api/chat.py | 5 ++-- src/backend/langflow/api/chat_manager.py | 36 ++++++++++++++++++++++-- 3 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 src/backend/langflow/api/callback.py diff --git a/src/backend/langflow/api/callback.py b/src/backend/langflow/api/callback.py new file mode 100644 index 000000000..47a8d945c --- /dev/null +++ b/src/backend/langflow/api/callback.py @@ -0,0 +1,18 @@ +from typing import Any +from langchain.callbacks.base import AsyncCallbackHandler + +from langflow.api.schemas import ChatResponse + + +# https://github.com/hwchase17/chat-langchain/blob/master/callback.py +class StreamingLLMCallbackHandler(AsyncCallbackHandler): + """Callback handler for streaming LLM responses.""" + + def __init__(self, websocket): + self.websocket = websocket + + async def on_llm_new_token(self, token: str, **kwargs: Any) -> None: + resp = ChatResponse( + sender="bot", message=token, type="stream", intermediate_steps="" + ) + await self.websocket.send_json(resp.dict()) diff --git a/src/backend/langflow/api/chat.py b/src/backend/langflow/api/chat.py index 11b861c77..b2da73d52 100644 --- a/src/backend/langflow/api/chat.py +++ b/src/backend/langflow/api/chat.py @@ -1,5 +1,4 @@ from fastapi import APIRouter, WebSocket -from uuid import uuid4 from langflow.api.chat_manager import ChatManager @@ -7,6 +6,8 @@ router = APIRouter() chat_manager = ChatManager() -@router.websocket("/ws/{client_id}") +@router.websocket("/chat/{client_id}") async def websocket_endpoint(client_id: str, websocket: WebSocket): await chat_manager.handle_websocket(client_id, websocket) + + diff --git a/src/backend/langflow/api/chat_manager.py b/src/backend/langflow/api/chat_manager.py index 8dcaf05ac..5ce7d2452 100644 --- a/src/backend/langflow/api/chat_manager.py +++ b/src/backend/langflow/api/chat_manager.py @@ -5,9 +5,12 @@ from typing import Dict, List from collections import defaultdict from fastapi import WebSocket import json +from langchain.llms import OpenAI, AzureOpenAI +from langchain.chat_models import ChatOpenAI, AzureChatOpenAI from langflow.api.schemas import ChatMessage, ChatResponse, FileResponse from langflow.cache.manager import AsyncSubject - +from langchain.callbacks.base import AsyncCallbackManager +from langflow.api.callback import StreamingLLMCallbackHandler from langflow.interface.run import ( async_get_result_and_steps, load_or_build_langchain_object, @@ -90,7 +93,6 @@ class ChatManager: async def process_message(self, client_id: str, payload: Dict): # Process the graph data and chat message - chat_message = payload.pop("message", "") chat_message = ChatMessage(sender="you", message=chat_message) await self.chat_history.add_message(client_id, chat_message) @@ -105,10 +107,12 @@ class ChatManager: # Generate result and thought try: logger.debug("Generating result and thought") + result, intermediate_steps = await process_graph( graph_data=graph_data, is_first_message=is_first_message, chat_message=chat_message, + websocket=self.active_connections[client_id], ) except Exception as e: # Log stack trace @@ -129,6 +133,7 @@ class ChatManager: async def handle_websocket(self, client_id: str, websocket: WebSocket): await self.connect(client_id, websocket) + try: chat_history = self.chat_history.get_history(client_id) await websocket.send_json(json.dumps(chat_history)) @@ -146,9 +151,13 @@ class ChatManager: async def process_graph( - graph_data: Dict, is_first_message: bool, chat_message: ChatMessage + graph_data: Dict, + is_first_message: bool, + chat_message: ChatMessage, + websocket: WebSocket, ): langchain_object = load_or_build_langchain_object(graph_data, is_first_message) + langchain_object = try_setting_streaming_options(langchain_object, websocket) logger.debug("Loaded langchain object") if langchain_object is None: @@ -171,6 +180,27 @@ async def process_graph( raise e +def try_setting_streaming_options(langchain_object, websocket): + # If the LLM type is OpenAI or ChatOpenAI, + # set streaming to True + # First we need to find the LLM + llm = None + if hasattr(langchain_object, "llm"): + llm = langchain_object.llm + elif hasattr(langchain_object, "llm_chain") and hasattr( + langchain_object.llm_chain, "llm" + ): + llm = langchain_object.llm_chain.llm + if isinstance(llm, (OpenAI, ChatOpenAI, AzureOpenAI, AzureChatOpenAI)): + llm.streaming = bool(hasattr(llm, "streaming")) + + if hasattr(langchain_object, "callback_manager"): + stream_handler = StreamingLLMCallbackHandler(websocket) + stream_manager = AsyncCallbackManager([stream_handler]) + langchain_object.callback_manager = stream_manager + return langchain_object + + def pil_to_base64(image: Image) -> str: buffered = BytesIO() image.save(buffered, format="PNG") From 09320824a9cdf29bd474ee757a1bc54d534f766d Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Mon, 24 Apr 2023 10:32:15 -0300 Subject: [PATCH 10/99] refactor(validate.py): extract build_graph function to langflow.interface.run module feat(validate.py): add post_validate_node endpoint to validate a single node in the graph by its id --- src/backend/langflow/api/validate.py | 18 ++++++++++++++++++ src/backend/langflow/interface/run.py | 14 +++++++------- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/backend/langflow/api/validate.py b/src/backend/langflow/api/validate.py index a60bcc506..07f113a8b 100644 --- a/src/backend/langflow/api/validate.py +++ b/src/backend/langflow/api/validate.py @@ -7,6 +7,7 @@ from langflow.api.base import ( PromptValidationResponse, validate_prompt, ) +from langflow.interface.run import build_graph from langflow.utils.logger import logger from langflow.utils.validate import validate_code @@ -33,3 +34,20 @@ def post_validate_prompt(prompt: Prompt): except Exception as e: logger.exception(e) raise HTTPException(status_code=500, detail=str(e)) from e + + +# validate node +@router.post("/node/{node_id}", status_code=200) +def post_validate_node(node_id: str, data: dict): + try: + # build graph + graph = build_graph(data) + # validate node + node = graph.get_node(node_id) + if node is not None: + _ = node.build() + return node.params + raise + except Exception as e: + logger.exception(e) + raise HTTPException(status_code=500, detail=str(e)) from e diff --git a/src/backend/langflow/interface/run.py b/src/backend/langflow/interface/run.py index deba28586..9fb9b82fe 100644 --- a/src/backend/langflow/interface/run.py +++ b/src/backend/langflow/interface/run.py @@ -39,16 +39,16 @@ def build_langchain_object_with_caching(data_graph): """ logger.debug("Building langchain object") - nodes = data_graph["nodes"] - # Add input variables - # nodes = payload.extract_input_variables(nodes) - # Nodes, edges and root node - edges = data_graph["edges"] - graph = Graph(nodes, edges) - + graph = build_graph(data_graph) return graph.build() +def build_graph(data_graph): + nodes = data_graph["nodes"] + edges = data_graph["edges"] + return Graph(nodes, edges) + + def build_langchain_object(data_graph): """ Build langchain object from data_graph. From f122151b12f388d7fa64f222fc1cbfee1c13f4b5 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Mon, 24 Apr 2023 13:43:52 -0300 Subject: [PATCH 11/99] fix(validate.py): raise an exception with a message when node is not found feat(GenericNode): add node validation with outline color feedback feat(tailwind.config.js): add styles for outline colors and animations --- src/backend/langflow/api/validate.py | 2 +- .../src/CustomNodes/GenericNode/index.tsx | 56 +++++++++++++- src/frontend/tailwind.config.js | 75 ++++++++++++------- 3 files changed, 100 insertions(+), 33 deletions(-) diff --git a/src/backend/langflow/api/validate.py b/src/backend/langflow/api/validate.py index 07f113a8b..d80010be4 100644 --- a/src/backend/langflow/api/validate.py +++ b/src/backend/langflow/api/validate.py @@ -47,7 +47,7 @@ def post_validate_node(node_id: str, data: dict): if node is not None: _ = node.build() return node.params - raise + raise Exception(f"Node {node_id} not found") except Exception as e: logger.exception(e) raise HTTPException(status_code=500, detail=str(e)) from e diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index ff13af901..c96591cc9 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -7,9 +7,10 @@ import { } from "../../utils"; import ParameterComponent from "./components/parameterComponent"; import { typesContext } from "../../contexts/typesContext"; -import { useContext, useRef } from "react"; +import { useContext, useState, useEffect, useRef } from "react"; import { NodeDataType } from "../../types/flow"; import { alertContext } from "../../contexts/alertContext"; +import { useCallback } from 'react'; export default function GenericNode({ data, @@ -22,6 +23,53 @@ export default function GenericNode({ const showError = useRef(true); const { types, deleteNode } = useContext(typesContext); const Icon = nodeIcons[types[data.type]]; + + // State for outline color + const [isGreenOutline, setIsGreenOutline] = useState(false); + const [isRedOutline, setIsRedOutline] = useState(false); + const { reactFlowInstance } = useContext(typesContext); + + const validateNode = useCallback(async () => { + try { + const response = await fetch(`/validate/node/${data.id}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(reactFlowInstance.toObject()), + }); + + if (response.status === 200) { + setIsGreenOutline(true); + setIsRedOutline(false); + } else if (response.status === 500) { + setIsRedOutline(true); + setIsGreenOutline(false); + } + } catch (error) { + console.error('Error validating node:', error); + setIsRedOutline(true); + } + }, [data.id, reactFlowInstance]); + + useEffect(() => { + validateNode(); + }, [ + validateNode, + ...Object.values(data.node.template).flatMap((t) => Object.values(t)), + + ]); + + useEffect(() => { + if (isGreenOutline) { + setTimeout(() => { + setIsGreenOutline(false); + }, 1000); + } + }, [isGreenOutline]); + + const outlineColor = isGreenOutline ? 'animate-pulse-green' : isRedOutline ? 'border-red-outline' : ''; + if (!Icon) { if (showError.current) { setErrorData({ @@ -34,9 +82,11 @@ export default function GenericNode({ deleteNode(data.id); return; } + return (
Date: Mon, 24 Apr 2023 19:25:33 -0300 Subject: [PATCH 12/99] added chat trigger component --- .../chatComponent/chatTrigger/index.tsx | 34 ++++++ .../src/components/chatComponent/index.tsx | 30 +----- src/frontend/src/modals/chatModal/index.tsx | 102 ++++++++++++++++++ 3 files changed, 138 insertions(+), 28 deletions(-) create mode 100644 src/frontend/src/components/chatComponent/chatTrigger/index.tsx create mode 100644 src/frontend/src/modals/chatModal/index.tsx diff --git a/src/frontend/src/components/chatComponent/chatTrigger/index.tsx b/src/frontend/src/components/chatComponent/chatTrigger/index.tsx new file mode 100644 index 000000000..f1841caba --- /dev/null +++ b/src/frontend/src/components/chatComponent/chatTrigger/index.tsx @@ -0,0 +1,34 @@ +import { Transition } from "@headlessui/react"; +import { Bars3CenterLeftIcon } from "@heroicons/react/24/outline"; +import { nodeColors } from "../../../utils"; + +export default function ChatTrigger({open, setOpen}){ + return( +
+
+ +
+
+
) +} \ No newline at end of file diff --git a/src/frontend/src/components/chatComponent/index.tsx b/src/frontend/src/components/chatComponent/index.tsx index 11516f004..6393df55b 100644 --- a/src/frontend/src/components/chatComponent/index.tsx +++ b/src/frontend/src/components/chatComponent/index.tsx @@ -19,6 +19,7 @@ import { TabsContext } from "../../contexts/tabsContext"; import { ChatType } from "../../types/chat"; import ChatMessage from "./chatMessage"; import { NodeType } from "../../types/flow"; +import ChatTrigger from "./chatTrigger"; const _ = require("lodash"); @@ -258,34 +259,7 @@ export default function Chat({ flow, reactFlowInstance }: ChatType) {
- -
-
- -
-
-
+ ); } diff --git a/src/frontend/src/modals/chatModal/index.tsx b/src/frontend/src/modals/chatModal/index.tsx new file mode 100644 index 000000000..93e4ed3d4 --- /dev/null +++ b/src/frontend/src/modals/chatModal/index.tsx @@ -0,0 +1,102 @@ +import { Dialog, Transition } from "@headlessui/react"; +import { XMarkIcon, ClipboardDocumentListIcon } from "@heroicons/react/24/outline"; +import { Fragment, useContext, useRef, useState } from "react"; +import { PopUpContext } from "../../contexts/popUpContext"; + +export default function TextAreaModal(){ + const [open, setOpen] = useState(true); + const { closePopUp } = useContext(PopUpContext); + const ref = useRef(); + function setModalOpen(x:boolean){ + setOpen(x); + if(x === false){ + setTimeout(() => {closePopUp()}, 300); + } + } + return ( + + + +
+ + +
+
+ + +
+ +
+
+
+
+
+
+ + Edit text + +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ ) +} \ No newline at end of file From 8ed1ea92d41c442183a384e14db1e67ee673cbde Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Mon, 24 Apr 2023 20:03:05 -0300 Subject: [PATCH 13/99] clean chat area --- .../src/CustomNodes/GenericNode/index.tsx | 2 +- .../chatComponent/chatTrigger/index.tsx | 16 +- src/frontend/src/modals/chatModal/index.tsx | 157 +++++++----------- src/frontend/src/utils.ts | 2 +- 4 files changed, 72 insertions(+), 105 deletions(-) diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index ff13af901..f362ca8ff 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -38,7 +38,7 @@ export default function GenericNode({
diff --git a/src/frontend/src/components/chatComponent/chatTrigger/index.tsx b/src/frontend/src/components/chatComponent/chatTrigger/index.tsx index f1841caba..6c4bbadde 100644 --- a/src/frontend/src/components/chatComponent/chatTrigger/index.tsx +++ b/src/frontend/src/components/chatComponent/chatTrigger/index.tsx @@ -1,8 +1,12 @@ import { Transition } from "@headlessui/react"; -import { Bars3CenterLeftIcon } from "@heroicons/react/24/outline"; +import { Bars3CenterLeftIcon, ChatBubbleBottomCenterTextIcon } from "@heroicons/react/24/outline"; import { nodeColors } from "../../../utils"; +import { PopUpContext } from "../../../contexts/popUpContext"; +import { useContext } from "react"; +import ChatModal from "../../../modals/chatModal"; export default function ChatTrigger({open, setOpen}){ + const {openPopUp} = useContext(PopUpContext) return( -
-
+
+
diff --git a/src/frontend/src/modals/chatModal/index.tsx b/src/frontend/src/modals/chatModal/index.tsx index 93e4ed3d4..6c232939f 100644 --- a/src/frontend/src/modals/chatModal/index.tsx +++ b/src/frontend/src/modals/chatModal/index.tsx @@ -1,102 +1,65 @@ import { Dialog, Transition } from "@headlessui/react"; -import { XMarkIcon, ClipboardDocumentListIcon } from "@heroicons/react/24/outline"; +import { + XMarkIcon, + ClipboardDocumentListIcon, +} from "@heroicons/react/24/outline"; import { Fragment, useContext, useRef, useState } from "react"; import { PopUpContext } from "../../contexts/popUpContext"; -export default function TextAreaModal(){ - const [open, setOpen] = useState(true); - const { closePopUp } = useContext(PopUpContext); - const ref = useRef(); - function setModalOpen(x:boolean){ - setOpen(x); - if(x === false){ - setTimeout(() => {closePopUp()}, 300); - } - } - return ( - - - -
- +export default function ChatModal() { + const [open, setOpen] = useState(true); + const { closePopUp } = useContext(PopUpContext); + const ref = useRef(); + function setModalOpen(x: boolean) { + setOpen(x); + if (x === false) { + setTimeout(() => { + closePopUp(); + }, 300); + } + } + return ( + + + +
+ -
-
- - -
- -
-
-
-
-
-
- - Edit text - -
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
- ) -} \ No newline at end of file +
+
+ + +
+
+
+
input area
+
+
+
+
+
+
+
+ ); +} diff --git a/src/frontend/src/utils.ts b/src/frontend/src/utils.ts index 405d56297..608035ff8 100644 --- a/src/frontend/src/utils.ts +++ b/src/frontend/src/utils.ts @@ -78,7 +78,7 @@ export const nodeColors: {[char: string]: string} = { tools: "#FF3434", memories: "#F5B85A", advanced: "#000000", - chat: "#454173", + chat: "#198BF6", thought:"#272541", embeddings:"#42BAA7", documentloaders:"#7AAE42", From 2c0b846f5411ffb090ba3341d84b0a3b64be1607 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Mon, 24 Apr 2023 21:02:23 -0300 Subject: [PATCH 14/99] migrate chat logic to chat modal --- .../chatComponent/chatTrigger/index.tsx | 4 +- .../src/components/chatComponent/index.tsx | 2 +- src/frontend/src/contexts/index.tsx | 6 +- src/frontend/src/modals/chatModal/index.tsx | 215 +++++++++++++++++- 4 files changed, 214 insertions(+), 13 deletions(-) diff --git a/src/frontend/src/components/chatComponent/chatTrigger/index.tsx b/src/frontend/src/components/chatComponent/chatTrigger/index.tsx index 6c4bbadde..2f08ae92a 100644 --- a/src/frontend/src/components/chatComponent/chatTrigger/index.tsx +++ b/src/frontend/src/components/chatComponent/chatTrigger/index.tsx @@ -5,7 +5,7 @@ import { PopUpContext } from "../../../contexts/popUpContext"; import { useContext } from "react"; import ChatModal from "../../../modals/chatModal"; -export default function ChatTrigger({open, setOpen}){ +export default function ChatTrigger({open, setOpen,flow}){ const {openPopUp} = useContext(PopUpContext) return( { setOpen(true); - openPopUp() + openPopUp() }} >
diff --git a/src/frontend/src/components/chatComponent/index.tsx b/src/frontend/src/components/chatComponent/index.tsx index 6393df55b..c18deb8de 100644 --- a/src/frontend/src/components/chatComponent/index.tsx +++ b/src/frontend/src/components/chatComponent/index.tsx @@ -259,7 +259,7 @@ export default function Chat({ flow, reactFlowInstance }: ChatType) {
- + ); } diff --git a/src/frontend/src/contexts/index.tsx b/src/frontend/src/contexts/index.tsx index 310606ea5..06c576b83 100644 --- a/src/frontend/src/contexts/index.tsx +++ b/src/frontend/src/contexts/index.tsx @@ -13,12 +13,10 @@ export default function ContextWrapper({ children }: { children: ReactNode }) { - - + - {children} + {children} - diff --git a/src/frontend/src/modals/chatModal/index.tsx b/src/frontend/src/modals/chatModal/index.tsx index 6c232939f..5d3c8df81 100644 --- a/src/frontend/src/modals/chatModal/index.tsx +++ b/src/frontend/src/modals/chatModal/index.tsx @@ -2,14 +2,177 @@ import { Dialog, Transition } from "@headlessui/react"; import { XMarkIcon, ClipboardDocumentListIcon, + LockClosedIcon, + PaperAirplaneIcon, } from "@heroicons/react/24/outline"; -import { Fragment, useContext, useRef, useState } from "react"; +import { Fragment, useContext, useEffect, useRef, useState } from "react"; import { PopUpContext } from "../../contexts/popUpContext"; +import { NodeType } from "../../types/flow"; +import { TabsContext } from "../../contexts/tabsContext"; +import { alertContext } from "../../contexts/alertContext"; +import { classNames, snakeToNormalCase } from "../../utils"; +import { sendAll } from "../../controllers/API"; +import { typesContext } from "../../contexts/typesContext"; +import ChatMessage from "../../components/chatComponent/chatMessage"; +const _ = require("lodash"); + +export default function ChatModal({ flow }) { + const { updateFlow, lockChat, setLockChat, flows, tabIndex } = + useContext(TabsContext); + const [saveChat, setSaveChat] = useState(false); + const [chatValue, setChatValue] = useState(""); + const [chatHistory, setChatHistory] = useState(flow.chat); + const { reactFlowInstance } = useContext(typesContext); + const { setErrorData, setNoticeData } = useContext(alertContext); + const addChatHistory = ( + message: string, + isSend: boolean, + thought?: string + ) => { + let tabsChange = false; + setChatHistory((old) => { + let newChat = _.cloneDeep(old); + if (JSON.stringify(flow.chat) !== JSON.stringify(old)) { + tabsChange = true; + return old; + } + if (thought) { + newChat.push({ message, isSend, thought }); + } else { + newChat.push({ message, isSend }); + } + return newChat; + }); + if (tabsChange) { + if (thought) { + updateFlow({ + ..._.cloneDeep(flow), + chat: [...flow.chat, { isSend, message, thought }], + }); + } else { + updateFlow({ + ..._.cloneDeep(flow), + chat: [...flow.chat, { isSend, message }], + }); + } + } + setSaveChat((chat) => !chat); + }; + useEffect(() => { + updateFlow({ ..._.cloneDeep(flow), chat: chatHistory }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [saveChat]); + useEffect(() => { + setChatHistory(flow.chat); + }, [flow]); + useEffect(() => { + if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" }); + }, [chatHistory]); + + function validateNode(n: NodeType): Array { + if (!n.data?.node?.template || !Object.keys(n.data.node.template)) { + setNoticeData({ + title: + "We've noticed a potential issue with a node in the flow. Please review it and, if necessary, submit a bug report with your exported flow file. Thank you for your help!", + }); + return []; + } + + const { + type, + node: { template }, + } = n.data; + + return Object.keys(template).reduce( + (errors: Array, t) => + errors.concat( + template[t].required && + template[t].show && + (!template[t].value || template[t].value === "") && + !reactFlowInstance + .getEdges() + .some( + (e) => + e.targetHandle.split("|")[1] === t && + e.targetHandle.split("|")[2] === n.id + ) + ? [ + `${type} is missing ${ + template.display_name + ? template.display_name + : snakeToNormalCase(template[t].name) + }.`, + ] + : [] + ), + [] as string[] + ); + } + + function validateNodes() { + console.log(reactFlowInstance) + return reactFlowInstance + .getNodes() + .flatMap((n: NodeType) => validateNode(n)); + } + + const ref = useRef(null); + + function sendMessage() { + if (chatValue !== "") { + let nodeValidationErrors = validateNodes(); + if (nodeValidationErrors.length === 0) { + setLockChat(true); + let message = chatValue; + setChatValue(""); + addChatHistory(message, true); + + sendAll({ + ...reactFlowInstance.toObject(), + message, + chatHistory, + name: flow.name, + description: flow.description, + }) + .then((r) => { + addChatHistory(r.data.result, false, r.data.thought); + setLockChat(false); + }) + .catch((error) => { + setErrorData({ + title: error.message ?? "Unknown Error", + list: [error.response.data.detail], + }); + setLockChat(false); + let lastMessage; + setChatHistory((chatHistory) => { + let newChat = chatHistory; + + lastMessage = newChat.pop().message; + return newChat; + }); + setChatValue(lastMessage); + }); + } else { + setErrorData({ + title: "Oops! Looks like you missed some required information:", + list: nodeValidationErrors, + }); + } + } else { + setErrorData({ + title: "Error sending message", + list: ["The message cannot be empty."], + }); + } + } + function clearChat() { + setChatHistory([]); + updateFlow({ ..._.cloneDeep(flow), chat: [] }); + } -export default function ChatModal() { const [open, setOpen] = useState(true); const { closePopUp } = useContext(PopUpContext); - const ref = useRef(); function setModalOpen(x: boolean) { setOpen(x); if (x === false) { @@ -50,10 +213,50 @@ export default function ChatModal() { leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > -
-
+
+ {chatHistory.map((c, i) => ( + + ))} +
+
+
+
+ { + if (event.key === "Enter" && !lockChat) { + sendMessage(); + } + }} + type="text" + disabled={lockChat} + value={lockChat ? "Thinking..." : chatValue} + onChange={(e) => { + setChatValue(e.target.value); + }} + className={classNames( + lockChat + ? "bg-gray-500 text-white" + : "dark:bg-gray-700", + "form-input block w-full rounded-md border-gray-300 dark:border-gray-600 dark:text-white pr-10 sm:text-sm" + )} + placeholder={"Send a message..."} + /> +
+ +
-
input area
From 020e9c8461fe7131005849d5ce08e2eacc6196bc Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Mon, 24 Apr 2023 21:10:51 -0300 Subject: [PATCH 15/99] chat modal working --- src/frontend/src/modals/chatModal/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/modals/chatModal/index.tsx b/src/frontend/src/modals/chatModal/index.tsx index 5d3c8df81..99f1b4ee0 100644 --- a/src/frontend/src/modals/chatModal/index.tsx +++ b/src/frontend/src/modals/chatModal/index.tsx @@ -213,7 +213,7 @@ export default function ChatModal({ flow }) { leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > -
+
{chatHistory.map((c, i) => ( ))} From 68870301f3df0df7e95fc9f643f8e850f938628e Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Mon, 24 Apr 2023 22:04:25 -0300 Subject: [PATCH 16/99] chat modal messages firts version finished --- .../modals/chatModal/chatMessage/index.tsx | 69 +++++++++++++++++++ src/frontend/src/modals/chatModal/index.tsx | 6 +- 2 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 src/frontend/src/modals/chatModal/chatMessage/index.tsx diff --git a/src/frontend/src/modals/chatModal/chatMessage/index.tsx b/src/frontend/src/modals/chatModal/chatMessage/index.tsx new file mode 100644 index 000000000..c29c1c12c --- /dev/null +++ b/src/frontend/src/modals/chatModal/chatMessage/index.tsx @@ -0,0 +1,69 @@ +import { + ChatBubbleLeftEllipsisIcon, + ChatBubbleOvalLeftEllipsisIcon, + PlusSmallIcon, +} from "@heroicons/react/24/outline"; +import { useState } from "react"; +import { ChatMessageType } from "../../../types/chat"; +import { classNames } from "../../../utils"; +import { UserIcon } from "@heroicons/react/24/solid"; +var Convert = require("ansi-to-html"); +var convert = new Convert({ newline: true }); + +export default function ChatMessage({ chat }: { chat: ChatMessageType }) { + const [hidden, setHidden] = useState(true); + return ( +
+
+ {!chat.isSend && 🦜} + {chat.isSend && } +
+ {!chat.isSend ? ( +
+
+ {hidden && chat.thought && chat.thought !== "" && ( +
setHidden((prev) => !prev)} + className="absolute -top-1 -left-2 cursor-pointer" + > + +
+ )} + {chat.thought && chat.thought !== "" && !hidden && ( +
setHidden((prev) => !prev)} + className=" text-start inline-block w-full pb-3 pt-3 px-5 cursor-pointer" + dangerouslySetInnerHTML={{ + __html: convert.toHtml(chat.thought), + }} + >
+ )} + {chat.thought && chat.thought !== "" && !hidden &&

} +
+ {chat.message} +
+
+
+ ) : ( +
+
+ {chat.message} +
+
+ )} +
+ ); +} diff --git a/src/frontend/src/modals/chatModal/index.tsx b/src/frontend/src/modals/chatModal/index.tsx index 99f1b4ee0..b66acf612 100644 --- a/src/frontend/src/modals/chatModal/index.tsx +++ b/src/frontend/src/modals/chatModal/index.tsx @@ -13,7 +13,7 @@ import { alertContext } from "../../contexts/alertContext"; import { classNames, snakeToNormalCase } from "../../utils"; import { sendAll } from "../../controllers/API"; import { typesContext } from "../../contexts/typesContext"; -import ChatMessage from "../../components/chatComponent/chatMessage"; +import ChatMessage from "./chatMessage"; const _ = require("lodash"); export default function ChatModal({ flow }) { @@ -212,8 +212,8 @@ export default function ChatModal({ flow }) { leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - -
+ +
{chatHistory.map((c, i) => ( ))} From 152525deccd13447c0936d4373f15970dc387596 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Mon, 24 Apr 2023 23:10:51 -0300 Subject: [PATCH 17/99] chat clear --- src/frontend/package-lock.json | 8 ++++---- src/frontend/package.json | 2 +- .../src/modals/chatModal/chatMessage/index.tsx | 5 +++-- src/frontend/src/modals/chatModal/index.tsx | 14 +++++++++++--- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index 35d27c5f4..8491f6873 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -31,7 +31,7 @@ "react-cookie": "^4.1.1", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.2", - "react-icons": "^4.7.1", + "react-icons": "^4.8.0", "react-laag": "^2.0.5", "react-router-dom": "^6.8.1", "react-scripts": "5.0.1", @@ -15040,9 +15040,9 @@ "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, "node_modules/react-icons": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.7.1.tgz", - "integrity": "sha512-yHd3oKGMgm7zxo3EA7H2n7vxSoiGmHk5t6Ou4bXsfcgWyhfDKMpyKfhHR6Bjnn63c+YXBLBPUql9H4wPJM6sXw==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.8.0.tgz", + "integrity": "sha512-N6+kOLcihDiAnj5Czu637waJqSnwlMNROzVZMhfX68V/9bu9qHaMIJC4UdozWoOk57gahFCNHwVvWzm0MTzRjg==", "peerDependencies": { "react": "*" } diff --git a/src/frontend/package.json b/src/frontend/package.json index 6eba117fc..feb11e814 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -26,7 +26,7 @@ "react-cookie": "^4.1.1", "react-dom": "^18.2.0", "react-error-boundary": "^4.0.2", - "react-icons": "^4.7.1", + "react-icons": "^4.8.0", "react-laag": "^2.0.5", "react-router-dom": "^6.8.1", "react-scripts": "5.0.1", diff --git a/src/frontend/src/modals/chatModal/chatMessage/index.tsx b/src/frontend/src/modals/chatModal/chatMessage/index.tsx index c29c1c12c..0cec858a4 100644 --- a/src/frontend/src/modals/chatModal/chatMessage/index.tsx +++ b/src/frontend/src/modals/chatModal/chatMessage/index.tsx @@ -7,6 +7,7 @@ import { useState } from "react"; import { ChatMessageType } from "../../../types/chat"; import { classNames } from "../../../utils"; import { UserIcon } from "@heroicons/react/24/solid"; +import {AiFillRobot} from "react-icons/ai" var Convert = require("ansi-to-html"); var convert = new Convert({ newline: true }); @@ -21,10 +22,10 @@ export default function ChatMessage({ chat }: { chat: ChatMessageType }) { >
- {!chat.isSend && 🦜} + {!chat.isSend && } {chat.isSend && }
{!chat.isSend ? ( diff --git a/src/frontend/src/modals/chatModal/index.tsx b/src/frontend/src/modals/chatModal/index.tsx index b66acf612..56213b031 100644 --- a/src/frontend/src/modals/chatModal/index.tsx +++ b/src/frontend/src/modals/chatModal/index.tsx @@ -14,6 +14,8 @@ import { classNames, snakeToNormalCase } from "../../utils"; import { sendAll } from "../../controllers/API"; import { typesContext } from "../../contexts/typesContext"; import ChatMessage from "./chatMessage"; +import { FaEraser } from "react-icons/fa"; + const _ = require("lodash"); export default function ChatModal({ flow }) { @@ -110,7 +112,7 @@ export default function ChatModal({ flow }) { } function validateNodes() { - console.log(reactFlowInstance) + console.log(reactFlowInstance); return reactFlowInstance .getNodes() .flatMap((n: NodeType) => validateNode(n)); @@ -198,7 +200,7 @@ export default function ChatModal({ flow }) { leaveFrom="opacity-100" leaveTo="opacity-0" > -
+
@@ -212,7 +214,13 @@ export default function ChatModal({ flow }) { leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" > - + +
+ +
{chatHistory.map((c, i) => ( From 7c166162731ad46a2ef8fad4bb5e5643ccc41484 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Mon, 24 Apr 2023 23:21:22 -0300 Subject: [PATCH 18/99] chat popUp not using popUp context --- .../chatComponent/chatTrigger/index.tsx | 1 - .../src/components/chatComponent/index.tsx | 240 +----------------- src/frontend/src/modals/chatModal/index.tsx | 5 +- 3 files changed, 6 insertions(+), 240 deletions(-) diff --git a/src/frontend/src/components/chatComponent/chatTrigger/index.tsx b/src/frontend/src/components/chatComponent/chatTrigger/index.tsx index 2f08ae92a..9d5f67e0c 100644 --- a/src/frontend/src/components/chatComponent/chatTrigger/index.tsx +++ b/src/frontend/src/components/chatComponent/chatTrigger/index.tsx @@ -22,7 +22,6 @@ export default function ChatTrigger({open, setOpen,flow}){ -
-
- {chatHistory.map((c, i) => ( - - ))} -
-
-
-
- { - if (event.key === "Enter" && !lockChat) { - sendMessage(); - } - }} - type="text" - disabled={lockChat} - value={lockChat ? "Thinking..." : chatValue} - onChange={(e) => { - setChatValue(e.target.value); - }} - className={classNames( - lockChat ? "bg-gray-500 text-white" : "dark:bg-gray-700", - "form-input block w-full rounded-md border-gray-300 dark:border-gray-600 dark:text-white pr-10 sm:text-sm" - )} - placeholder={"Send a message..."} - /> -
- -
-
-
-
-
- - + + ); } diff --git a/src/frontend/src/modals/chatModal/index.tsx b/src/frontend/src/modals/chatModal/index.tsx index 56213b031..15bc2a89a 100644 --- a/src/frontend/src/modals/chatModal/index.tsx +++ b/src/frontend/src/modals/chatModal/index.tsx @@ -1,7 +1,5 @@ import { Dialog, Transition } from "@headlessui/react"; import { - XMarkIcon, - ClipboardDocumentListIcon, LockClosedIcon, PaperAirplaneIcon, } from "@heroicons/react/24/outline"; @@ -18,7 +16,7 @@ import { FaEraser } from "react-icons/fa"; const _ = require("lodash"); -export default function ChatModal({ flow }) { +export default function ChatModal({ flow,open, setOpen }) { const { updateFlow, lockChat, setLockChat, flows, tabIndex } = useContext(TabsContext); const [saveChat, setSaveChat] = useState(false); @@ -173,7 +171,6 @@ export default function ChatModal({ flow }) { updateFlow({ ..._.cloneDeep(flow), chat: [] }); } - const [open, setOpen] = useState(true); const { closePopUp } = useContext(PopUpContext); function setModalOpen(x: boolean) { setOpen(x); From ce7e9d02543ed5434bb7a4de58cd83c88225a283 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Mon, 24 Apr 2023 23:31:42 -0300 Subject: [PATCH 19/99] shortcut on ctrl shift k --- .../src/components/chatComponent/index.tsx | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/frontend/src/components/chatComponent/index.tsx b/src/frontend/src/components/chatComponent/index.tsx index 99e663713..5997deebf 100644 --- a/src/frontend/src/components/chatComponent/index.tsx +++ b/src/frontend/src/components/chatComponent/index.tsx @@ -1,31 +1,31 @@ -import { Transition } from "@headlessui/react"; import { - Bars3CenterLeftIcon, - LockClosedIcon, - PaperAirplaneIcon, - XMarkIcon, -} from "@heroicons/react/24/outline"; -import { - MouseEventHandler, - useContext, useEffect, useRef, useState, } from "react"; -import { sendAll } from "../../controllers/API"; -import { alertContext } from "../../contexts/alertContext"; -import { classNames, nodeColors, snakeToNormalCase } from "../../utils"; -import { TabsContext } from "../../contexts/tabsContext"; + import { ChatType } from "../../types/chat"; -import ChatMessage from "./chatMessage"; -import { NodeType } from "../../types/flow"; import ChatTrigger from "./chatTrigger"; import ChatModal from "../../modals/chatModal"; const _ = require("lodash"); export default function Chat({ flow }: ChatType) { + const [open, setOpen] = useState(false); + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + console.log(event) + if (event.key === "K" && event.shiftKey && (event.metaKey||event.ctrlKey)) { + console.log("dfdsfds") + setOpen(oldState=>!oldState); + } + }; + document.addEventListener("keydown", handleKeyDown); + return () => { + document.removeEventListener("keydown", handleKeyDown); + }; + }, []); return ( <> From 7af5f68861483012909d5a7f1c8f6966005e44b1 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Tue, 25 Apr 2023 14:31:57 -0300 Subject: [PATCH 20/99] feat(validate): add debounced validation for nodes in GenericNode component fix(validate): set validation status to "success" or "error" based on response status feat(validate): add state for validation status and outline color in GenericNode component refactor(validate): use useDebouncedCallback hook for debouncing validation function refactor(validate): simplify useEffect dependencies in GenericNode component --- src/backend/langflow/api/validate.py | 2 +- .../src/CustomNodes/GenericNode/index.tsx | 341 +++++++++--------- 2 files changed, 180 insertions(+), 163 deletions(-) diff --git a/src/backend/langflow/api/validate.py b/src/backend/langflow/api/validate.py index d80010be4..e1b5a3a1a 100644 --- a/src/backend/langflow/api/validate.py +++ b/src/backend/langflow/api/validate.py @@ -46,7 +46,7 @@ def post_validate_node(node_id: str, data: dict): node = graph.get_node(node_id) if node is not None: _ = node.build() - return node.params + return str(node.params) raise Exception(f"Node {node_id} not found") except Exception as e: logger.exception(e) diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index c96591cc9..93497c0fc 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -1,186 +1,203 @@ import { TrashIcon } from "@heroicons/react/24/outline"; +import { useDebouncedCallback } from "use-debounce"; import { - classNames, - nodeColors, - nodeIcons, - snakeToNormalCase, + classNames, + nodeColors, + nodeIcons, + snakeToNormalCase, } from "../../utils"; import ParameterComponent from "./components/parameterComponent"; import { typesContext } from "../../contexts/typesContext"; import { useContext, useState, useEffect, useRef } from "react"; import { NodeDataType } from "../../types/flow"; import { alertContext } from "../../contexts/alertContext"; -import { useCallback } from 'react'; +import { useCallback } from "react"; export default function GenericNode({ - data, - selected, + data, + selected, }: { - data: NodeDataType; - selected: boolean; + data: NodeDataType; + selected: boolean; }) { - const { setErrorData } = useContext(alertContext); - const showError = useRef(true); - const { types, deleteNode } = useContext(typesContext); - const Icon = nodeIcons[types[data.type]]; + const { setErrorData } = useContext(alertContext); + const showError = useRef(true); + const { types, deleteNode } = useContext(typesContext); + const Icon = nodeIcons[types[data.type]]; + const [validationStatus, setValidationStatus] = useState("idle"); + // State for outline color + const [isGreenOutline, setIsGreenOutline] = useState(false); + const [isRedOutline, setIsRedOutline] = useState(false); + const { reactFlowInstance } = useContext(typesContext); - // State for outline color - const [isGreenOutline, setIsGreenOutline] = useState(false); - const [isRedOutline, setIsRedOutline] = useState(false); - const { reactFlowInstance } = useContext(typesContext); + const debouncedValidateNode = useDebouncedCallback(async () => { + // Check if the validationStatus is "success" + if (validationStatus === "success") return; - const validateNode = useCallback(async () => { - try { - const response = await fetch(`/validate/node/${data.id}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(reactFlowInstance.toObject()), - }); + try { + const response = await fetch(`/validate/node/${data.id}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(reactFlowInstance.toObject()), + }); - if (response.status === 200) { - setIsGreenOutline(true); - setIsRedOutline(false); - } else if (response.status === 500) { - setIsRedOutline(true); - setIsGreenOutline(false); - } - } catch (error) { - console.error('Error validating node:', error); - setIsRedOutline(true); - } - }, [data.id, reactFlowInstance]); + if (response.status === 200) { + setValidationStatus("success"); + } else if (response.status === 500) { + setValidationStatus("error"); + } + } catch (error) { + console.error("Error validating node:", error); + setValidationStatus("error"); + } + }, 1000); - useEffect(() => { - validateNode(); - }, [ - validateNode, - ...Object.values(data.node.template).flatMap((t) => Object.values(t)), + const validateNode = useCallback(() => { + debouncedValidateNode(); + }, [debouncedValidateNode]); - ]); + useEffect(() => { + validateNode(); + }, [ + validateNode, + ...Object.values(data.node.template).flatMap((t) => Object.values(t)), + ]); - useEffect(() => { - if (isGreenOutline) { - setTimeout(() => { - setIsGreenOutline(false); - }, 1000); - } - }, [isGreenOutline]); + useEffect(() => { + if (validationStatus === "success") { + setIsGreenOutline(true); + setIsRedOutline(false); + setTimeout(() => { + setIsGreenOutline(false); + }, 1000); + } else if (validationStatus === "error") { + setIsRedOutline(true); + setIsGreenOutline(false); + } else { + setIsGreenOutline(false); + setIsRedOutline(false); + } + }, [validationStatus]); - const outlineColor = isGreenOutline ? 'animate-pulse-green' : isRedOutline ? 'border-red-outline' : ''; + const outlineColor = isGreenOutline + ? "animate-pulse-green" + : isRedOutline + ? "border-red-outline" + : ""; - if (!Icon) { - if (showError.current) { - setErrorData({ - title: data.type - ? `The ${data.type} node could not be rendered, please review your json file` - : "There was a node that can't be rendered, please review your json file", - }); - showError.current = false; - } - deleteNode(data.id); - return; - } + if (!Icon) { + if (showError.current) { + setErrorData({ + title: data.type + ? `The ${data.type} node could not be rendered, please review your json file` + : "There was a node that can't be rendered, please review your json file", + }); + showError.current = false; + } + deleteNode(data.id); + return; + } - return ( -
-
-
- -
{data.type}
-
- -
+ return ( +
+
+
+ +
{data.type}
+
+ +
-
-
- {data.node.description} -
+
+
+ {data.node.description} +
- <> - {Object.keys(data.node.template) - .filter((t) => t.charAt(0) !== "_") - .map((t: string, idx) => ( -
- {idx === 0 ? ( -
- !key.startsWith("_") && data.node.template[key].show - ).length === 0 - ? "hidden" - : "" - )} - > - Inputs -
- ) : ( - <> - )} - {data.node.template[t].show ? ( - - ) : ( - <> - )} -
- ))} -
- Output -
- - -
-
- ); + <> + {Object.keys(data.node.template) + .filter((t) => t.charAt(0) !== "_") + .map((t: string, idx) => ( +
+ {idx === 0 ? ( +
+ !key.startsWith("_") && data.node.template[key].show + ).length === 0 + ? "hidden" + : "" + )} + > + Inputs +
+ ) : ( + <> + )} + {data.node.template[t].show ? ( + + ) : ( + <> + )} +
+ ))} +
+ Output +
+ + +
+
+ ); } From ff9a2e66637be9bec990ec5e419d8e9ce0c7e889 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Tue, 25 Apr 2023 14:39:34 -0300 Subject: [PATCH 21/99] feat(frontend): add use-debounce package to dependencies in package.json --- src/frontend/package-lock.json | 12 ++++++++++++ src/frontend/package.json | 1 + 2 files changed, 13 insertions(+) diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index df61239c5..8e7c578ec 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -38,6 +38,7 @@ "reactflow": "^11.5.5", "tailwindcss": "^3.2.6", "typescript": "^4.9.5", + "use-debounce": "^9.0.4", "web-vitals": "^2.1.4" } }, @@ -17015,6 +17016,17 @@ "requires-port": "^1.0.0" } }, + "node_modules/use-debounce": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-9.0.4.tgz", + "integrity": "sha512-6X8H/mikbrt0XE8e+JXRtZ8yYVvKkdYRfmIhWZYsP8rcNs9hk3APV8Ua2mFkKRLcJKVdnX2/Vwrmg2GWKUQEaQ==", + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/use-sync-external-store": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", diff --git a/src/frontend/package.json b/src/frontend/package.json index b669569e3..bdc24b488 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -33,6 +33,7 @@ "reactflow": "^11.5.5", "tailwindcss": "^3.2.6", "typescript": "^4.9.5", + "use-debounce": "^9.0.4", "web-vitals": "^2.1.4" }, "scripts": { From f8f62834646fe651a7fe067ec3525e825aa86c59 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Tue, 25 Apr 2023 15:29:02 -0300 Subject: [PATCH 22/99] websocket first implementation --- src/frontend/src/modals/chatModal/index.tsx | 45 ++++++++++++++++----- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/src/frontend/src/modals/chatModal/index.tsx b/src/frontend/src/modals/chatModal/index.tsx index 15bc2a89a..cb9397b46 100644 --- a/src/frontend/src/modals/chatModal/index.tsx +++ b/src/frontend/src/modals/chatModal/index.tsx @@ -1,22 +1,19 @@ import { Dialog, Transition } from "@headlessui/react"; -import { - LockClosedIcon, - PaperAirplaneIcon, -} from "@heroicons/react/24/outline"; +import { LockClosedIcon, PaperAirplaneIcon } from "@heroicons/react/24/outline"; import { Fragment, useContext, useEffect, useRef, useState } from "react"; import { PopUpContext } from "../../contexts/popUpContext"; -import { NodeType } from "../../types/flow"; +import { FlowType, NodeType } from "../../types/flow"; import { TabsContext } from "../../contexts/tabsContext"; import { alertContext } from "../../contexts/alertContext"; import { classNames, snakeToNormalCase } from "../../utils"; -import { sendAll } from "../../controllers/API"; import { typesContext } from "../../contexts/typesContext"; import ChatMessage from "./chatMessage"; import { FaEraser } from "react-icons/fa"; +import { sendAllProps } from "../../types/api"; const _ = require("lodash"); -export default function ChatModal({ flow,open, setOpen }) { +export default function ChatModal({ flow, open, setOpen }:{open:boolean,setOpen:Function,flow:FlowType}) { const { updateFlow, lockChat, setLockChat, flows, tabIndex } = useContext(TabsContext); const [saveChat, setSaveChat] = useState(false); @@ -24,6 +21,7 @@ export default function ChatModal({ flow,open, setOpen }) { const [chatHistory, setChatHistory] = useState(flow.chat); const { reactFlowInstance } = useContext(typesContext); const { setErrorData, setNoticeData } = useContext(alertContext); + const [ws, setWs] = useState(null); const addChatHistory = ( message: string, isSend: boolean, @@ -58,6 +56,31 @@ export default function ChatModal({ flow,open, setOpen }) { } setSaveChat((chat) => !chat); }; + + useEffect(() => { + const newWs = new WebSocket(`/chat/${flow.id}`); + newWs.onopen = () => { + console.log('WebSocket connection established!'); + }; + newWs.onmessage = (event) => { + const data = JSON.parse(event.data); + console.log('Received data:', data); + // Do something with the data received from the WebSocket + }; + setWs(newWs); + + return () => { + newWs.close(); + }; + }, []); + + async function sendAll(data: sendAllProps) { + if (ws) { + ws.send(JSON.stringify(data)); + } + return {data:{result:"sdsdsad",thought:"dsdsad"}} + } + useEffect(() => { updateFlow({ ..._.cloneDeep(flow), chat: chatHistory }); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -213,9 +236,11 @@ export default function ChatModal({ flow,open, setOpen }) { >
-
From e4fdfdd6498ff252630d3305773465079d5fb767 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Tue, 25 Apr 2023 15:40:21 -0300 Subject: [PATCH 23/99] added new icon --- .../src/assets/Gooey Ring-5s-271px.svg | 40 +++++++++++++++++++ .../src/components/chatComponent/index.tsx | 2 - .../modals/chatModal/chatMessage/index.tsx | 4 +- src/frontend/src/modals/chatModal/index.tsx | 2 +- 4 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 src/frontend/src/assets/Gooey Ring-5s-271px.svg diff --git a/src/frontend/src/assets/Gooey Ring-5s-271px.svg b/src/frontend/src/assets/Gooey Ring-5s-271px.svg new file mode 100644 index 000000000..6c3433420 --- /dev/null +++ b/src/frontend/src/assets/Gooey Ring-5s-271px.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/frontend/src/components/chatComponent/index.tsx b/src/frontend/src/components/chatComponent/index.tsx index 5997deebf..0b251ee16 100644 --- a/src/frontend/src/components/chatComponent/index.tsx +++ b/src/frontend/src/components/chatComponent/index.tsx @@ -15,9 +15,7 @@ export default function Chat({ flow }: ChatType) { const [open, setOpen] = useState(false); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { - console.log(event) if (event.key === "K" && event.shiftKey && (event.metaKey||event.ctrlKey)) { - console.log("dfdsfds") setOpen(oldState=>!oldState); } }; diff --git a/src/frontend/src/modals/chatModal/chatMessage/index.tsx b/src/frontend/src/modals/chatModal/chatMessage/index.tsx index 0cec858a4..d49d5244d 100644 --- a/src/frontend/src/modals/chatModal/chatMessage/index.tsx +++ b/src/frontend/src/modals/chatModal/chatMessage/index.tsx @@ -25,8 +25,8 @@ export default function ChatMessage({ chat }: { chat: ChatMessageType }) { "rounded-full w-8 h-8 flex items-center my-3 justify-center",chat.isSend?"bg-black":"bg-black" )} > - {!chat.isSend && } - {chat.isSend && } + {!chat.isSend && } + {chat.isSend && }
{!chat.isSend ? (
diff --git a/src/frontend/src/modals/chatModal/index.tsx b/src/frontend/src/modals/chatModal/index.tsx index cb9397b46..c1a0645ad 100644 --- a/src/frontend/src/modals/chatModal/index.tsx +++ b/src/frontend/src/modals/chatModal/index.tsx @@ -58,7 +58,7 @@ export default function ChatModal({ flow, open, setOpen }:{open:boolean,setOpen: }; useEffect(() => { - const newWs = new WebSocket(`/chat/${flow.id}`); + const newWs = new WebSocket(`ws://backend:7860/chat/${flow.id}`); newWs.onopen = () => { console.log('WebSocket connection established!'); }; From e1544aadae4a1b66c5414216760393d1fcb8b894 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Tue, 25 Apr 2023 16:35:17 -0300 Subject: [PATCH 24/99] chore(pyproject.toml): add websockets dependency refactor(chat_manager.py): remove redundant json.dumps() and convert BaseModel to dict before sending to websocket --- poetry.lock | 144 +++++++++++------------ pyproject.toml | 1 + src/backend/langflow/api/chat_manager.py | 11 +- 3 files changed, 81 insertions(+), 75 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6616801b0..01f7dc4a6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -4530,82 +4530,82 @@ files = [ [[package]] name = "websockets" -version = "11.0.1" +version = "11.0.2" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "websockets-11.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d30cc1a90bcbf9e22e1f667c1c5a7428e2d37362288b4ebfd5118eb0b11afa9"}, - {file = "websockets-11.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dc77283a7c7b2b24e00fe8c3c4f7cf36bba4f65125777e906aae4d58d06d0460"}, - {file = "websockets-11.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0929c2ebdf00cedda77bf77685693e38c269011236e7c62182fee5848c29a4fa"}, - {file = "websockets-11.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db234da3aff01e8483cf0015b75486c04d50dbf90004bd3e5b46d384e1bd6c9e"}, - {file = "websockets-11.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7fdfbed727ce6b4b5e6622d15a6efb2098b2d9e22ba4dc54b2e3ce80f982045"}, - {file = "websockets-11.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5f3d0d177b3db3d1d02cce7ba6c0063586499ac28afe0c992be74ffc40d9257"}, - {file = "websockets-11.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:25ea5dbd3b00c56b034639dc6fe4d1dd095b8205bab1782d9a47cb020695fdf4"}, - {file = "websockets-11.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:dbeada3b8f1f6d9497840f761906c4236f912a42da4515520168bc7c525b52b0"}, - {file = "websockets-11.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:892959b627eedcdf98ac7022f9f71f050a59624b380b67862da10c32ea3c221a"}, - {file = "websockets-11.0.1-cp310-cp310-win32.whl", hash = "sha256:fc0a96a6828bfa6f1ccec62b54630bcdcc205d483f5a8806c0a8abb26101c54b"}, - {file = "websockets-11.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:3a88375b648a2c479532943cc19a018df1e5fcea85d5f31963c0b22794d1bdc1"}, - {file = "websockets-11.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3cf18bbd44b36749b7b66f047a30a40b799b8c0bd9a1b9173cba86a234b4306b"}, - {file = "websockets-11.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:deb0dd98ea4e76b833f0bfd7a6042b51115360d5dfcc7c1daa72dfc417b3327a"}, - {file = "websockets-11.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:45a85dc6b3ff76239379feb4355aadebc18d6e587c8deb866d11060755f4d3ea"}, - {file = "websockets-11.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d68bd2a3e9fff6f7043c0a711cb1ebba9f202c196a3943d0c885650cd0b6464"}, - {file = "websockets-11.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfd0b9b18d64c51e5cd322e16b5bf4fe490db65c9f7b18fd5382c824062ead7e"}, - {file = "websockets-11.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef0e6253c36e42f2637cfa3ff9b3903df60d05ec040c718999f6a0644ce1c497"}, - {file = "websockets-11.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:12180bc1d72c6a9247472c1dee9dfd7fc2e23786f25feee7204406972d8dab39"}, - {file = "websockets-11.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a797da96d4127e517a5cb0965cd03fd6ec21e02667c1258fa0579501537fbe5c"}, - {file = "websockets-11.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:07cc20655fb16aeef1a8f03236ba8671c61d332580b996b6396a5b7967ba4b3d"}, - {file = "websockets-11.0.1-cp311-cp311-win32.whl", hash = "sha256:a01c674e0efe0f14aec7e722ed0e0e272fa2f10e8ea8260837e1f4f5dc4b3e53"}, - {file = "websockets-11.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:2796f097841619acf053245f266a4f66cb27c040f0d9097e5f21301aab95ff43"}, - {file = "websockets-11.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:54d084756c50dfc8086dce97b945f210ca43950154e1e04a44a30c6e6a2bcbb1"}, - {file = "websockets-11.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fe2aed5963ca267c40a2d29b1ee4e8ab008ac8d5daa284fdda9275201b8a334"}, - {file = "websockets-11.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e92dbac318a84fef722f38ca57acef19cbb89527aba5d420b96aa2656970ee"}, - {file = "websockets-11.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec4e87eb9916b481216b1fede7d8913be799915f5216a0c801867cbed8eeb903"}, - {file = "websockets-11.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d4e0990b6a04b07095c969969da659eecf9069cf8e7b8f49c8f5ee1bb50e3352"}, - {file = "websockets-11.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c90343fd0774749d23c1891dd8b3e9210f9afd30986673ce0f9d5857f5cb1562"}, - {file = "websockets-11.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ac042e8ba9d7f2618e84af27927fdce0f3e03528eb74f343977486c093868389"}, - {file = "websockets-11.0.1-cp37-cp37m-win32.whl", hash = "sha256:385c5391becb9b58e0a4f33345e12762fd857ccf9fbf6fee428669929ba45e4c"}, - {file = "websockets-11.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:aef1602db81096ce3d3847865128c8879635bdad7963fb2b7df290edb9e9150a"}, - {file = "websockets-11.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:52ba83ea132390e426f9a7b48848248a2dc0e7120ca8c65d5a8fc1efaa4eb51b"}, - {file = "websockets-11.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:007ed0d62f7e06eeb6e3a848b0d83b9fbd9e14674a59a61326845f27d20d7452"}, - {file = "websockets-11.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f888b9565ca1d1c25ab827d184f57f4772ffbfa6baf5710b873b01936cc335ee"}, - {file = "websockets-11.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db78535b791840a584c48cf3f4215eae38a7e2f43271ecd27ce4ba8a798beaaa"}, - {file = "websockets-11.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa1c23ed3a02732fba906ec337df65d4cc23f9f453635e1a803c285b59c7d987"}, - {file = "websockets-11.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e039f106d48d3c241f1943bccfb383bd38ec39900d6dcaad0c73cc5fe129f346"}, - {file = "websockets-11.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b0ed24a3aa4213029e100257e5e73c5f912e70ca35630081de94b7f9e2cf4a9b"}, - {file = "websockets-11.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5a3022f9291bf2d35ebf65929297d625e68effd3a5647b8eb8b89d51b09394c"}, - {file = "websockets-11.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1cb23597819f68ac6a6d133a002a1b3ef12a22850236b083242c93f81f206d5a"}, - {file = "websockets-11.0.1-cp38-cp38-win32.whl", hash = "sha256:349dd1fa56a30d530555988be98013688de67809f384671883f8bf8b8c9de984"}, - {file = "websockets-11.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:87ae582cf2319e45bc457a57232daded27a3c771263cab42fb8864214bbd74ea"}, - {file = "websockets-11.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a88815a0c6253ad1312ef186620832fb347706c177730efec34e3efe75e0e248"}, - {file = "websockets-11.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d5a6fa353b5ef36970c3bd1cd7cecbc08bb8f2f1a3d008b0691208cf34ebf5b0"}, - {file = "websockets-11.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5ffe6fc5e5fe9f2634cdc59b805e4ba1fcccf3a5622f5f36c3c7c287f606e283"}, - {file = "websockets-11.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b138f4bf8a64c344e12c76283dac279d11adab89ac62ae4a32ac8490d3c94832"}, - {file = "websockets-11.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aedd94422745da60672a901f53de1f50b16e85408b18672b9b210db4a776b5a6"}, - {file = "websockets-11.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4667d4e41fa37fa3d836b2603b8b40d6887fa4838496d48791036394f7ace39"}, - {file = "websockets-11.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:43e0de552be624e5c0323ff4fcc9f0b4a9a6dc6e0116b8aa2cbb6e0d3d2baf09"}, - {file = "websockets-11.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ceeef57b9aec8f27e523de4da73c518ece7721aefe7064f18aa28baabfe61b94"}, - {file = "websockets-11.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9d91279d57f6546eaf43671d1de50621e0578f13c2f17c96c458a72d170698d7"}, - {file = "websockets-11.0.1-cp39-cp39-win32.whl", hash = "sha256:29282631da3bfeb5db497e4d3d94d56ee36222fbebd0b51014e68a2e70736fb1"}, - {file = "websockets-11.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:e2654e94c705ce9b768441d8e3a387a84951ca1056efdc4a26a4a6ee723c01b6"}, - {file = "websockets-11.0.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:60a19d4ff5f451254f8623f6aa4169065f73a50ec7b59ab6b9dcddff4aa00267"}, - {file = "websockets-11.0.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a58e83f82098d062ae5d4cbe7073b8783999c284d6f079f2fefe87cd8957ac8"}, - {file = "websockets-11.0.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b91657b65355954e47f0df874917fa200426b3a7f4e68073326a8cfc2f6deef8"}, - {file = "websockets-11.0.1-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53b8e1ee01eb5b8be5c8a69ae26b0820dbc198d092ad50b3451adc3cdd55d455"}, - {file = "websockets-11.0.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:5d8d5d17371ed9eb9f0e3a8d326bdf8172700164c2e705bc7f1905a719a189be"}, - {file = "websockets-11.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e53419201c6c1439148feb99de6b307651a88b8defd41348cc23bbe2a290de1d"}, - {file = "websockets-11.0.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:718d19c494637f28e651031b3df6a791b9e86e0097c65ed5e8ec49b400b1210e"}, - {file = "websockets-11.0.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7b2544eb3e7bc39ce59812371214cd97762080dab90c3afc857890039384753"}, - {file = "websockets-11.0.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec4a887d2236e3878c07033ad5566f6b4d5d954b85f92a219519a1745d0c93e9"}, - {file = "websockets-11.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ae59a9f0a77ecb0cbdedea7d206a547ff136e8bfbc7d2d98772fb02d398797bb"}, - {file = "websockets-11.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ef35cef161f76031f833146f895e7e302196e01c704c00d269c04d8e18f3ac37"}, - {file = "websockets-11.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79b6548e57ab18f071b9bfe3ffe02af7184dd899bc674e2817d8fe7e9e7489ec"}, - {file = "websockets-11.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8d9793f3fb0da16232503df14411dabafed5a81fc9077dc430cfc6f60e71179"}, - {file = "websockets-11.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42aa05e890fcf1faed8e535c088a1f0f27675827cbacf62d3024eb1e6d4c9e0c"}, - {file = "websockets-11.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5d4f4b341100d313b08149d7031eb6d12738ac758b0c90d2f9be8675f401b019"}, - {file = "websockets-11.0.1-py3-none-any.whl", hash = "sha256:85b4127f7da332feb932eee833c70e5e1670469e8c9de7ef3874aa2a91a6fbb2"}, - {file = "websockets-11.0.1.tar.gz", hash = "sha256:369410925b240b30ef1c1deadbd6331e9cd865ad0b8966bf31e276cc8e0da159"}, + {file = "websockets-11.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:580cc95c58118f8c39106be71e24d0b7e1ad11a155f40a2ee687f99b3e5e432e"}, + {file = "websockets-11.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:143782041e95b63083b02107f31cda999f392903ae331de1307441f3a4557d51"}, + {file = "websockets-11.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8df63dcd955eb6b2e371d95aacf8b7c535e482192cff1b6ce927d8f43fb4f552"}, + {file = "websockets-11.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9b2dced5cbbc5094678cc1ec62160f7b0fe4defd601cd28a36fde7ee71bbb5"}, + {file = "websockets-11.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0eeeea3b01c97fd3b5049a46c908823f68b59bf0e18d79b231d8d6764bc81ee"}, + {file = "websockets-11.0.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:502683c5dedfc94b9f0f6790efb26aa0591526e8403ad443dce922cd6c0ec83b"}, + {file = "websockets-11.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d3cc3e48b6c9f7df8c3798004b9c4b92abca09eeea5e1b0a39698f05b7a33b9d"}, + {file = "websockets-11.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:808b8a33c961bbd6d33c55908f7c137569b09ea7dd024bce969969aa04ecf07c"}, + {file = "websockets-11.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:34a6f8996964ccaa40da42ee36aa1572adcb1e213665e24aa2f1037da6080909"}, + {file = "websockets-11.0.2-cp310-cp310-win32.whl", hash = "sha256:8f24cd758cbe1607a91b720537685b64e4d39415649cac9177cd1257317cf30c"}, + {file = "websockets-11.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:3b87cd302f08ea9e74fdc080470eddbed1e165113c1823fb3ee6328bc40ca1d3"}, + {file = "websockets-11.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3565a8f8c7bdde7c29ebe46146bd191290413ee6f8e94cf350609720c075b0a1"}, + {file = "websockets-11.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f97e03d4d5a4f0dca739ea274be9092822f7430b77d25aa02da6775e490f6846"}, + {file = "websockets-11.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f392587eb2767afa8a34e909f2fec779f90b630622adc95d8b5e26ea8823cb8"}, + {file = "websockets-11.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7742cd4524622cc7aa71734b51294644492a961243c4fe67874971c4d3045982"}, + {file = "websockets-11.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46dda4bc2030c335abe192b94e98686615f9274f6b56f32f2dd661fb303d9d12"}, + {file = "websockets-11.0.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6b2bfa1d884c254b841b0ff79373b6b80779088df6704f034858e4d705a4802"}, + {file = "websockets-11.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1df2413266bf48430ef2a752c49b93086c6bf192d708e4a9920544c74cd2baa6"}, + {file = "websockets-11.0.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf45d273202b0c1cec0f03a7972c655b93611f2e996669667414557230a87b88"}, + {file = "websockets-11.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a09cce3dacb6ad638fdfa3154d9e54a98efe7c8f68f000e55ca9c716496ca67"}, + {file = "websockets-11.0.2-cp311-cp311-win32.whl", hash = "sha256:2174a75d579d811279855df5824676d851a69f52852edb0e7551e0eeac6f59a4"}, + {file = "websockets-11.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:c78ca3037a954a4209b9f900e0eabbc471fb4ebe96914016281df2c974a93e3e"}, + {file = "websockets-11.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a2100b02d1aaf66dc48ff1b2a72f34f6ebc575a02bc0350cc8e9fbb35940166"}, + {file = "websockets-11.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dca9708eea9f9ed300394d4775beb2667288e998eb6f542cdb6c02027430c599"}, + {file = "websockets-11.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:320ddceefd2364d4afe6576195201a3632a6f2e6d207b0c01333e965b22dbc84"}, + {file = "websockets-11.0.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2a573c8d71b7af937852b61e7ccb37151d719974146b5dc734aad350ef55a02"}, + {file = "websockets-11.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:13bd5bebcd16a4b5e403061b8b9dcc5c77e7a71e3c57e072d8dff23e33f70fba"}, + {file = "websockets-11.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:95c09427c1c57206fe04277bf871b396476d5a8857fa1b99703283ee497c7a5d"}, + {file = "websockets-11.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2eb042734e710d39e9bc58deab23a65bd2750e161436101488f8af92f183c239"}, + {file = "websockets-11.0.2-cp37-cp37m-win32.whl", hash = "sha256:5875f623a10b9ba154cb61967f940ab469039f0b5e61c80dd153a65f024d9fb7"}, + {file = "websockets-11.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:634239bc844131863762865b75211a913c536817c0da27f691400d49d256df1d"}, + {file = "websockets-11.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3178d965ec204773ab67985a09f5696ca6c3869afeed0bb51703ea404a24e975"}, + {file = "websockets-11.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:955fcdb304833df2e172ce2492b7b47b4aab5dcc035a10e093d911a1916f2c87"}, + {file = "websockets-11.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb46d2c7631b2e6f10f7c8bac7854f7c5e5288f024f1c137d4633c79ead1e3c0"}, + {file = "websockets-11.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25aae96c1060e85836552a113495db6d857400288161299d77b7b20f2ac569f2"}, + {file = "websockets-11.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2abeeae63154b7f63d9f764685b2d299e9141171b8b896688bd8baec6b3e2303"}, + {file = "websockets-11.0.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:daa1e8ea47507555ed7a34f8b49398d33dff5b8548eae3de1dc0ef0607273a33"}, + {file = "websockets-11.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:954eb789c960fa5daaed3cfe336abc066941a5d456ff6be8f0e03dd89886bb4c"}, + {file = "websockets-11.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3ffe251a31f37e65b9b9aca5d2d67fd091c234e530f13d9dce4a67959d5a3fba"}, + {file = "websockets-11.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:adf6385f677ed2e0b021845b36f55c43f171dab3a9ee0ace94da67302f1bc364"}, + {file = "websockets-11.0.2-cp38-cp38-win32.whl", hash = "sha256:aa7b33c1fb2f7b7b9820f93a5d61ffd47f5a91711bc5fa4583bbe0c0601ec0b2"}, + {file = "websockets-11.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:220d5b93764dd70d7617f1663da64256df7e7ea31fc66bc52c0e3750ee134ae3"}, + {file = "websockets-11.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0fb4480556825e4e6bf2eebdbeb130d9474c62705100c90e59f2f56459ddab42"}, + {file = "websockets-11.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ec00401846569aaf018700249996143f567d50050c5b7b650148989f956547af"}, + {file = "websockets-11.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87c69f50281126dcdaccd64d951fb57fbce272578d24efc59bce72cf264725d0"}, + {file = "websockets-11.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:232b6ba974f5d09b1b747ac232f3a3d8f86de401d7b565e837cc86988edf37ac"}, + {file = "websockets-11.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:392d409178db1e46d1055e51cc850136d302434e12d412a555e5291ab810f622"}, + {file = "websockets-11.0.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4fe2442091ff71dee0769a10449420fd5d3b606c590f78dd2b97d94b7455640"}, + {file = "websockets-11.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ede13a6998ba2568b21825809d96e69a38dc43184bdeebbde3699c8baa21d015"}, + {file = "websockets-11.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4c54086b2d2aec3c3cb887ad97e9c02c6be9f1d48381c7419a4aa932d31661e4"}, + {file = "websockets-11.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e37a76ccd483a6457580077d43bc3dfe1fd784ecb2151fcb9d1c73f424deaeba"}, + {file = "websockets-11.0.2-cp39-cp39-win32.whl", hash = "sha256:d1881518b488a920434a271a6e8a5c9481a67c4f6352ebbdd249b789c0467ddc"}, + {file = "websockets-11.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:25e265686ea385f22a00cc2b719b880797cd1bb53b46dbde969e554fb458bfde"}, + {file = "websockets-11.0.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ce69f5c742eefd039dce8622e99d811ef2135b69d10f9aa79fbf2fdcc1e56cd7"}, + {file = "websockets-11.0.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b985ba2b9e972cf99ddffc07df1a314b893095f62c75bc7c5354a9c4647c6503"}, + {file = "websockets-11.0.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b52def56d2a26e0e9c464f90cadb7e628e04f67b0ff3a76a4d9a18dfc35e3dd"}, + {file = "websockets-11.0.2-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d70a438ef2a22a581d65ad7648e949d4ccd20e3c8ed7a90bbc46df4e60320891"}, + {file = "websockets-11.0.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:752fbf420c71416fb1472fec1b4cb8631c1aa2be7149e0a5ba7e5771d75d2bb9"}, + {file = "websockets-11.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:dd906b0cdc417ea7a5f13bb3c6ca3b5fd563338dc596996cb0fdd7872d691c0a"}, + {file = "websockets-11.0.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e79065ff6549dd3c765e7916067e12a9c91df2affea0ac51bcd302aaf7ad207"}, + {file = "websockets-11.0.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46388a050d9e40316e58a3f0838c63caacb72f94129eb621a659a6e49bad27ce"}, + {file = "websockets-11.0.2-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c7de298371d913824f71b30f7685bb07ad13969c79679cca5b1f7f94fec012f"}, + {file = "websockets-11.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6d872c972c87c393e6a49c1afbdc596432df8c06d0ff7cd05aa18e885e7cfb7c"}, + {file = "websockets-11.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b444366b605d2885f0034dd889faf91b4b47668dd125591e2c64bfde611ac7e1"}, + {file = "websockets-11.0.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b967a4849db6b567dec3f7dd5d97b15ce653e3497b8ce0814e470d5e074750"}, + {file = "websockets-11.0.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2acdc82099999e44fa7bd8c886f03c70a22b1d53ae74252f389be30d64fd6004"}, + {file = "websockets-11.0.2-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:518ed6782d9916c5721ebd61bb7651d244178b74399028302c8617d0620af291"}, + {file = "websockets-11.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:58477b041099bb504e1a5ddd8aa86302ed1d5c6995bdd3db2b3084ef0135d277"}, + {file = "websockets-11.0.2-py3-none-any.whl", hash = "sha256:5004c087d17251938a52cce21b3dbdabeecbbe432ce3f5bbbf15d8692c36eac9"}, + {file = "websockets-11.0.2.tar.gz", hash = "sha256:b1a69701eb98ed83dd099de4a686dc892c413d974fa31602bc00aca7cb988ac9"}, ] [[package]] @@ -4801,4 +4801,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "f0a23552d8cbd3d38722a69698cf823cdd19a90b707538837da64a933f8d13b4" +content-hash = "1320f2d5466c6569ee0563cc81b28d8c8897c8f07b4e0912c231c6e4033a2bf8" diff --git a/pyproject.toml b/pyproject.toml index bc0753c5d..4136785ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,7 @@ fake-useragent = "^1.1.3" docstring-parser = "^0.15" psycopg2-binary = "^2.9.6" pyarrow = "^11.0.0" +websockets = "^11.0.2" [tool.poetry.group.dev.dependencies] black = "^23.1.0" diff --git a/src/backend/langflow/api/chat_manager.py b/src/backend/langflow/api/chat_manager.py index 5ce7d2452..bc9b2dc2d 100644 --- a/src/backend/langflow/api/chat_manager.py +++ b/src/backend/langflow/api/chat_manager.py @@ -89,7 +89,7 @@ class ChatManager: async def send_json(self, client_id: str, message: ChatMessage): websocket = self.active_connections[client_id] - await websocket.send_json(json.dumps(message.dict())) + await websocket.send_json(message.dict()) async def process_message(self, client_id: str, payload: Dict): # Process the graph data and chat message @@ -136,11 +136,16 @@ class ChatManager: try: chat_history = self.chat_history.get_history(client_id) - await websocket.send_json(json.dumps(chat_history)) + # iterate and make BaseModel into dict + chat_history = [chat.dict() for chat in chat_history] + await websocket.send_json(chat_history) while True: json_payload = await websocket.receive_json() - payload = json.loads(json_payload) + try: + payload = json.loads(json_payload) + except TypeError: + payload = json_payload with self.cache_manager.set_client_id(client_id): await self.process_message(client_id, payload) except Exception as e: From 4fd659703fa9cb9107cebb81505d154a902a358d Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Tue, 25 Apr 2023 16:35:44 -0300 Subject: [PATCH 25/99] socket small changes --- src/frontend/src/components/chatComponent/index.tsx | 2 +- .../src/modals/chatModal/chatMessage/index.tsx | 10 ++++------ src/frontend/src/modals/chatModal/index.tsx | 3 +-- src/frontend/src/svg.d.ts | 5 +++++ 4 files changed, 11 insertions(+), 9 deletions(-) create mode 100644 src/frontend/src/svg.d.ts diff --git a/src/frontend/src/components/chatComponent/index.tsx b/src/frontend/src/components/chatComponent/index.tsx index 0b251ee16..8108f3f36 100644 --- a/src/frontend/src/components/chatComponent/index.tsx +++ b/src/frontend/src/components/chatComponent/index.tsx @@ -26,7 +26,7 @@ export default function Chat({ flow }: ChatType) { }, []); return ( <> - + ); diff --git a/src/frontend/src/modals/chatModal/chatMessage/index.tsx b/src/frontend/src/modals/chatModal/chatMessage/index.tsx index d49d5244d..d960a149b 100644 --- a/src/frontend/src/modals/chatModal/chatMessage/index.tsx +++ b/src/frontend/src/modals/chatModal/chatMessage/index.tsx @@ -1,13 +1,11 @@ import { - ChatBubbleLeftEllipsisIcon, ChatBubbleOvalLeftEllipsisIcon, - PlusSmallIcon, } from "@heroicons/react/24/outline"; import { useState } from "react"; import { ChatMessageType } from "../../../types/chat"; import { classNames } from "../../../utils"; +import AiIcon from "../../../assets/Gooey Ring-5s-271px.svg" import { UserIcon } from "@heroicons/react/24/solid"; -import {AiFillRobot} from "react-icons/ai" var Convert = require("ansi-to-html"); var convert = new Convert({ newline: true }); @@ -22,11 +20,11 @@ export default function ChatMessage({ chat }: { chat: ChatMessageType }) { >
- {!chat.isSend && } - {chat.isSend && } + {!chat.isSend && } + {chat.isSend && }
{!chat.isSend ? (
diff --git a/src/frontend/src/modals/chatModal/index.tsx b/src/frontend/src/modals/chatModal/index.tsx index c1a0645ad..f0783a744 100644 --- a/src/frontend/src/modals/chatModal/index.tsx +++ b/src/frontend/src/modals/chatModal/index.tsx @@ -58,7 +58,7 @@ export default function ChatModal({ flow, open, setOpen }:{open:boolean,setOpen: }; useEffect(() => { - const newWs = new WebSocket(`ws://backend:7860/chat/${flow.id}`); + const newWs = new WebSocket(`ws://localhost:7860/chat/${flow.id}`); newWs.onopen = () => { console.log('WebSocket connection established!'); }; @@ -133,7 +133,6 @@ export default function ChatModal({ flow, open, setOpen }:{open:boolean,setOpen: } function validateNodes() { - console.log(reactFlowInstance); return reactFlowInstance .getNodes() .flatMap((n: NodeType) => validateNode(n)); diff --git a/src/frontend/src/svg.d.ts b/src/frontend/src/svg.d.ts new file mode 100644 index 000000000..a6e109e02 --- /dev/null +++ b/src/frontend/src/svg.d.ts @@ -0,0 +1,5 @@ +declare module '*.svg' { + const content: any; + export default content; + } + \ No newline at end of file From 9053eb81a0b1808dfd82008643c461d81708d4b1 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Tue, 25 Apr 2023 16:36:11 -0300 Subject: [PATCH 26/99] package-lock.json update --- package-lock.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..6d722a3cc --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "reactFlow", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} From 3c93e7308269a8982f16e8d7125b396955f900f1 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Tue, 25 Apr 2023 16:41:14 -0300 Subject: [PATCH 27/99] new poetry config for websocket --- poetry.lock | 161 ++++++++++++++++++++++++++----------------------- pyproject.toml | 1 + 2 files changed, 88 insertions(+), 74 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6616801b0..5419d50c1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. +# This file is automatically @generated by Poetry and should not be changed by hand. [[package]] name = "aiohttp" @@ -3837,7 +3837,7 @@ files = [ ] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and platform_machine == \"aarch64\" or python_version >= \"3\" and platform_machine == \"ppc64le\" or python_version >= \"3\" and platform_machine == \"x86_64\" or python_version >= \"3\" and platform_machine == \"amd64\" or python_version >= \"3\" and platform_machine == \"AMD64\" or python_version >= \"3\" and platform_machine == \"win32\" or python_version >= \"3\" and platform_machine == \"WIN32\""} +greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} [package.extras] aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] @@ -4016,6 +4016,10 @@ category = "main" optional = false python-versions = ">=3.8.0" files = [ + {file = "torch-2.0.0-1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:c9090bda7d2eeeecd74f51b721420dbeb44f838d4536cc1b284e879417e3064a"}, + {file = "torch-2.0.0-1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:bd42db2a48a20574d2c33489e120e9f32789c4dc13c514b0c44272972d14a2d7"}, + {file = "torch-2.0.0-1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8969aa8375bcbc0c2993e7ede0a7f889df9515f18b9b548433f412affed478d9"}, + {file = "torch-2.0.0-1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:ab2da16567cb55b67ae39e32d520d68ec736191d88ac79526ca5874754c32203"}, {file = "torch-2.0.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:7a9319a67294ef02459a19738bbfa8727bb5307b822dadd708bc2ccf6c901aca"}, {file = "torch-2.0.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:9f01fe1f6263f31bd04e1757946fd63ad531ae37f28bb2dbf66f5c826ee089f4"}, {file = "torch-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:527f4ae68df7b8301ee6b1158ca56350282ea633686537b30dbb5d7b4a52622a"}, @@ -4233,6 +4237,15 @@ category = "main" optional = false python-versions = "*" files = [ + {file = "triton-2.0.0-1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38806ee9663f4b0f7cd64790e96c579374089e58f49aac4a6608121aa55e2505"}, + {file = "triton-2.0.0-1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:226941c7b8595219ddef59a1fdb821e8c744289a132415ddd584facedeb475b1"}, + {file = "triton-2.0.0-1-cp36-cp36m-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4c9fc8c89874bc48eb7e7b2107a9b8d2c0bf139778637be5bfccb09191685cfd"}, + {file = "triton-2.0.0-1-cp37-cp37m-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d2684b6a60b9f174f447f36f933e9a45f31db96cb723723ecd2dcfd1c57b778b"}, + {file = "triton-2.0.0-1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9d4978298b74fcf59a75fe71e535c092b023088933b2f1df933ec32615e4beef"}, + {file = "triton-2.0.0-1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:74f118c12b437fb2ca25e1a04759173b517582fcf4c7be11913316c764213656"}, + {file = "triton-2.0.0-1-pp37-pypy37_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9618815a8da1d9157514f08f855d9e9ff92e329cd81c0305003eb9ec25cc5add"}, + {file = "triton-2.0.0-1-pp38-pypy38_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1aca3303629cd3136375b82cb9921727f804e47ebee27b2677fef23005c3851a"}, + {file = "triton-2.0.0-1-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e3e13aa8b527c9b642e3a9defcc0fbd8ffbe1c80d8ac8c15a01692478dc64d8a"}, {file = "triton-2.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f05a7e64e4ca0565535e3d5d3405d7e49f9d308505bb7773d21fb26a4c008c2"}, {file = "triton-2.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb4b99ca3c6844066e516658541d876c28a5f6e3a852286bbc97ad57134827fd"}, {file = "triton-2.0.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47b4d70dc92fb40af553b4460492c31dc7d3a114a979ffb7a5cdedb7eb546c08"}, @@ -4530,82 +4543,82 @@ files = [ [[package]] name = "websockets" -version = "11.0.1" +version = "11.0.2" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "websockets-11.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d30cc1a90bcbf9e22e1f667c1c5a7428e2d37362288b4ebfd5118eb0b11afa9"}, - {file = "websockets-11.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dc77283a7c7b2b24e00fe8c3c4f7cf36bba4f65125777e906aae4d58d06d0460"}, - {file = "websockets-11.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0929c2ebdf00cedda77bf77685693e38c269011236e7c62182fee5848c29a4fa"}, - {file = "websockets-11.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db234da3aff01e8483cf0015b75486c04d50dbf90004bd3e5b46d384e1bd6c9e"}, - {file = "websockets-11.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7fdfbed727ce6b4b5e6622d15a6efb2098b2d9e22ba4dc54b2e3ce80f982045"}, - {file = "websockets-11.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5f3d0d177b3db3d1d02cce7ba6c0063586499ac28afe0c992be74ffc40d9257"}, - {file = "websockets-11.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:25ea5dbd3b00c56b034639dc6fe4d1dd095b8205bab1782d9a47cb020695fdf4"}, - {file = "websockets-11.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:dbeada3b8f1f6d9497840f761906c4236f912a42da4515520168bc7c525b52b0"}, - {file = "websockets-11.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:892959b627eedcdf98ac7022f9f71f050a59624b380b67862da10c32ea3c221a"}, - {file = "websockets-11.0.1-cp310-cp310-win32.whl", hash = "sha256:fc0a96a6828bfa6f1ccec62b54630bcdcc205d483f5a8806c0a8abb26101c54b"}, - {file = "websockets-11.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:3a88375b648a2c479532943cc19a018df1e5fcea85d5f31963c0b22794d1bdc1"}, - {file = "websockets-11.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3cf18bbd44b36749b7b66f047a30a40b799b8c0bd9a1b9173cba86a234b4306b"}, - {file = "websockets-11.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:deb0dd98ea4e76b833f0bfd7a6042b51115360d5dfcc7c1daa72dfc417b3327a"}, - {file = "websockets-11.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:45a85dc6b3ff76239379feb4355aadebc18d6e587c8deb866d11060755f4d3ea"}, - {file = "websockets-11.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d68bd2a3e9fff6f7043c0a711cb1ebba9f202c196a3943d0c885650cd0b6464"}, - {file = "websockets-11.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfd0b9b18d64c51e5cd322e16b5bf4fe490db65c9f7b18fd5382c824062ead7e"}, - {file = "websockets-11.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef0e6253c36e42f2637cfa3ff9b3903df60d05ec040c718999f6a0644ce1c497"}, - {file = "websockets-11.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:12180bc1d72c6a9247472c1dee9dfd7fc2e23786f25feee7204406972d8dab39"}, - {file = "websockets-11.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a797da96d4127e517a5cb0965cd03fd6ec21e02667c1258fa0579501537fbe5c"}, - {file = "websockets-11.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:07cc20655fb16aeef1a8f03236ba8671c61d332580b996b6396a5b7967ba4b3d"}, - {file = "websockets-11.0.1-cp311-cp311-win32.whl", hash = "sha256:a01c674e0efe0f14aec7e722ed0e0e272fa2f10e8ea8260837e1f4f5dc4b3e53"}, - {file = "websockets-11.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:2796f097841619acf053245f266a4f66cb27c040f0d9097e5f21301aab95ff43"}, - {file = "websockets-11.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:54d084756c50dfc8086dce97b945f210ca43950154e1e04a44a30c6e6a2bcbb1"}, - {file = "websockets-11.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fe2aed5963ca267c40a2d29b1ee4e8ab008ac8d5daa284fdda9275201b8a334"}, - {file = "websockets-11.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84e92dbac318a84fef722f38ca57acef19cbb89527aba5d420b96aa2656970ee"}, - {file = "websockets-11.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec4e87eb9916b481216b1fede7d8913be799915f5216a0c801867cbed8eeb903"}, - {file = "websockets-11.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d4e0990b6a04b07095c969969da659eecf9069cf8e7b8f49c8f5ee1bb50e3352"}, - {file = "websockets-11.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c90343fd0774749d23c1891dd8b3e9210f9afd30986673ce0f9d5857f5cb1562"}, - {file = "websockets-11.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ac042e8ba9d7f2618e84af27927fdce0f3e03528eb74f343977486c093868389"}, - {file = "websockets-11.0.1-cp37-cp37m-win32.whl", hash = "sha256:385c5391becb9b58e0a4f33345e12762fd857ccf9fbf6fee428669929ba45e4c"}, - {file = "websockets-11.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:aef1602db81096ce3d3847865128c8879635bdad7963fb2b7df290edb9e9150a"}, - {file = "websockets-11.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:52ba83ea132390e426f9a7b48848248a2dc0e7120ca8c65d5a8fc1efaa4eb51b"}, - {file = "websockets-11.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:007ed0d62f7e06eeb6e3a848b0d83b9fbd9e14674a59a61326845f27d20d7452"}, - {file = "websockets-11.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f888b9565ca1d1c25ab827d184f57f4772ffbfa6baf5710b873b01936cc335ee"}, - {file = "websockets-11.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db78535b791840a584c48cf3f4215eae38a7e2f43271ecd27ce4ba8a798beaaa"}, - {file = "websockets-11.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa1c23ed3a02732fba906ec337df65d4cc23f9f453635e1a803c285b59c7d987"}, - {file = "websockets-11.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e039f106d48d3c241f1943bccfb383bd38ec39900d6dcaad0c73cc5fe129f346"}, - {file = "websockets-11.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b0ed24a3aa4213029e100257e5e73c5f912e70ca35630081de94b7f9e2cf4a9b"}, - {file = "websockets-11.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5a3022f9291bf2d35ebf65929297d625e68effd3a5647b8eb8b89d51b09394c"}, - {file = "websockets-11.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1cb23597819f68ac6a6d133a002a1b3ef12a22850236b083242c93f81f206d5a"}, - {file = "websockets-11.0.1-cp38-cp38-win32.whl", hash = "sha256:349dd1fa56a30d530555988be98013688de67809f384671883f8bf8b8c9de984"}, - {file = "websockets-11.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:87ae582cf2319e45bc457a57232daded27a3c771263cab42fb8864214bbd74ea"}, - {file = "websockets-11.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a88815a0c6253ad1312ef186620832fb347706c177730efec34e3efe75e0e248"}, - {file = "websockets-11.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d5a6fa353b5ef36970c3bd1cd7cecbc08bb8f2f1a3d008b0691208cf34ebf5b0"}, - {file = "websockets-11.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5ffe6fc5e5fe9f2634cdc59b805e4ba1fcccf3a5622f5f36c3c7c287f606e283"}, - {file = "websockets-11.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b138f4bf8a64c344e12c76283dac279d11adab89ac62ae4a32ac8490d3c94832"}, - {file = "websockets-11.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aedd94422745da60672a901f53de1f50b16e85408b18672b9b210db4a776b5a6"}, - {file = "websockets-11.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4667d4e41fa37fa3d836b2603b8b40d6887fa4838496d48791036394f7ace39"}, - {file = "websockets-11.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:43e0de552be624e5c0323ff4fcc9f0b4a9a6dc6e0116b8aa2cbb6e0d3d2baf09"}, - {file = "websockets-11.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ceeef57b9aec8f27e523de4da73c518ece7721aefe7064f18aa28baabfe61b94"}, - {file = "websockets-11.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9d91279d57f6546eaf43671d1de50621e0578f13c2f17c96c458a72d170698d7"}, - {file = "websockets-11.0.1-cp39-cp39-win32.whl", hash = "sha256:29282631da3bfeb5db497e4d3d94d56ee36222fbebd0b51014e68a2e70736fb1"}, - {file = "websockets-11.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:e2654e94c705ce9b768441d8e3a387a84951ca1056efdc4a26a4a6ee723c01b6"}, - {file = "websockets-11.0.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:60a19d4ff5f451254f8623f6aa4169065f73a50ec7b59ab6b9dcddff4aa00267"}, - {file = "websockets-11.0.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a58e83f82098d062ae5d4cbe7073b8783999c284d6f079f2fefe87cd8957ac8"}, - {file = "websockets-11.0.1-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b91657b65355954e47f0df874917fa200426b3a7f4e68073326a8cfc2f6deef8"}, - {file = "websockets-11.0.1-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:53b8e1ee01eb5b8be5c8a69ae26b0820dbc198d092ad50b3451adc3cdd55d455"}, - {file = "websockets-11.0.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:5d8d5d17371ed9eb9f0e3a8d326bdf8172700164c2e705bc7f1905a719a189be"}, - {file = "websockets-11.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e53419201c6c1439148feb99de6b307651a88b8defd41348cc23bbe2a290de1d"}, - {file = "websockets-11.0.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:718d19c494637f28e651031b3df6a791b9e86e0097c65ed5e8ec49b400b1210e"}, - {file = "websockets-11.0.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7b2544eb3e7bc39ce59812371214cd97762080dab90c3afc857890039384753"}, - {file = "websockets-11.0.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec4a887d2236e3878c07033ad5566f6b4d5d954b85f92a219519a1745d0c93e9"}, - {file = "websockets-11.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ae59a9f0a77ecb0cbdedea7d206a547ff136e8bfbc7d2d98772fb02d398797bb"}, - {file = "websockets-11.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ef35cef161f76031f833146f895e7e302196e01c704c00d269c04d8e18f3ac37"}, - {file = "websockets-11.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79b6548e57ab18f071b9bfe3ffe02af7184dd899bc674e2817d8fe7e9e7489ec"}, - {file = "websockets-11.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8d9793f3fb0da16232503df14411dabafed5a81fc9077dc430cfc6f60e71179"}, - {file = "websockets-11.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42aa05e890fcf1faed8e535c088a1f0f27675827cbacf62d3024eb1e6d4c9e0c"}, - {file = "websockets-11.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5d4f4b341100d313b08149d7031eb6d12738ac758b0c90d2f9be8675f401b019"}, - {file = "websockets-11.0.1-py3-none-any.whl", hash = "sha256:85b4127f7da332feb932eee833c70e5e1670469e8c9de7ef3874aa2a91a6fbb2"}, - {file = "websockets-11.0.1.tar.gz", hash = "sha256:369410925b240b30ef1c1deadbd6331e9cd865ad0b8966bf31e276cc8e0da159"}, + {file = "websockets-11.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:580cc95c58118f8c39106be71e24d0b7e1ad11a155f40a2ee687f99b3e5e432e"}, + {file = "websockets-11.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:143782041e95b63083b02107f31cda999f392903ae331de1307441f3a4557d51"}, + {file = "websockets-11.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8df63dcd955eb6b2e371d95aacf8b7c535e482192cff1b6ce927d8f43fb4f552"}, + {file = "websockets-11.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9b2dced5cbbc5094678cc1ec62160f7b0fe4defd601cd28a36fde7ee71bbb5"}, + {file = "websockets-11.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0eeeea3b01c97fd3b5049a46c908823f68b59bf0e18d79b231d8d6764bc81ee"}, + {file = "websockets-11.0.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:502683c5dedfc94b9f0f6790efb26aa0591526e8403ad443dce922cd6c0ec83b"}, + {file = "websockets-11.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d3cc3e48b6c9f7df8c3798004b9c4b92abca09eeea5e1b0a39698f05b7a33b9d"}, + {file = "websockets-11.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:808b8a33c961bbd6d33c55908f7c137569b09ea7dd024bce969969aa04ecf07c"}, + {file = "websockets-11.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:34a6f8996964ccaa40da42ee36aa1572adcb1e213665e24aa2f1037da6080909"}, + {file = "websockets-11.0.2-cp310-cp310-win32.whl", hash = "sha256:8f24cd758cbe1607a91b720537685b64e4d39415649cac9177cd1257317cf30c"}, + {file = "websockets-11.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:3b87cd302f08ea9e74fdc080470eddbed1e165113c1823fb3ee6328bc40ca1d3"}, + {file = "websockets-11.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3565a8f8c7bdde7c29ebe46146bd191290413ee6f8e94cf350609720c075b0a1"}, + {file = "websockets-11.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f97e03d4d5a4f0dca739ea274be9092822f7430b77d25aa02da6775e490f6846"}, + {file = "websockets-11.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f392587eb2767afa8a34e909f2fec779f90b630622adc95d8b5e26ea8823cb8"}, + {file = "websockets-11.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7742cd4524622cc7aa71734b51294644492a961243c4fe67874971c4d3045982"}, + {file = "websockets-11.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46dda4bc2030c335abe192b94e98686615f9274f6b56f32f2dd661fb303d9d12"}, + {file = "websockets-11.0.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6b2bfa1d884c254b841b0ff79373b6b80779088df6704f034858e4d705a4802"}, + {file = "websockets-11.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1df2413266bf48430ef2a752c49b93086c6bf192d708e4a9920544c74cd2baa6"}, + {file = "websockets-11.0.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf45d273202b0c1cec0f03a7972c655b93611f2e996669667414557230a87b88"}, + {file = "websockets-11.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a09cce3dacb6ad638fdfa3154d9e54a98efe7c8f68f000e55ca9c716496ca67"}, + {file = "websockets-11.0.2-cp311-cp311-win32.whl", hash = "sha256:2174a75d579d811279855df5824676d851a69f52852edb0e7551e0eeac6f59a4"}, + {file = "websockets-11.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:c78ca3037a954a4209b9f900e0eabbc471fb4ebe96914016281df2c974a93e3e"}, + {file = "websockets-11.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a2100b02d1aaf66dc48ff1b2a72f34f6ebc575a02bc0350cc8e9fbb35940166"}, + {file = "websockets-11.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dca9708eea9f9ed300394d4775beb2667288e998eb6f542cdb6c02027430c599"}, + {file = "websockets-11.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:320ddceefd2364d4afe6576195201a3632a6f2e6d207b0c01333e965b22dbc84"}, + {file = "websockets-11.0.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2a573c8d71b7af937852b61e7ccb37151d719974146b5dc734aad350ef55a02"}, + {file = "websockets-11.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:13bd5bebcd16a4b5e403061b8b9dcc5c77e7a71e3c57e072d8dff23e33f70fba"}, + {file = "websockets-11.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:95c09427c1c57206fe04277bf871b396476d5a8857fa1b99703283ee497c7a5d"}, + {file = "websockets-11.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2eb042734e710d39e9bc58deab23a65bd2750e161436101488f8af92f183c239"}, + {file = "websockets-11.0.2-cp37-cp37m-win32.whl", hash = "sha256:5875f623a10b9ba154cb61967f940ab469039f0b5e61c80dd153a65f024d9fb7"}, + {file = "websockets-11.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:634239bc844131863762865b75211a913c536817c0da27f691400d49d256df1d"}, + {file = "websockets-11.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3178d965ec204773ab67985a09f5696ca6c3869afeed0bb51703ea404a24e975"}, + {file = "websockets-11.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:955fcdb304833df2e172ce2492b7b47b4aab5dcc035a10e093d911a1916f2c87"}, + {file = "websockets-11.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb46d2c7631b2e6f10f7c8bac7854f7c5e5288f024f1c137d4633c79ead1e3c0"}, + {file = "websockets-11.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25aae96c1060e85836552a113495db6d857400288161299d77b7b20f2ac569f2"}, + {file = "websockets-11.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2abeeae63154b7f63d9f764685b2d299e9141171b8b896688bd8baec6b3e2303"}, + {file = "websockets-11.0.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:daa1e8ea47507555ed7a34f8b49398d33dff5b8548eae3de1dc0ef0607273a33"}, + {file = "websockets-11.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:954eb789c960fa5daaed3cfe336abc066941a5d456ff6be8f0e03dd89886bb4c"}, + {file = "websockets-11.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3ffe251a31f37e65b9b9aca5d2d67fd091c234e530f13d9dce4a67959d5a3fba"}, + {file = "websockets-11.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:adf6385f677ed2e0b021845b36f55c43f171dab3a9ee0ace94da67302f1bc364"}, + {file = "websockets-11.0.2-cp38-cp38-win32.whl", hash = "sha256:aa7b33c1fb2f7b7b9820f93a5d61ffd47f5a91711bc5fa4583bbe0c0601ec0b2"}, + {file = "websockets-11.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:220d5b93764dd70d7617f1663da64256df7e7ea31fc66bc52c0e3750ee134ae3"}, + {file = "websockets-11.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0fb4480556825e4e6bf2eebdbeb130d9474c62705100c90e59f2f56459ddab42"}, + {file = "websockets-11.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ec00401846569aaf018700249996143f567d50050c5b7b650148989f956547af"}, + {file = "websockets-11.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87c69f50281126dcdaccd64d951fb57fbce272578d24efc59bce72cf264725d0"}, + {file = "websockets-11.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:232b6ba974f5d09b1b747ac232f3a3d8f86de401d7b565e837cc86988edf37ac"}, + {file = "websockets-11.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:392d409178db1e46d1055e51cc850136d302434e12d412a555e5291ab810f622"}, + {file = "websockets-11.0.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4fe2442091ff71dee0769a10449420fd5d3b606c590f78dd2b97d94b7455640"}, + {file = "websockets-11.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ede13a6998ba2568b21825809d96e69a38dc43184bdeebbde3699c8baa21d015"}, + {file = "websockets-11.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4c54086b2d2aec3c3cb887ad97e9c02c6be9f1d48381c7419a4aa932d31661e4"}, + {file = "websockets-11.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e37a76ccd483a6457580077d43bc3dfe1fd784ecb2151fcb9d1c73f424deaeba"}, + {file = "websockets-11.0.2-cp39-cp39-win32.whl", hash = "sha256:d1881518b488a920434a271a6e8a5c9481a67c4f6352ebbdd249b789c0467ddc"}, + {file = "websockets-11.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:25e265686ea385f22a00cc2b719b880797cd1bb53b46dbde969e554fb458bfde"}, + {file = "websockets-11.0.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ce69f5c742eefd039dce8622e99d811ef2135b69d10f9aa79fbf2fdcc1e56cd7"}, + {file = "websockets-11.0.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b985ba2b9e972cf99ddffc07df1a314b893095f62c75bc7c5354a9c4647c6503"}, + {file = "websockets-11.0.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b52def56d2a26e0e9c464f90cadb7e628e04f67b0ff3a76a4d9a18dfc35e3dd"}, + {file = "websockets-11.0.2-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d70a438ef2a22a581d65ad7648e949d4ccd20e3c8ed7a90bbc46df4e60320891"}, + {file = "websockets-11.0.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:752fbf420c71416fb1472fec1b4cb8631c1aa2be7149e0a5ba7e5771d75d2bb9"}, + {file = "websockets-11.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:dd906b0cdc417ea7a5f13bb3c6ca3b5fd563338dc596996cb0fdd7872d691c0a"}, + {file = "websockets-11.0.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e79065ff6549dd3c765e7916067e12a9c91df2affea0ac51bcd302aaf7ad207"}, + {file = "websockets-11.0.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46388a050d9e40316e58a3f0838c63caacb72f94129eb621a659a6e49bad27ce"}, + {file = "websockets-11.0.2-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c7de298371d913824f71b30f7685bb07ad13969c79679cca5b1f7f94fec012f"}, + {file = "websockets-11.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6d872c972c87c393e6a49c1afbdc596432df8c06d0ff7cd05aa18e885e7cfb7c"}, + {file = "websockets-11.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b444366b605d2885f0034dd889faf91b4b47668dd125591e2c64bfde611ac7e1"}, + {file = "websockets-11.0.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b967a4849db6b567dec3f7dd5d97b15ce653e3497b8ce0814e470d5e074750"}, + {file = "websockets-11.0.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2acdc82099999e44fa7bd8c886f03c70a22b1d53ae74252f389be30d64fd6004"}, + {file = "websockets-11.0.2-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:518ed6782d9916c5721ebd61bb7651d244178b74399028302c8617d0620af291"}, + {file = "websockets-11.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:58477b041099bb504e1a5ddd8aa86302ed1d5c6995bdd3db2b3084ef0135d277"}, + {file = "websockets-11.0.2-py3-none-any.whl", hash = "sha256:5004c087d17251938a52cce21b3dbdabeecbbe432ce3f5bbbf15d8692c36eac9"}, + {file = "websockets-11.0.2.tar.gz", hash = "sha256:b1a69701eb98ed83dd099de4a686dc892c413d974fa31602bc00aca7cb988ac9"}, ] [[package]] @@ -4801,4 +4814,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "f0a23552d8cbd3d38722a69698cf823cdd19a90b707538837da64a933f8d13b4" +content-hash = "1320f2d5466c6569ee0563cc81b28d8c8897c8f07b4e0912c231c6e4033a2bf8" diff --git a/pyproject.toml b/pyproject.toml index bc0753c5d..4136785ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,7 @@ fake-useragent = "^1.1.3" docstring-parser = "^0.15" psycopg2-binary = "^2.9.6" pyarrow = "^11.0.0" +websockets = "^11.0.2" [tool.poetry.group.dev.dependencies] black = "^23.1.0" From 2d6854165024a6bdffa98e05e78b792a6a88db17 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Tue, 25 Apr 2023 17:30:53 -0300 Subject: [PATCH 28/99] refactor(api): remove sender field from ChatMessage and ChatResponse schemas fix(api): fix ChatManager.get_history method to exclude start and stream messages feat(api): add is_bot field to ChatMessage, ChatResponse, and FileResponse schemas --- src/backend/langflow/api/callback.py | 4 +--- src/backend/langflow/api/chat.py | 3 +-- src/backend/langflow/api/chat_manager.py | 30 +++++++++++------------- src/backend/langflow/api/schemas.py | 11 ++++----- 4 files changed, 20 insertions(+), 28 deletions(-) diff --git a/src/backend/langflow/api/callback.py b/src/backend/langflow/api/callback.py index 47a8d945c..cad4b1416 100644 --- a/src/backend/langflow/api/callback.py +++ b/src/backend/langflow/api/callback.py @@ -12,7 +12,5 @@ class StreamingLLMCallbackHandler(AsyncCallbackHandler): self.websocket = websocket async def on_llm_new_token(self, token: str, **kwargs: Any) -> None: - resp = ChatResponse( - sender="bot", message=token, type="stream", intermediate_steps="" - ) + resp = ChatResponse(message=token, type="stream", intermediate_steps="") await self.websocket.send_json(resp.dict()) diff --git a/src/backend/langflow/api/chat.py b/src/backend/langflow/api/chat.py index b2da73d52..d5c2dc879 100644 --- a/src/backend/langflow/api/chat.py +++ b/src/backend/langflow/api/chat.py @@ -8,6 +8,5 @@ chat_manager = ChatManager() @router.websocket("/chat/{client_id}") async def websocket_endpoint(client_id: str, websocket: WebSocket): + """Websocket endpoint for chat.""" await chat_manager.handle_websocket(client_id, websocket) - - diff --git a/src/backend/langflow/api/chat_manager.py b/src/backend/langflow/api/chat_manager.py index bc9b2dc2d..8f407a791 100644 --- a/src/backend/langflow/api/chat_manager.py +++ b/src/backend/langflow/api/chat_manager.py @@ -26,11 +26,17 @@ class ChatHistory(AsyncSubject): self.history: Dict[str, List[ChatMessage]] = defaultdict(list) async def add_message(self, client_id: str, message: ChatMessage): + """Add a message to the chat history.""" + self.history[client_id].append(message) await self.notify() def get_history(self, client_id: str) -> List[ChatMessage]: - return self.history[client_id] + """Get the chat history for a client.""" + if history := self.history.get(client_id, []): + return [msg for msg in history if msg.type not in ["start", "stream"]] + else: + return [] class ChatManager: @@ -46,7 +52,7 @@ class ChatManager: client_id = self.cache_manager.current_client_id if client_id in self.active_connections: chat_response = self.chat_history.get_history(client_id)[-1] - if chat_response.sender == "bot": + if chat_response.is_bot: # Process FileResponse if isinstance(chat_response, FileResponse): # If data_type is pandas, convert to csv @@ -63,7 +69,6 @@ class ChatManager: self.last_cached_object_dict = self.cache_manager.get_last() # Add a new ChatResponse with the data chat_response = FileResponse( - sender="bot", message=None, type="file", data=self.last_cached_object_dict["obj"], @@ -94,13 +99,11 @@ class ChatManager: async def process_message(self, client_id: str, payload: Dict): # Process the graph data and chat message chat_message = payload.pop("message", "") - chat_message = ChatMessage(sender="you", message=chat_message) + chat_message = ChatMessage(message=chat_message) await self.chat_history.add_message(client_id, chat_message) graph_data = payload - start_resp = ChatResponse( - sender="bot", message=None, type="start", intermediate_steps="" - ) + start_resp = ChatResponse(message=None, type="start", intermediate_steps="") await self.chat_history.add_message(client_id, start_resp) is_first_message = len(self.chat_history.get_history(client_id=client_id)) == 0 @@ -117,14 +120,9 @@ class ChatManager: except Exception as e: # Log stack trace logger.exception(e) - error_resp = ChatResponse( - sender="bot", message=str(e), type="error", intermediate_steps="" - ) - await self.send_json(client_id, error_resp) - return + raise e # Send a response back to the frontend, if needed response = ChatResponse( - sender="bot", message=result or "", intermediate_steps=intermediate_steps or "", type="end", @@ -151,6 +149,7 @@ class ChatManager: except Exception as e: # Handle any exceptions that might occur print(f"Error: {e}") + raise e finally: self.disconnect(client_id) @@ -198,11 +197,10 @@ def try_setting_streaming_options(langchain_object, websocket): llm = langchain_object.llm_chain.llm if isinstance(llm, (OpenAI, ChatOpenAI, AzureOpenAI, AzureChatOpenAI)): llm.streaming = bool(hasattr(llm, "streaming")) - - if hasattr(langchain_object, "callback_manager"): stream_handler = StreamingLLMCallbackHandler(websocket) stream_manager = AsyncCallbackManager([stream_handler]) - langchain_object.callback_manager = stream_manager + llm.callback_manager = stream_manager + return langchain_object diff --git a/src/backend/langflow/api/schemas.py b/src/backend/langflow/api/schemas.py index 1aefe5c8e..c9b210210 100644 --- a/src/backend/langflow/api/schemas.py +++ b/src/backend/langflow/api/schemas.py @@ -5,14 +5,9 @@ from pydantic import BaseModel, validator class ChatMessage(BaseModel): """Chat message schema.""" - sender: str + is_bot: bool = False message: Union[str, None] = None - - @validator("sender") - def sender_must_be_bot_or_you(cls, v): - if v not in ["bot", "you"]: - raise ValueError("sender must be bot or you") - return v + type: str = "human" class ChatResponse(ChatMessage): @@ -20,6 +15,7 @@ class ChatResponse(ChatMessage): intermediate_steps: str type: str + is_bot: bool = True @validator("type") def validate_message_type(cls, v): @@ -34,6 +30,7 @@ class FileResponse(ChatMessage): data: Any data_type: str type: str = "file" + is_bot: bool = True @validator("data_type") def validate_data_type(cls, v): From b526c436cd6d4a2f65e0249044de8176d117170e Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Tue, 25 Apr 2023 17:34:14 -0300 Subject: [PATCH 29/99] removed chat history from flow save --- .../src/components/chatComponent/index.tsx | 3 +- src/frontend/src/contexts/tabsContext.tsx | 55 ++++++++------- src/frontend/src/modals/chatModal/index.tsx | 69 ++++++++----------- src/frontend/src/types/flow/index.ts | 1 - 4 files changed, 60 insertions(+), 68 deletions(-) diff --git a/src/frontend/src/components/chatComponent/index.tsx b/src/frontend/src/components/chatComponent/index.tsx index 8108f3f36..668a970bd 100644 --- a/src/frontend/src/components/chatComponent/index.tsx +++ b/src/frontend/src/components/chatComponent/index.tsx @@ -4,14 +4,13 @@ import { useState, } from "react"; -import { ChatType } from "../../types/chat"; +import { ChatMessageType, ChatType } from "../../types/chat"; import ChatTrigger from "./chatTrigger"; import ChatModal from "../../modals/chatModal"; const _ = require("lodash"); export default function Chat({ flow }: ChatType) { - const [open, setOpen] = useState(false); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { diff --git a/src/frontend/src/contexts/tabsContext.tsx b/src/frontend/src/contexts/tabsContext.tsx index 97e62e5e2..b430591b1 100644 --- a/src/frontend/src/contexts/tabsContext.tsx +++ b/src/frontend/src/contexts/tabsContext.tsx @@ -1,11 +1,18 @@ -import { createContext, useEffect, useState, useRef, ReactNode, useContext } from "react"; +import { + createContext, + useEffect, + useState, + useRef, + ReactNode, + useContext, +} from "react"; import { FlowType } from "../types/flow"; import { TabsContextType } from "../types/tabs"; import { normalCaseToSnakeCase } from "../utils"; import { alertContext } from "./alertContext"; const TabsContextInitialValue: TabsContextType = { - save:()=>{}, + save: () => {}, tabIndex: 0, setTabIndex: (index: number) => {}, flows: [], @@ -13,11 +20,11 @@ const TabsContextInitialValue: TabsContextType = { addFlow: (flowData?: any) => {}, updateFlow: (newFlow: FlowType) => {}, incrementNodeId: () => 0, - downloadFlow: (flow:FlowType) => {}, + downloadFlow: (flow: FlowType) => {}, uploadFlow: () => {}, lockChat: false, - setLockChat:(prevState:boolean)=>{}, - hardReset:()=>{}, + setLockChat: (prevState: boolean) => {}, + hardReset: () => {}, }; export const TabsContext = createContext( @@ -25,7 +32,7 @@ export const TabsContext = createContext( ); export function TabsProvider({ children }: { children: ReactNode }) { - const {setNoticeData} = useContext(alertContext) + const { setNoticeData } = useContext(alertContext); const [tabIndex, setTabIndex] = useState(0); const [flows, setFlows] = useState>([]); const [id, setId] = useState(0); @@ -36,20 +43,18 @@ export function TabsProvider({ children }: { children: ReactNode }) { newNodeId.current = newNodeId.current + 1; return newNodeId.current; } - function save(){ + function save() { if (flows.length !== 0) - window.localStorage.setItem( - "tabsData", - JSON.stringify({ tabIndex, flows, id, nodeId: newNodeId.current }) - ); + window.localStorage.setItem( + "tabsData", + JSON.stringify({ tabIndex, flows, id, nodeId: newNodeId.current }) + ); } useEffect(() => { //save tabs locally - save() + save(); }, [flows, id, tabIndex, newNodeId]); - - useEffect(() => { //get tabs locally saved let cookie = window.localStorage.getItem("tabsData"); @@ -61,15 +66,17 @@ export function TabsProvider({ children }: { children: ReactNode }) { newNodeId.current = cookieObject.nodeId; } }, []); - function hardReset(){ - newNodeId.current=0; - setTabIndex(0);setFlows([]);setId(0); + function hardReset() { + newNodeId.current = 0; + setTabIndex(0); + setFlows([]); + setId(0); } /** * Downloads the current flow as a JSON file */ - function downloadFlow(flow:FlowType) { + function downloadFlow(flow: FlowType) { // create a data URI with the current flow data const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent( JSON.stringify(flow) @@ -82,7 +89,9 @@ export function TabsProvider({ children }: { children: ReactNode }) { // simulate a click on the link element to trigger the download link.click(); - setNoticeData({title:"Warning: Critical data,JSON file may including API keys."}) + setNoticeData({ + title: "Warning: Critical data,JSON file may including API keys.", + }); } /** @@ -139,15 +148,14 @@ export function TabsProvider({ children }: { children: ReactNode }) { function addFlow(flow?: FlowType) { // Get data from the flow or set it to null if there's no flow provided. const data = flow?.data ? flow.data : null; - const description = flow?.description?flow.description:"" + const description = flow?.description ? flow.description : ""; // Create a new flow with a default name if no flow is provided. let newFlow: FlowType = { description, - name: flow?.name??"New Flow", + name: flow?.name ?? "New Flow", id: id.toString(), data, - chat: flow ? flow.chat : [], }; // Increment the ID counter. @@ -171,10 +179,9 @@ export function TabsProvider({ children }: { children: ReactNode }) { const newFlows = [...prevState]; const index = newFlows.findIndex((flow) => flow.id === newFlow.id); if (index !== -1) { - newFlows[index].description = newFlow.description??"" + newFlows[index].description = newFlow.description ?? ""; newFlows[index].data = newFlow.data; newFlows[index].name = newFlow.name; - newFlows[index].chat = newFlow.chat; } return newFlows; }); diff --git a/src/frontend/src/modals/chatModal/index.tsx b/src/frontend/src/modals/chatModal/index.tsx index f0783a744..dc462e328 100644 --- a/src/frontend/src/modals/chatModal/index.tsx +++ b/src/frontend/src/modals/chatModal/index.tsx @@ -10,15 +10,23 @@ import { typesContext } from "../../contexts/typesContext"; import ChatMessage from "./chatMessage"; import { FaEraser } from "react-icons/fa"; import { sendAllProps } from "../../types/api"; +import { ChatMessageType } from "../../types/chat"; const _ = require("lodash"); -export default function ChatModal({ flow, open, setOpen }:{open:boolean,setOpen:Function,flow:FlowType}) { - const { updateFlow, lockChat, setLockChat, flows, tabIndex } = +export default function ChatModal({ + flow, + open, + setOpen, +}: { + open: boolean; + setOpen: Function; + flow: FlowType; +}) { + const { updateFlow, lockChat, setLockChat} = useContext(TabsContext); - const [saveChat, setSaveChat] = useState(false); const [chatValue, setChatValue] = useState(""); - const [chatHistory, setChatHistory] = useState(flow.chat); + const [chatHistory, setChatHistory] = useState([]); const { reactFlowInstance } = useContext(typesContext); const { setErrorData, setNoticeData } = useContext(alertContext); const [ws, setWs] = useState(null); @@ -27,13 +35,9 @@ export default function ChatModal({ flow, open, setOpen }:{open:boolean,setOpen: isSend: boolean, thought?: string ) => { - let tabsChange = false; + setChatHistory((old) => { let newChat = _.cloneDeep(old); - if (JSON.stringify(flow.chat) !== JSON.stringify(old)) { - tabsChange = true; - return old; - } if (thought) { newChat.push({ message, isSend, thought }); } else { @@ -41,53 +45,36 @@ export default function ChatModal({ flow, open, setOpen }:{open:boolean,setOpen: } return newChat; }); - if (tabsChange) { - if (thought) { - updateFlow({ - ..._.cloneDeep(flow), - chat: [...flow.chat, { isSend, message, thought }], - }); - } else { - updateFlow({ - ..._.cloneDeep(flow), - chat: [...flow.chat, { isSend, message }], - }); - } - } - setSaveChat((chat) => !chat); }; useEffect(() => { const newWs = new WebSocket(`ws://localhost:7860/chat/${flow.id}`); newWs.onopen = () => { - console.log('WebSocket connection established!'); + console.log("WebSocket connection established!"); }; newWs.onmessage = (event) => { - const data = JSON.parse(event.data); - console.log('Received data:', data); - // Do something with the data received from the WebSocket + const data = JSON.parse(event.data); + console.log("Received data:", data); + if(Array.isArray(data)){ + console.log("entrou") + setChatHistory([{isSend:true,message:"sdsdsad"}]) + } + // Do something with the data received from the WebSocket }; setWs(newWs); - + return () => { - newWs.close(); + newWs.close(); }; - }, []); + }, []); - async function sendAll(data: sendAllProps) { + async function sendAll(data: sendAllProps) { if (ws) { - ws.send(JSON.stringify(data)); + ws.send(JSON.stringify(data)); } - return {data:{result:"sdsdsad",thought:"dsdsad"}} - } + return { data: { result: "sdsdsad", thought: "dsdsad" } }; + } - useEffect(() => { - updateFlow({ ..._.cloneDeep(flow), chat: chatHistory }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [saveChat]); - useEffect(() => { - setChatHistory(flow.chat); - }, [flow]); useEffect(() => { if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" }); }, [chatHistory]); diff --git a/src/frontend/src/types/flow/index.ts b/src/frontend/src/types/flow/index.ts index 50b0cab7a..26354c55c 100644 --- a/src/frontend/src/types/flow/index.ts +++ b/src/frontend/src/types/flow/index.ts @@ -6,7 +6,6 @@ export type FlowType = { name: string; id: string; data: ReactFlowJsonObject; - chat: Array; description:string; }; export type NodeType = {id:string,type:string,position:XYPosition,data:NodeDataType} From 61a3b9aad4cdcd0adc59cce7bdb2d0a97beb6808 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Tue, 25 Apr 2023 17:51:16 -0300 Subject: [PATCH 30/99] chat working as old with websocket, need to improve error handling and file events --- src/frontend/src/modals/chatModal/index.tsx | 38 +++++++++++++++------ 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/frontend/src/modals/chatModal/index.tsx b/src/frontend/src/modals/chatModal/index.tsx index dc462e328..9eb6de490 100644 --- a/src/frontend/src/modals/chatModal/index.tsx +++ b/src/frontend/src/modals/chatModal/index.tsx @@ -10,7 +10,7 @@ import { typesContext } from "../../contexts/typesContext"; import ChatMessage from "./chatMessage"; import { FaEraser } from "react-icons/fa"; import { sendAllProps } from "../../types/api"; -import { ChatMessageType } from "../../types/chat"; +import { ChatMessageType, ChatType } from "../../types/chat"; const _ = require("lodash"); @@ -23,8 +23,7 @@ export default function ChatModal({ setOpen: Function; flow: FlowType; }) { - const { updateFlow, lockChat, setLockChat} = - useContext(TabsContext); + const { updateFlow, lockChat, setLockChat } = useContext(TabsContext); const [chatValue, setChatValue] = useState(""); const [chatHistory, setChatHistory] = useState([]); const { reactFlowInstance } = useContext(typesContext); @@ -35,7 +34,6 @@ export default function ChatModal({ isSend: boolean, thought?: string ) => { - setChatHistory((old) => { let newChat = _.cloneDeep(old); if (thought) { @@ -55,9 +53,31 @@ export default function ChatModal({ newWs.onmessage = (event) => { const data = JSON.parse(event.data); console.log("Received data:", data); - if(Array.isArray(data)){ - console.log("entrou") - setChatHistory([{isSend:true,message:"sdsdsad"}]) + //get chat history + if (Array.isArray(data)) { + console.log("entrou"); + + setChatHistory((_) => { + let newChatHistory: ChatMessageType[] = []; + data.forEach( + (chatItem: { + intermediate_steps?: "string"; + is_bot: boolean; + message: string; + type: string; + }) => { + newChatHistory.push({ + isSend: !chatItem.is_bot, + message: chatItem.message, + thought:chatItem.intermediate_steps + }); + } + ); + return newChatHistory; + }); + } + if(data.type==='end'){ + addChatHistory(data.message, false, data.intermediate_steps); } // Do something with the data received from the WebSocket }; @@ -72,7 +92,6 @@ export default function ChatModal({ if (ws) { ws.send(JSON.stringify(data)); } - return { data: { result: "sdsdsad", thought: "dsdsad" } }; } useEffect(() => { @@ -143,8 +162,7 @@ export default function ChatModal({ name: flow.name, description: flow.description, }) - .then((r) => { - addChatHistory(r.data.result, false, r.data.thought); + .then(() => { setLockChat(false); }) .catch((error) => { From be5b7822d7dc68417b0b9af1e272485f60a285bd Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Tue, 25 Apr 2023 18:36:46 -0300 Subject: [PATCH 31/99] custom scrollbar and text input became text area for chat --- .../src/modals/chatModal/chatInput/index.tsx | 57 +++++++++++++++++++ src/frontend/src/modals/chatModal/index.tsx | 44 +++----------- src/frontend/tailwind.config.js | 16 +++++- 3 files changed, 80 insertions(+), 37 deletions(-) create mode 100644 src/frontend/src/modals/chatModal/chatInput/index.tsx diff --git a/src/frontend/src/modals/chatModal/chatInput/index.tsx b/src/frontend/src/modals/chatModal/chatInput/index.tsx new file mode 100644 index 000000000..7b371edb1 --- /dev/null +++ b/src/frontend/src/modals/chatModal/chatInput/index.tsx @@ -0,0 +1,57 @@ +import { LockClosedIcon, PaperAirplaneIcon } from "@heroicons/react/24/outline"; +import { classNames } from "../../../utils"; +import { useRef } from "react"; + +export default function ChatInput({ + lockChat, + chatValue, + sendMessage, + setChatValue, +}: { + lockChat:boolean; + chatValue:string; + sendMessage:Function; + setChatValue:Function; +}) { + const inputRef = useRef(null); + return ( + <> + -
-
- -
-
- -
-
-
- - -
-
- - - ); +
+
+ + +
+ +
+
+
+
+
+
+ + Export as + +
+
+
+
+ + { + if (event.target.value != "") { + let newFlow = flows[tabIndex]; + newFlow.name = event.target.value; + setName(event.target.value); + updateFlow(newFlow); + } else { + setName(event.target.value); + } + }} + type="text" + name="name" + value={name ?? null} + placeholder="File name" + id="name" + className="focus:border focus:border-blue block w-full px-3 py-2 border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-800 dark:border-gray-600 dark:focus:border-blue-500 dark:focus:ring-blue-500 text-gray-900 dark:text-gray-100" + /> +
+
+ + +
+ +
+ +
+
+ +
+
+
+
+
+
+
+ + + ); } diff --git a/src/frontend/src/pages/FlowPage/components/tabsManagerComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/tabsManagerComponent/index.tsx index 6d7a4d844..e6a037d10 100644 --- a/src/frontend/src/pages/FlowPage/components/tabsManagerComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/tabsManagerComponent/index.tsx @@ -5,11 +5,11 @@ import { TabsContext } from "../../../../contexts/tabsContext"; import FlowPage from "../.."; import { darkContext } from "../../../../contexts/darkContext"; import { - ArrowDownTrayIcon, - ArrowUpTrayIcon, - BellIcon, - MoonIcon, - SunIcon, + ArrowDownTrayIcon, + ArrowUpTrayIcon, + BellIcon, + MoonIcon, + SunIcon, } from "@heroicons/react/24/outline"; import { PopUpContext } from "../../../../contexts/popUpContext"; import AlertDropdown from "../../../../alerts/alertDropDown"; @@ -18,103 +18,98 @@ import ImportModal from "../../../../modals/importModal"; import ExportModal from "../../../../modals/exportModal"; export default function TabsManagerComponent() { - const { flows, addFlow, tabIndex, setTabIndex, uploadFlow, downloadFlow } = - useContext(TabsContext); - const { openPopUp } = useContext(PopUpContext); - const AlertWidth = 256; - const { dark, setDark } = useContext(darkContext); - const { notificationCenter, setNotificationCenter } = - useContext(alertContext); - useEffect(() => { - //create the first flow - if (flows.length === 0) { - addFlow(); - } - }, [addFlow, flows.length]); + const { flows, addFlow, tabIndex, setTabIndex, uploadFlow, downloadFlow } = + useContext(TabsContext); + const { openPopUp } = useContext(PopUpContext); + const AlertWidth = 256; + const { dark, setDark } = useContext(darkContext); + const { notificationCenter, setNotificationCenter } = + useContext(alertContext); + useEffect(() => { + //create the first flow + if (flows.length === 0) { + addFlow(); + } + }, [addFlow, flows.length]); - return ( -
-
- {flows.map((flow, index) => { - return ( - setTabIndex(index)} - selected={index === tabIndex} - key={index} - flow={flow} - /> - ); - })} - { - addFlow(); - }} - selected={false} - flow={null} - /> -
- - - - -
-
-
- - {flows[tabIndex] ? ( - - ) : ( - <> - )} - -
-
- ); + return ( +
+
+ {flows.map((flow, index) => { + return ( + setTabIndex(index)} + selected={index === tabIndex} + key={index} + flow={flow} + /> + ); + })} + { + addFlow(); + }} + selected={false} + flow={null} + /> +
+ + + + +
+
+
+ + {flows[tabIndex] ? ( + + ) : ( + <> + )} + +
+
+ ); } diff --git a/src/frontend/src/types/alerts/index.ts b/src/frontend/src/types/alerts/index.ts index a8478ba1a..7033d99eb 100644 --- a/src/frontend/src/types/alerts/index.ts +++ b/src/frontend/src/types/alerts/index.ts @@ -1,12 +1,29 @@ -export type ErrorAlertType = {title:string,list:Array,id:string,removeAlert:(id:string)=>void} -export type NoticeAlertType = {title:string,link:string,id:string,removeAlert:(id:string)=>void} -export type SuccessAlertType = {title:string,id:string, removeAlert:(id:string)=>void} -export type SingleAlertComponentType = {dropItem:AlertItemType,removeAlert:(index:string)=>void} +export type ErrorAlertType = { + title: string; + list: Array; + id: string; + removeAlert: (id: string) => void; +}; +export type NoticeAlertType = { + title: string; + link: string; + id: string; + removeAlert: (id: string) => void; +}; +export type SuccessAlertType = { + title: string; + id: string; + removeAlert: (id: string) => void; +}; +export type SingleAlertComponentType = { + dropItem: AlertItemType; + removeAlert: (index: string) => void; +}; export type AlertDropdownType = {}; export type AlertItemType = { - type: "notice" | "error" | "success"; - title: string; - link?: string; - list?: Array; - id: string; - }; \ No newline at end of file + type: "notice" | "error" | "success"; + title: string; + link?: string; + list?: Array; + id: string; +}; From 45c04befe71b3d3dde3279c6df313cab8d410a0f Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Thu, 27 Apr 2023 17:24:25 -0300 Subject: [PATCH 75/99] refactor(Makefile): rename run_backend target to backend and add install_backend target feat(Makefile): add frontend target to install and run frontend app using npm --- Makefile | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8712a0aeb..0eef78999 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,14 @@ install_frontend: run_frontend: cd src/frontend && npm start -run_backend: +frontend: + make install_frontend + make run_frontend + +install_backend: + poetry install + +backend: poetry run uvicorn langflow.main:app --port 7860 --reload --log-level debug build_frontend: From 6e2b4b01dc68705ed3fe26297648d2b717b9fa41 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Thu, 27 Apr 2023 20:52:53 -0300 Subject: [PATCH 76/99] style(dropdownComponent): add dark mode support to dropdown component options and background color --- src/frontend/src/components/dropdownComponent/index.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/frontend/src/components/dropdownComponent/index.tsx b/src/frontend/src/components/dropdownComponent/index.tsx index aad367adb..bb4cd621c 100644 --- a/src/frontend/src/components/dropdownComponent/index.tsx +++ b/src/frontend/src/components/dropdownComponent/index.tsx @@ -41,14 +41,16 @@ export default function Dropdown({ leaveFrom="opacity-100" leaveTo="opacity-0" > - + {options.map((option, id) => ( classNames( - active ? "text-white bg-indigo-600" : "text-gray-900", - "relative cursor-default select-none py-2 pl-3 pr-9" + active + ? "text-white bg-indigo-600 dark:bg-indigo-500" + : "text-gray-900", + "relative cursor-default select-none py-2 pl-3 pr-9 dark:text-gray-300 dark:bg-gray-800" ) } value={option} From e92cf1110a8e29f358a8d5acf2b314e4063b5582 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Thu, 27 Apr 2023 23:23:14 -0300 Subject: [PATCH 77/99] change chat shortcut to ctrl+k and handled websocket errors --- .../src/components/chatComponent/index.tsx | 3 +- src/frontend/src/modals/chatModal/index.tsx | 193 +++++++++++------- 2 files changed, 116 insertions(+), 80 deletions(-) diff --git a/src/frontend/src/components/chatComponent/index.tsx b/src/frontend/src/components/chatComponent/index.tsx index 668a970bd..a4eae7f21 100644 --- a/src/frontend/src/components/chatComponent/index.tsx +++ b/src/frontend/src/components/chatComponent/index.tsx @@ -14,7 +14,8 @@ export default function Chat({ flow }: ChatType) { const [open, setOpen] = useState(false); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { - if (event.key === "K" && event.shiftKey && (event.metaKey||event.ctrlKey)) { + event.preventDefault() + if ((event.key === "K"||event.key==="k") && (event.metaKey||event.ctrlKey)) { setOpen(oldState=>!oldState); } }; diff --git a/src/frontend/src/modals/chatModal/index.tsx b/src/frontend/src/modals/chatModal/index.tsx index 5b3810796..6adb1e51f 100644 --- a/src/frontend/src/modals/chatModal/index.tsx +++ b/src/frontend/src/modals/chatModal/index.tsx @@ -28,7 +28,6 @@ export default function ChatModal({ setOpen: Function; flow: FlowType; }) { - const { updateFlow } = useContext(TabsContext); const [chatValue, setChatValue] = useState(""); const [chatHistory, setChatHistory] = useState([]); const { reactFlowInstance } = useContext(typesContext); @@ -44,7 +43,7 @@ export default function ChatModal({ setChatHistory((old) => { let newChat = _.cloneDeep(old); if (files) { - newChat.push({ message, isSend, files,thought }); + newChat.push({ message, isSend, files, thought }); } else if (thought) { newChat.push({ message, isSend, thought }); } else { @@ -55,95 +54,128 @@ export default function ChatModal({ }; function connectWS() { - const newWs = new WebSocket(`ws://127.0.0.1:5003/chat/${flow.id}`); - newWs.onopen = () => { - console.log("WebSocket connection established!"); - }; - newWs.onmessage = (event) => { - try { - setLockChat(false); - const data = JSON.parse(event.data); - console.log("Received data:", data); - //get chat history - if (Array.isArray(data)) { - console.log(data); + console.log("conectou"); + try { + const newWs = new WebSocket(`ws://127.0.0.1:5003/chat/${flow.id}`); + newWs.onopen = () => { + console.log("WebSocket connection established!"); + }; + newWs.onmessage = (event) => { + try { + setLockChat(false); + const data = JSON.parse(event.data); + console.log("Received data:", data); + //get chat history + if (Array.isArray(data)) { + console.log(data); - setChatHistory((_) => { - let newChatHistory: ChatMessageType[] = []; - data.forEach( - (chatItem: { - intermediate_steps?: "string"; - is_bot: boolean; - message: string; - type: string; - files?: Array; - }) => { - if (chatItem.message) { - newChatHistory.push( - chatItem.files - ? { - isSend: !chatItem.is_bot, - message: chatItem.message, - thought: chatItem.intermediate_steps, - files: chatItem.files, - } - : { - isSend: !chatItem.is_bot, - message: chatItem.message, - thought: chatItem.intermediate_steps, - } - ); + setChatHistory((_) => { + let newChatHistory: ChatMessageType[] = []; + data.forEach( + (chatItem: { + intermediate_steps?: "string"; + is_bot: boolean; + message: string; + type: string; + files?: Array; + }) => { + if (chatItem.message) { + newChatHistory.push( + chatItem.files + ? { + isSend: !chatItem.is_bot, + message: chatItem.message, + thought: chatItem.intermediate_steps, + files: chatItem.files, + } + : { + isSend: !chatItem.is_bot, + message: chatItem.message, + thought: chatItem.intermediate_steps, + } + ); + } } - } - ); - return newChatHistory; - }); - } - if (data.type === "end") { - if (data.files) { - addChatHistory( - data.message, - false, - data.intermediate_steps, - data.files - ); - } else { - addChatHistory(data.message, false, data.intermediate_steps); + ); + return newChatHistory; + }); + } + if (data.type === "end") { + if (data.files) { + addChatHistory( + data.message, + false, + data.intermediate_steps, + data.files + ); + } else { + addChatHistory(data.message, false, data.intermediate_steps); + } + } + if (data.type == "file") { + console.log(data); + } + } catch (error) { + if (event.data !== "Error: 1005") { + setErrorData({ title: event.data }); + newWs.close() + connectWS() } } - if (data.type == "file") { - console.log(data); - } - } catch (error) { - if(event.data!=="Error: 1005"){ - setErrorData({ title: event.data }); - + }; + newWs.onclose = (_) => { + if (open) { + setLockChat(false); + setTimeout(() => { + connectWS(); + }, 1000); } + }; + newWs.onerror= (ev)=>{ + console.log(ev,"error") } - }; - newWs.onclose = (_) => { - if (open) { - setLockChat(false); - setTimeout(() => { - connectWS(); - }, 100); - } - }; - setWs(newWs); + setWs(newWs); - return newWs; + return newWs; + } catch { + setErrorData({ + title: "There was an error on web connection, please: ", + list: [ + "refresh the page", + "use a new flow tab", + "check if the backend is up", + ], + }); + } } useEffect(() => { - let newWs = connectWS(); - return () => { - newWs.close(); - }; + if (ws && (ws.readyState === ws.CLOSED|| ws.readyState===ws.CLOSING)) { + let newWs = connectWS(); + return () => { + console.log("trigger") + newWs.close(); + }; + } + }, [lockChat]); + + useEffect(() => { + let newWs = connectWS(); + return () => { + console.log("trigger") + newWs.close(); + } }, []); async function sendAll(data: sendAllProps) { - if (ws) { - ws.send(JSON.stringify(data)); + try { + if (ws) { + ws.send(JSON.stringify(data)); + } + } catch (error) { + setErrorData({title:"There was an erro sending the message",list:[error.message]}) + setChatValue(data.message) + connectWS(); } } @@ -294,7 +326,10 @@ export default function ChatModal({ ) : (
- 👋 LangFlow Chat + 👋{" "} + + LangFlow Chat +
From d52e7700c0e27a8782b45e63c6831a09d9669eb9 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Fri, 28 Apr 2023 14:42:38 -0300 Subject: [PATCH 78/99] fix(nodes.py): set prompt field as not required and show it if no prompt is provided --- src/backend/langflow/template/nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/langflow/template/nodes.py b/src/backend/langflow/template/nodes.py index f2e8bd94f..f0f2112a6 100644 --- a/src/backend/langflow/template/nodes.py +++ b/src/backend/langflow/template/nodes.py @@ -425,7 +425,7 @@ class ChainFrontendNode(FrontendNode): field.required = True field.show = True # Separated for possible future changes - if field.name == "prompt": + if field.name == "prompt" and field.value is None: # if no prompt is provided, use the default prompt field.required = False field.show = True From e4b4cf19a26d40d57be7311a148bc353b6bf894d Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Fri, 28 Apr 2023 14:43:27 -0300 Subject: [PATCH 79/99] refactor(GenericNode): remove commented out code and add comments to code feat(GenericNode): add validation check for node changes by comparing length of nodes and edges array --- src/frontend/src/CustomNodes/GenericNode/index.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index 93497c0fc..7ad4bd59f 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -32,7 +32,7 @@ export default function GenericNode({ const debouncedValidateNode = useDebouncedCallback(async () => { // Check if the validationStatus is "success" - if (validationStatus === "success") return; + // if (validationStatus === "success") return; try { const response = await fetch(`/validate/node/${data.id}`, { @@ -62,7 +62,11 @@ export default function GenericNode({ validateNode(); }, [ validateNode, - ...Object.values(data.node.template).flatMap((t) => Object.values(t)), + // Use the result of ...reactFlowInstance.toObject() + // to determine if the node has changed + // turn into an array of nodes and edges + // and compare the length of the array + ...Object.values(reactFlowInstance.toObject()), ]); useEffect(() => { From 2bc1fdd32efe2d9dbbfa5114555186d7d79905e6 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Fri, 28 Apr 2023 14:46:05 -0300 Subject: [PATCH 80/99] prettier --- .../src/components/chatComponent/index.tsx | 47 ++- src/frontend/src/controllers/API/index.ts | 61 +-- src/frontend/src/pages/FlowPage/index.tsx | 375 +++++++++--------- src/frontend/src/reportWebVitals.ts | 4 +- src/frontend/src/svg.d.ts | 9 +- src/frontend/src/types/api/index.ts | 54 ++- src/frontend/src/types/components/index.ts | 118 +++--- src/frontend/src/types/entities/index.ts | 7 +- src/frontend/src/types/flow/index.ts | 26 +- src/frontend/src/types/tabs/index.ts | 24 +- src/frontend/src/types/typesContext/index.ts | 14 +- src/frontend/src/utils.ts | 114 +++--- 12 files changed, 454 insertions(+), 399 deletions(-) diff --git a/src/frontend/src/components/chatComponent/index.tsx b/src/frontend/src/components/chatComponent/index.tsx index a4eae7f21..913822bd3 100644 --- a/src/frontend/src/components/chatComponent/index.tsx +++ b/src/frontend/src/components/chatComponent/index.tsx @@ -1,8 +1,4 @@ -import { - useEffect, - useRef, - useState, -} from "react"; +import { useEffect, useRef, useState } from "react"; import { ChatMessageType, ChatType } from "../../types/chat"; import ChatTrigger from "./chatTrigger"; @@ -11,23 +7,26 @@ import ChatModal from "../../modals/chatModal"; const _ = require("lodash"); export default function Chat({ flow }: ChatType) { - const [open, setOpen] = useState(false); - useEffect(() => { - const handleKeyDown = (event: KeyboardEvent) => { - event.preventDefault() - if ((event.key === "K"||event.key==="k") && (event.metaKey||event.ctrlKey)) { - setOpen(oldState=>!oldState); - } - }; - document.addEventListener("keydown", handleKeyDown); - return () => { - document.removeEventListener("keydown", handleKeyDown); - }; - }, []); - return ( - <> - - - - ); + const [open, setOpen] = useState(false); + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + // event.preventDefault() + if ( + (event.key === "K" || event.key === "k") && + (event.metaKey || event.ctrlKey) + ) { + setOpen((oldState) => !oldState); + } + }; + document.addEventListener("keydown", handleKeyDown); + return () => { + document.removeEventListener("keydown", handleKeyDown); + }; + }, []); + return ( + <> + + + + ); } diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index 490ec2837..f6f46404b 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -1,40 +1,43 @@ -import { PromptTypeAPI, errorsTypeAPI } from './../../types/api/index'; -import { APIObjectType, sendAllProps } from '../../types/api/index'; +import { PromptTypeAPI, errorsTypeAPI } from "./../../types/api/index"; +import { APIObjectType, sendAllProps } from "../../types/api/index"; import axios, { AxiosResponse } from "axios"; -import { FlowType } from '../../types/flow'; +import { FlowType } from "../../types/flow"; -export async function getAll():Promise> { - return await axios.get(`/all`); +export async function getAll(): Promise> { + return await axios.get(`/all`); } -export async function sendAll(data:sendAllProps) { - return await axios.post(`/predict`, data); +export async function sendAll(data: sendAllProps) { + return await axios.post(`/predict`, data); } -export async function checkCode(code:string):Promise>{ - - return await axios.post('/validate/code',{code}) +export async function checkCode( + code: string +): Promise> { + return await axios.post("/validate/code", { code }); } -export async function checkPrompt(template:string):Promise>{ - - return await axios.post('/validate/prompt',{template}) +export async function checkPrompt( + template: string +): Promise> { + return await axios.post("/validate/prompt", { template }); } export async function getExamples(): Promise { - const url = 'https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples'; - const response = await axios.get(url); - - const jsonFiles = response.data.filter((file: any) => { - return file.name.endsWith('.json'); - }); - - const contentsPromises = jsonFiles.map(async (file: any) => { - const contentResponse = await axios.get(file.download_url); - return contentResponse.data; - }); - - const contents = await Promise.all(contentsPromises); - - return contents; - } \ No newline at end of file + const url = + "https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples"; + const response = await axios.get(url); + + const jsonFiles = response.data.filter((file: any) => { + return file.name.endsWith(".json"); + }); + + const contentsPromises = jsonFiles.map(async (file: any) => { + const contentResponse = await axios.get(file.download_url); + return contentResponse.data; + }); + + const contents = await Promise.all(contentsPromises); + + return contents; +} diff --git a/src/frontend/src/pages/FlowPage/index.tsx b/src/frontend/src/pages/FlowPage/index.tsx index 4fc315926..38fe8ecb8 100644 --- a/src/frontend/src/pages/FlowPage/index.tsx +++ b/src/frontend/src/pages/FlowPage/index.tsx @@ -1,15 +1,15 @@ import { useCallback, useContext, useEffect, useRef } from "react"; import ReactFlow, { - Background, - Controls, - addEdge, - useEdgesState, - useNodesState, - useReactFlow, - updateEdge, - EdgeChange, - Connection, - Edge, + Background, + Controls, + addEdge, + useEdgesState, + useNodesState, + useReactFlow, + updateEdge, + EdgeChange, + Connection, + Edge, } from "reactflow"; import { locationContext } from "../../contexts/locationContext"; import ExtraSidebar from "./components/extraSidebarComponent"; @@ -24,193 +24,198 @@ import { APIClassType } from "../../types/api"; import { isValidConnection } from "../../utils"; const nodeTypes = { - genericNode: GenericNode, + genericNode: GenericNode, }; var _ = require("lodash"); -export default function FlowPage({ flow }:{flow:FlowType}) { - let { updateFlow, incrementNodeId} = - useContext(TabsContext); - const { types, reactFlowInstance, setReactFlowInstance } = - useContext(typesContext); - const reactFlowWrapper = useRef(null); +export default function FlowPage({ flow }: { flow: FlowType }) { + let { updateFlow, incrementNodeId } = useContext(TabsContext); + const { types, reactFlowInstance, setReactFlowInstance } = + useContext(typesContext); + const reactFlowWrapper = useRef(null); - const { setExtraComponent, setExtraNavigation } = useContext(locationContext); - const { setErrorData } = useContext(alertContext); - const [nodes, setNodes, onNodesChange] = useNodesState( - flow.data?.nodes ?? [] - ); - const [edges, setEdges, onEdgesChange] = useEdgesState( - flow.data?.edges ?? [] - ); - const { setViewport } = useReactFlow(); - const edgeUpdateSuccessful = useRef(true) + const { setExtraComponent, setExtraNavigation } = useContext(locationContext); + const { setErrorData } = useContext(alertContext); + const [nodes, setNodes, onNodesChange] = useNodesState( + flow.data?.nodes ?? [] + ); + const [edges, setEdges, onEdgesChange] = useEdgesState( + flow.data?.edges ?? [] + ); + const { setViewport } = useReactFlow(); + const edgeUpdateSuccessful = useRef(true); - useEffect(() => { - if (reactFlowInstance && flow) { - flow.data = reactFlowInstance.toObject(); - updateFlow(flow); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [nodes, edges]); - //update flow when tabs change - useEffect(() => { - setNodes(flow?.data?.nodes ?? []); - setEdges(flow?.data?.edges ?? []); - if (reactFlowInstance) { - setViewport(flow?.data?.viewport ?? { x: 1, y: 0, zoom: 0.5 }); - } - }, [flow, reactFlowInstance, setEdges, setNodes, setViewport]); - //set extra sidebar - useEffect(() => { - setExtraComponent(); - setExtraNavigation({ title: "Components" }); - }, [setExtraComponent, setExtraNavigation]); + useEffect(() => { + if (reactFlowInstance && flow) { + flow.data = reactFlowInstance.toObject(); + updateFlow(flow); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [nodes, edges]); + //update flow when tabs change + useEffect(() => { + setNodes(flow?.data?.nodes ?? []); + setEdges(flow?.data?.edges ?? []); + if (reactFlowInstance) { + setViewport(flow?.data?.viewport ?? { x: 1, y: 0, zoom: 0.5 }); + } + }, [flow, reactFlowInstance, setEdges, setNodes, setViewport]); + //set extra sidebar + useEffect(() => { + setExtraComponent(); + setExtraNavigation({ title: "Components" }); + }, [setExtraComponent, setExtraNavigation]); - const onEdgesChangeMod = useCallback( - (s:EdgeChange[]) => { - onEdgesChange(s); - setNodes((x) => { - let newX = _.cloneDeep(x); - return newX; - }); - }, - [onEdgesChange, setNodes] - ); + const onEdgesChangeMod = useCallback( + (s: EdgeChange[]) => { + onEdgesChange(s); + setNodes((x) => { + let newX = _.cloneDeep(x); + return newX; + }); + }, + [onEdgesChange, setNodes] + ); - const onConnect = useCallback( - (params:Connection) => { - setEdges((eds) => - addEdge({ ...params, className: "animate-pulse" }, eds) - ); - setNodes((x) => { - let newX = _.cloneDeep(x); - return newX; - }); - }, - [setEdges, setNodes] - ); + const onConnect = useCallback( + (params: Connection) => { + setEdges((eds) => + addEdge({ ...params, className: "animate-pulse" }, eds) + ); + setNodes((x) => { + let newX = _.cloneDeep(x); + return newX; + }); + }, + [setEdges, setNodes] + ); - const onDragOver = useCallback((event:React.DragEvent) => { - event.preventDefault(); - event.dataTransfer.dropEffect = "move"; - }, []); + const onDragOver = useCallback((event: React.DragEvent) => { + event.preventDefault(); + event.dataTransfer.dropEffect = "move"; + }, []); - const onDrop = useCallback( - (event:React.DragEvent) => { - event.preventDefault(); - - // Helper function to generate a unique node ID - function getId() { - return `dndnode_` + incrementNodeId(); - } - - // Get the current bounds of the ReactFlow wrapper element - const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect(); - - // Extract the data from the drag event and parse it as a JSON object - let data:{type:string,node?:APIClassType} = JSON.parse(event.dataTransfer.getData("json")); - - // If data type is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node - if ( - data.type !== "chatInput" || - (data.type === "chatInput" && - !reactFlowInstance.getNodes().some((n) => n.type === "chatInputNode")) - ) { - // Calculate the position where the node should be created - const position = reactFlowInstance.project({ - x: event.clientX - reactflowBounds.left, - y: event.clientY - reactflowBounds.top, - }); - - // Generate a unique node ID - let newId = getId(); - - // Create a new node object - const newNode:NodeType = { - id: newId, - type: "genericNode", - position, - data: { - ...data, - id: newId, - value: null, - }, - }; - - // Add the new node to the list of nodes in state - setNodes((nds) => nds.concat(newNode)); - } else { - // If a chat input node already exists, set an error message - setErrorData({ - title: "Error creating node", - list: ["There can't be more than one chat input."], - }); - } - }, - // Specify dependencies for useCallback - [incrementNodeId, reactFlowInstance, setErrorData, setNodes] - ); + const onDrop = useCallback( + (event: React.DragEvent) => { + event.preventDefault(); - - const onDelete = (mynodes) => { - setEdges(edges.filter((ns) => !nodes.some((n) => ns.source === n.id || ns.target === n.id))); - } + // Helper function to generate a unique node ID + function getId() { + return `dndnode_` + incrementNodeId(); + } - const onEdgeUpdateStart = useCallback(() => { - edgeUpdateSuccessful.current = false; - }, []); + // Get the current bounds of the ReactFlow wrapper element + const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect(); - - const onEdgeUpdate = useCallback((oldEdge:Edge, newConnection:Connection) => { - if(isValidConnection(newConnection,reactFlowInstance)){ - edgeUpdateSuccessful.current = true; - setEdges((els) => updateEdge(oldEdge, newConnection, els)); - } - }, []); + // Extract the data from the drag event and parse it as a JSON object + let data: { type: string; node?: APIClassType } = JSON.parse( + event.dataTransfer.getData("json") + ); - const onEdgeUpdateEnd = useCallback((_, edge) => { - if (!edgeUpdateSuccessful.current) { - setEdges((eds) => eds.filter((e) => e.id !== edge.id)); - } - - edgeUpdateSuccessful.current = true; - }, []); - - return ( -
- {Object.keys(types).length > 0 ? ( - <> - - updateFlow({ ...flow, data: reactFlowInstance.toObject() }) - } - edges={edges} - onNodesChange={onNodesChange} - onEdgesChange={onEdgesChangeMod} - onConnect={onConnect} - onLoad={setReactFlowInstance} - onInit={setReactFlowInstance} - nodeTypes={nodeTypes} - onEdgeUpdate={onEdgeUpdate} - onEdgeUpdateStart={onEdgeUpdateStart} - onEdgeUpdateEnd={onEdgeUpdateEnd} - connectionLineComponent={ConnectionLineComponent} - onDragOver={onDragOver} - onDrop={onDrop} - onNodesDelete={onDelete} - > - - - - - - - ) : ( - <> - )} -
- ); + // If data type is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node + if ( + data.type !== "chatInput" || + (data.type === "chatInput" && + !reactFlowInstance.getNodes().some((n) => n.type === "chatInputNode")) + ) { + // Calculate the position where the node should be created + const position = reactFlowInstance.project({ + x: event.clientX - reactflowBounds.left, + y: event.clientY - reactflowBounds.top, + }); + + // Generate a unique node ID + let newId = getId(); + + // Create a new node object + const newNode: NodeType = { + id: newId, + type: "genericNode", + position, + data: { + ...data, + id: newId, + value: null, + }, + }; + + // Add the new node to the list of nodes in state + setNodes((nds) => nds.concat(newNode)); + } else { + // If a chat input node already exists, set an error message + setErrorData({ + title: "Error creating node", + list: ["There can't be more than one chat input."], + }); + } + }, + // Specify dependencies for useCallback + [incrementNodeId, reactFlowInstance, setErrorData, setNodes] + ); + + const onDelete = (mynodes) => { + setEdges( + edges.filter( + (ns) => !nodes.some((n) => ns.source === n.id || ns.target === n.id) + ) + ); + }; + + const onEdgeUpdateStart = useCallback(() => { + edgeUpdateSuccessful.current = false; + }, []); + + const onEdgeUpdate = useCallback( + (oldEdge: Edge, newConnection: Connection) => { + if (isValidConnection(newConnection, reactFlowInstance)) { + edgeUpdateSuccessful.current = true; + setEdges((els) => updateEdge(oldEdge, newConnection, els)); + } + }, + [] + ); + + const onEdgeUpdateEnd = useCallback((_, edge) => { + if (!edgeUpdateSuccessful.current) { + setEdges((eds) => eds.filter((e) => e.id !== edge.id)); + } + + edgeUpdateSuccessful.current = true; + }, []); + + return ( +
+ {Object.keys(types).length > 0 ? ( + <> + + updateFlow({ ...flow, data: reactFlowInstance.toObject() }) + } + edges={edges} + onNodesChange={onNodesChange} + onEdgesChange={onEdgesChangeMod} + onConnect={onConnect} + onLoad={setReactFlowInstance} + onInit={setReactFlowInstance} + nodeTypes={nodeTypes} + onEdgeUpdate={onEdgeUpdate} + onEdgeUpdateStart={onEdgeUpdateStart} + onEdgeUpdateEnd={onEdgeUpdateEnd} + connectionLineComponent={ConnectionLineComponent} + onDragOver={onDragOver} + onDrop={onDrop} + onNodesDelete={onDelete} + > + + + + + + ) : ( + <> + )} +
+ ); } diff --git a/src/frontend/src/reportWebVitals.ts b/src/frontend/src/reportWebVitals.ts index 49a2a16e0..5fa3583b7 100644 --- a/src/frontend/src/reportWebVitals.ts +++ b/src/frontend/src/reportWebVitals.ts @@ -1,8 +1,8 @@ -import { ReportHandler } from 'web-vitals'; +import { ReportHandler } from "web-vitals"; const reportWebVitals = (onPerfEntry?: ReportHandler) => { if (onPerfEntry && onPerfEntry instanceof Function) { - import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { getCLS(onPerfEntry); getFID(onPerfEntry); getFCP(onPerfEntry); diff --git a/src/frontend/src/svg.d.ts b/src/frontend/src/svg.d.ts index a6e109e02..1a3dd3c2a 100644 --- a/src/frontend/src/svg.d.ts +++ b/src/frontend/src/svg.d.ts @@ -1,5 +1,4 @@ -declare module '*.svg' { - const content: any; - export default content; - } - \ No newline at end of file +declare module "*.svg" { + const content: any; + export default content; +} diff --git a/src/frontend/src/types/api/index.ts b/src/frontend/src/types/api/index.ts index 4a2afcd18..8d26c4e15 100644 --- a/src/frontend/src/types/api/index.ts +++ b/src/frontend/src/types/api/index.ts @@ -1,20 +1,40 @@ -import { Node,Edge,Viewport } from "reactflow" +import { Node, Edge, Viewport } from "reactflow"; //kind and class are just representative names to represent the actual structure of the object received by the API -export type APIObjectType = {kind:APIKindType,[key:string]:APIKindType} -export type APIKindType= {class:APIClassType,[key:string]:APIClassType} -export type APITemplateType = {variable:TemplateVariableType,[key:string]:TemplateVariableType} -export type APIClassType ={base_classes:Array,description:string,template:APITemplateType,[key:string]:Array|string|APITemplateType} -export type TemplateVariableType = {type:string,required:boolean,placeholder?:string,list:boolean,show:boolean,multiline?:boolean,value?:any,[key:string]:any} -export type sendAllProps={ - nodes: Node[]; - edges: Edge[]; - name:string, - description:string; - viewport: Viewport; - message:string; - - chatHistory:{message:string,isSend:boolean}[], +export type APIObjectType = { kind: APIKindType; [key: string]: APIKindType }; +export type APIKindType = { class: APIClassType; [key: string]: APIClassType }; +export type APITemplateType = { + variable: TemplateVariableType; + [key: string]: TemplateVariableType; }; -export type errorsTypeAPI={function:{errors:Array},imports:{errors:Array}} -export type PromptTypeAPI = {input_variables:Array} \ No newline at end of file +export type APIClassType = { + base_classes: Array; + description: string; + template: APITemplateType; + [key: string]: Array | string | APITemplateType; +}; +export type TemplateVariableType = { + type: string; + required: boolean; + placeholder?: string; + list: boolean; + show: boolean; + multiline?: boolean; + value?: any; + [key: string]: any; +}; +export type sendAllProps = { + nodes: Node[]; + edges: Edge[]; + name: string; + description: string; + viewport: Viewport; + message: string; + + chatHistory: { message: string; isSend: boolean }[]; +}; +export type errorsTypeAPI = { + function: { errors: Array }; + imports: { errors: Array }; +}; +export type PromptTypeAPI = { input_variables: Array }; diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index d7448083e..e8c682724 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -1,81 +1,85 @@ import { ForwardRefExoticComponent, ReactElement, ReactNode } from "react"; import { NodeDataType } from "../flow/index"; export type InputComponentType = { - value: string; - disabled?: boolean; - onChange: (value: string) => void; - password: boolean; + value: string; + disabled?: boolean; + onChange: (value: string) => void; + password: boolean; }; export type ToggleComponentType = { - enabled: boolean; - setEnabled: (state: boolean) => void; - disabled: boolean; + enabled: boolean; + setEnabled: (state: boolean) => void; + disabled: boolean; }; export type DropDownComponentType = { - value: string; - options: string[]; - onSelect: (value: string) => void; + value: string; + options: string[]; + onSelect: (value: string) => void; }; export type ParameterComponentType = { - data: NodeDataType; - title: string; - id: string; - color: string; - left: boolean; - type: string; - required?: boolean; - name?: string; - tooltipTitle: string; + data: NodeDataType; + title: string; + id: string; + color: string; + left: boolean; + type: string; + required?: boolean; + name?: string; + tooltipTitle: string; }; export type InputListComponentType = { - value: string[]; - onChange: (value: string[]) => void; - disabled: boolean; + value: string[]; + onChange: (value: string[]) => void; + disabled: boolean; }; export type TextAreaComponentType = { - disabled: boolean; - onChange: (value: string[] | string) => void; - value: string; + disabled: boolean; + onChange: (value: string[] | string) => void; + value: string; }; export type FileComponentType = { - disabled: boolean; - onChange: (value: string[] | string) => void; - value: string; - suffixes:Array; - fileTypes:Array; - onFileChange:(value: string) => void; + disabled: boolean; + onChange: (value: string[] | string) => void; + value: string; + suffixes: Array; + fileTypes: Array; + onFileChange: (value: string) => void; }; export type DisclosureComponentType = { - children: ReactNode; - button: { - title: string; - Icon: ForwardRefExoticComponent>; - buttons?: { - Icon: ReactElement; - title: string; - onClick: (event?: React.MouseEvent) => void; - }[]; - }; + children: ReactNode; + button: { + title: string; + Icon: ForwardRefExoticComponent>; + buttons?: { + Icon: ReactElement; + title: string; + onClick: (event?: React.MouseEvent) => void; + }[]; + }; }; export type FloatComponentType = { - value: string; - disabled?: boolean; - onChange: (value: string) => void; + value: string; + disabled?: boolean; + onChange: (value: string) => void; }; -export type TooltipComponentType={children:ReactElement,title:string,placement?: - | 'bottom-end' - | 'bottom-start' - | 'bottom' - | 'left-end' - | 'left-start' - | 'left' - | 'right-end' - | 'right-start' - | 'right' - | 'top-end' - | 'top-start' - | 'top';} \ No newline at end of file +export type TooltipComponentType = { + children: ReactElement; + title: string; + placement?: + | "bottom-end" + | "bottom-start" + | "bottom" + | "left-end" + | "left-start" + | "left" + | "right-end" + | "right-start" + | "right" + | "top-end" + | "top-start" + | "top"; +}; diff --git a/src/frontend/src/types/entities/index.ts b/src/frontend/src/types/entities/index.ts index c0d591cfc..3c0877579 100644 --- a/src/frontend/src/types/entities/index.ts +++ b/src/frontend/src/types/entities/index.ts @@ -1,3 +1,8 @@ import { HomeIcon } from "@heroicons/react/24/outline"; -export type sidebarNavigationItemType = { name: string, href: string, icon: React.ForwardRefExoticComponent>, current: boolean } +export type sidebarNavigationItemType = { + name: string; + href: string; + icon: React.ForwardRefExoticComponent>; + current: boolean; +}; diff --git a/src/frontend/src/types/flow/index.ts b/src/frontend/src/types/flow/index.ts index 26354c55c..6c118aa49 100644 --- a/src/frontend/src/types/flow/index.ts +++ b/src/frontend/src/types/flow/index.ts @@ -1,12 +1,22 @@ -import { ChatMessageType } from './../chat/index'; -import { APIClassType } from '../api/index'; +import { ChatMessageType } from "./../chat/index"; +import { APIClassType } from "../api/index"; import { ReactFlowJsonObject, XYPosition } from "reactflow"; export type FlowType = { - name: string; - id: string; - data: ReactFlowJsonObject; - description:string; + name: string; + id: string; + data: ReactFlowJsonObject; + description: string; +}; +export type NodeType = { + id: string; + type?: string; + position: XYPosition; + data: NodeDataType; +}; +export type NodeDataType = { + type: string; + node?: APIClassType; + id: string; + value: any; }; -export type NodeType = {id:string,type:string,position:XYPosition,data:NodeDataType} -export type NodeDataType = {type:string,node?:APIClassType,id:string,value:any} \ No newline at end of file diff --git a/src/frontend/src/types/tabs/index.ts b/src/frontend/src/types/tabs/index.ts index 1b675e0c5..3fffbcd8a 100644 --- a/src/frontend/src/types/tabs/index.ts +++ b/src/frontend/src/types/tabs/index.ts @@ -1,15 +1,15 @@ import { FlowType } from "../flow"; export type TabsContextType = { - save:()=>void; - tabIndex: number; - setTabIndex: (index: number) => void; - flows: Array; - removeFlow: (id: string) => void; - addFlow: (flowData?: FlowType) => void; - updateFlow: (newFlow: FlowType) => void; - incrementNodeId: () => number; - downloadFlow: (flow:FlowType) => void; - uploadFlow: () => void; - hardReset:()=>void; -}; \ No newline at end of file + save: () => void; + tabIndex: number; + setTabIndex: (index: number) => void; + flows: Array; + removeFlow: (id: string) => void; + addFlow: (flowData?: FlowType) => void; + updateFlow: (newFlow: FlowType) => void; + incrementNodeId: () => number; + downloadFlow: (flow: FlowType) => void; + uploadFlow: () => void; + hardReset: () => void; +}; diff --git a/src/frontend/src/types/typesContext/index.ts b/src/frontend/src/types/typesContext/index.ts index e943dafe3..f3a8612b5 100644 --- a/src/frontend/src/types/typesContext/index.ts +++ b/src/frontend/src/types/typesContext/index.ts @@ -1,11 +1,11 @@ import { ReactFlowInstance } from "reactflow"; -const types:{[char: string]: string}={} +const types: { [char: string]: string } = {}; export type typesContextType = { - reactFlowInstance: ReactFlowInstance|null; - setReactFlowInstance: any; - deleteNode: (idx: string) => void; - types: typeof types; - setTypes: (newState: {}) => void; -}; \ No newline at end of file + reactFlowInstance: ReactFlowInstance | null; + setReactFlowInstance: any; + deleteNode: (idx: string) => void; + types: typeof types; + setTypes: (newState: {}) => void; +}; diff --git a/src/frontend/src/utils.ts b/src/frontend/src/utils.ts index 608035ff8..c7457a70b 100644 --- a/src/frontend/src/utils.ts +++ b/src/frontend/src/utils.ts @@ -14,13 +14,13 @@ import { FingerPrintIcon, ScissorsIcon, CircleStackIcon, - Squares2X2Icon + Squares2X2Icon, } from "@heroicons/react/24/outline"; import { Connection, Edge, Node, ReactFlowInstance } from "reactflow"; import { FlowType } from "./types/flow"; -var _ = require('lodash') +var _ = require("lodash"); -export function classNames(...classes:Array) { +export function classNames(...classes: Array) { return classes.filter(Boolean).join(" "); } @@ -70,7 +70,7 @@ export const borderLColors = { gray: "border-l-gray-500", }; -export const nodeColors: {[char: string]: string} = { +export const nodeColors: { [char: string]: string } = { prompts: "#4367BF", llms: "#6344BE", chains: "#FE7500", @@ -79,18 +79,18 @@ export const nodeColors: {[char: string]: string} = { memories: "#F5B85A", advanced: "#000000", chat: "#198BF6", - thought:"#272541", - embeddings:"#42BAA7", - documentloaders:"#7AAE42", + thought: "#272541", + embeddings: "#42BAA7", + documentloaders: "#7AAE42", vectorstores: "#AA8742", textsplitters: "#B47CB5", - toolkits:"#DB2C2C", - wrappers:"#E6277A", - utilities:"#31A3CC", - unknown:"#9CA3AF" + toolkits: "#DB2C2C", + wrappers: "#E6277A", + utilities: "#31A3CC", + unknown: "#9CA3AF", }; -export const nodeNames:{[char: string]: string} = { +export const nodeNames: { [char: string]: string } = { prompts: "Prompts", llms: "LLMs", chains: "Chains", @@ -102,14 +102,18 @@ export const nodeNames:{[char: string]: string} = { embeddings: "Embeddings", documentloaders: "Document Loaders", vectorstores: "Vector Stores", - toolkits:"Toolkits", - wrappers:"Wrappers", + toolkits: "Toolkits", + wrappers: "Wrappers", textsplitters: "Text Splitters", - utilities:"Utilities", - unknown:"Unknown" + utilities: "Utilities", + unknown: "Unknown", }; -export const nodeIcons:{[char: string]: React.ForwardRefExoticComponent>} = { +export const nodeIcons: { + [char: string]: React.ForwardRefExoticComponent< + React.SVGProps + >; +} = { agents: RocketLaunchIcon, chains: LinkIcon, memories: CpuChipIcon, @@ -118,14 +122,14 @@ export const nodeIcons:{[char: string]: React.ForwardRefExoticComponent { - if (index === 0) { - return word[0].toUpperCase() + word.slice(1).toLowerCase(); - } - return word.toLowerCase(); - }) - .join("_"); + .split(" ") + .map((word, index) => { + if (index === 0) { + return word[0].toUpperCase() + word.slice(1).toLowerCase(); + } + return word.toLowerCase(); + }) + .join("_"); } -export function roundNumber(x:number, decimals:number) { +export function roundNumber(x: number, decimals: number) { return Math.round(x * Math.pow(10, decimals)) / Math.pow(10, decimals); } @@ -345,12 +353,15 @@ export function getConnectedNodes(edge: Edge, nodes: Array): Array { } export function isValidConnection( - { source, target, sourceHandle, targetHandle }:Connection, - reactFlowInstance:ReactFlowInstance + { source, target, sourceHandle, targetHandle }: Connection, + reactFlowInstance: ReactFlowInstance ) { if ( - sourceHandle.split('|')[0] === targetHandle.split("|")[0] || - sourceHandle.split('|').slice(2).some((t) => t === targetHandle.split("|")[0]) || + sourceHandle.split("|")[0] === targetHandle.split("|")[0] || + sourceHandle + .split("|") + .slice(2) + .some((t) => t === targetHandle.split("|")[0]) || targetHandle.split("|")[0] === "str" ) { let targetNode = reactFlowInstance.getNode(target).data.node; @@ -375,16 +386,15 @@ export function isValidConnection( return false; } -export function removeApiKeys(flow:FlowType):FlowType{ - let cleanFLow = _.cloneDeep(flow) - cleanFLow.data.nodes.forEach(node=>{ - for(const key in node.data.node.template) - { - if(key.includes('api')){ - console.log(node.data.node.template[key]) - node.data.node.template[key].value = '' +export function removeApiKeys(flow: FlowType): FlowType { + let cleanFLow = _.cloneDeep(flow); + cleanFLow.data.nodes.forEach((node) => { + for (const key in node.data.node.template) { + if (key.includes("api")) { + console.log(node.data.node.template[key]); + node.data.node.template[key].value = ""; } } - }) - return cleanFLow -} \ No newline at end of file + }); + return cleanFLow; +} From 07863ea09847b9191568f5b9cb652eb0606ccf4a Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Fri, 28 Apr 2023 15:44:18 -0300 Subject: [PATCH 81/99] test(websocket.py): change websocket endpoint from /ws/test_client to /chat/test_client test(websocket.py): update assertions to match new endpoint and response format --- tests/test_websocket.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/test_websocket.py b/tests/test_websocket.py index 9ec128bcf..1137c4813 100644 --- a/tests/test_websocket.py +++ b/tests/test_websocket.py @@ -5,9 +5,9 @@ from fastapi.testclient import TestClient def test_websocket_connection(client: TestClient): - with client.websocket_connect("/ws/test_client") as websocket: + with client.websocket_connect("/chat/test_client") as websocket: assert websocket.scope["client"] == ["testclient", 50000] - assert websocket.scope["path"] == "/ws/test_client" + assert websocket.scope["path"] == "/chat/test_client" def test_chat_history(client: TestClient): @@ -17,21 +17,22 @@ def test_chat_history(client: TestClient): with patch("langflow.api.chat_manager.process_graph") as mock_process_graph: mock_process_graph.return_value = ("Hello, I'm a mock response!", "") - with client.websocket_connect("/ws/test_client") as websocket: + with client.websocket_connect("/chat/test_client") as websocket: # First message should be the history history = websocket.receive_json() - assert json.loads(history) == [] # Empty history + assert history == [] # Empty history # Send a message payload = {"message": "Hello"} websocket.send_json(json.dumps(payload)) # Receive the response from the server response = websocket.receive_json() - assert json.loads(response) == { + assert response == { "is_bot": True, "message": None, - "intermediate_steps": "", "type": "start", + "intermediate_steps": "", + "files": [], } # Send another message payload = {"message": "How are you?"} @@ -39,9 +40,10 @@ def test_chat_history(client: TestClient): # Receive the response from the server response = websocket.receive_json() - assert json.loads(response) == { + assert response == { "is_bot": True, "message": "Hello, I'm a mock response!", - "intermediate_steps": "", "type": "end", + "intermediate_steps": "", + "files": [], } From 357d13b1a90a594796709302231288474a7e17e8 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Fri, 28 Apr 2023 15:45:53 -0300 Subject: [PATCH 82/99] refactor(validate.py): change wrapped_function from async to sync fix(test_chains_template.py): set prompt show value to False for mid_journey_prompt_chain and time_travel_guide_chain tests --- src/backend/langflow/utils/validate.py | 2 +- tests/test_chains_template.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backend/langflow/utils/validate.py b/src/backend/langflow/utils/validate.py index 59d22a143..d1353bd77 100644 --- a/src/backend/langflow/utils/validate.py +++ b/src/backend/langflow/utils/validate.py @@ -155,7 +155,7 @@ def create_function(code, function_name): exec_globals[function_name] = locals()[function_name] # Return a function that imports necessary modules and calls the target function - async def wrapped_function(*args, **kwargs): + def wrapped_function(*args, **kwargs): for module_name, module in exec_globals.items(): if isinstance(module, type(importlib)): globals()[module_name] = module diff --git a/tests/test_chains_template.py b/tests/test_chains_template.py index 510437797..ad57a9a39 100644 --- a/tests/test_chains_template.py +++ b/tests/test_chains_template.py @@ -462,7 +462,7 @@ def test_mid_journey_prompt_chain(client: TestClient): assert template["prompt"] == { "required": False, "placeholder": "", - "show": True, + "show": False, "multiline": False, "value": { "input_variables": ["history", "input"], @@ -590,7 +590,7 @@ def test_time_travel_guide_chain(client: TestClient): assert template["prompt"] == { "required": False, "placeholder": "", - "show": True, + "show": False, "multiline": False, "value": { "input_variables": ["history", "input"], From 8e2342cf2173cb6c9a5f01beac2dca37c46bf9fb Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Fri, 28 Apr 2023 17:22:51 -0300 Subject: [PATCH 83/99] feat(loading.py): add validation for pydantic BaseModel subclasses This commit adds validation for pydantic BaseModel subclasses in the instantiate_class function. The function now checks if the class_object is a subclass of BaseModel and if so, it validates the params passed to it against the fields of the class_object. --- src/backend/langflow/interface/loading.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/backend/langflow/interface/loading.py b/src/backend/langflow/interface/loading.py index e3011d82c..250908ea7 100644 --- a/src/backend/langflow/interface/loading.py +++ b/src/backend/langflow/interface/loading.py @@ -25,6 +25,7 @@ from langflow.interface.toolkits.base import toolkits_creator from langflow.interface.types import get_type_list from langflow.interface.utils import load_file_into_dict from langflow.utils import util, validate +from pydantic import BaseModel def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any: @@ -35,6 +36,11 @@ def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any: class_object = import_by_type(_type=base_type, name=node_type) + if issubclass(class_object, BaseModel): + # validate params + fields = class_object.__fields__ + params = {key: value for key, value in params.items() if key in fields} + if base_type == "agents": # We need to initialize it differently return load_agent_executor(class_object, params) @@ -66,6 +72,7 @@ def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any: return load_toolkits_executor(node_type, loaded_toolkit, params) return loaded_toolkit elif base_type == "embeddings": + # ? Why remove model from params? params.pop("model") return class_object(**params) elif base_type == "vectorstores": From cde48131e3349a5aed5d4276e76a769bd6aebf90 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Fri, 28 Apr 2023 17:59:23 -0300 Subject: [PATCH 84/99] fix bug on event prevent default --- src/frontend/src/components/chatComponent/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/components/chatComponent/index.tsx b/src/frontend/src/components/chatComponent/index.tsx index a4eae7f21..10d285ba9 100644 --- a/src/frontend/src/components/chatComponent/index.tsx +++ b/src/frontend/src/components/chatComponent/index.tsx @@ -14,8 +14,8 @@ export default function Chat({ flow }: ChatType) { const [open, setOpen] = useState(false); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { - event.preventDefault() if ((event.key === "K"||event.key==="k") && (event.metaKey||event.ctrlKey)) { + event.preventDefault() setOpen(oldState=>!oldState); } }; From 8943de257569255cf2ea5222319d7355219943f8 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Fri, 28 Apr 2023 19:59:52 -0300 Subject: [PATCH 85/99] check advanced on node too --- src/frontend/src/CustomNodes/GenericNode/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index f362ca8ff..41d9e0ed8 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -76,7 +76,7 @@ export default function GenericNode({ "px-5 py-2 mt-2 dark:text-white text-center", Object.keys(data.node.template).filter( (key) => - !key.startsWith("_") && data.node.template[key].show + !key.startsWith("_") && data.node.template[key].show && !data.node.template[key].advanced ).length === 0 ? "hidden" : "" From c3dffa3508f517cb11cacf391d48002545b5a323 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Fri, 28 Apr 2023 20:31:53 -0300 Subject: [PATCH 86/99] feat(chat_manager.py): add empty_history method to ChatHistory class fix(chat_manager.py): empty chat history for a client when an exception is raised fix(GenericNode): fix useEffect dependencies to avoid unnecessary re-renders --- src/backend/langflow/api/chat_manager.py | 5 +++++ .../src/CustomNodes/GenericNode/index.tsx | 16 ++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/backend/langflow/api/chat_manager.py b/src/backend/langflow/api/chat_manager.py index dccd0381b..5c490c43c 100644 --- a/src/backend/langflow/api/chat_manager.py +++ b/src/backend/langflow/api/chat_manager.py @@ -36,6 +36,10 @@ class ChatHistory(Subject): else: return [] + def empty_history(self, client_id: str): + """Empty the chat history for a client.""" + self.history[client_id] = [] + class ChatManager: def __init__(self): @@ -119,6 +123,7 @@ class ChatManager: except Exception as e: # Log stack trace logger.exception(e) + self.chat_history.empty_history(client_id) raise e # Send a response back to the frontend, if needed intermediate_steps = intermediate_steps or "" diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index ec864f8d7..b6d9b78ee 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -29,6 +29,13 @@ export default function GenericNode({ const [isGreenOutline, setIsGreenOutline] = useState(false); const [isRedOutline, setIsRedOutline] = useState(false); const { reactFlowInstance } = useContext(typesContext); + const [params, setParams] = useState([]); + + useEffect(() => { + if (reactFlowInstance) { + setParams(Object.values(reactFlowInstance.toObject())); + } + }, [reactFlowInstance]); const debouncedValidateNode = useDebouncedCallback(async () => { // Check if the validationStatus is "success" @@ -60,14 +67,7 @@ export default function GenericNode({ useEffect(() => { validateNode(); - }, [ - validateNode, - // Use the result of ...reactFlowInstance.toObject() - // to determine if the node has changed - // turn into an array of nodes and edges - // and compare the length of the array - ...Object.values(reactFlowInstance.toObject()), - ]); + }, [validateNode, params]); useEffect(() => { if (validationStatus === "success") { From f97586813c7358c4eecf79b363cede34ac879269 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Fri, 28 Apr 2023 23:03:13 -0300 Subject: [PATCH 87/99] fixed validation --- src/frontend/package.json | 2 +- .../src/CustomNodes/GenericNode/index.tsx | 351 ++++++++---------- 2 files changed, 166 insertions(+), 187 deletions(-) diff --git a/src/frontend/package.json b/src/frontend/package.json index 69eb7b47f..0a33f0fd8 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -62,5 +62,5 @@ "last 1 safari version" ] }, - "proxy": "http://backend:7860" + "proxy": "http://127.0.0.1:7860" } \ No newline at end of file diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index b6d9b78ee..39dae0ed8 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -1,10 +1,10 @@ import { TrashIcon } from "@heroicons/react/24/outline"; import { useDebouncedCallback } from "use-debounce"; import { - classNames, - nodeColors, - nodeIcons, - snakeToNormalCase, + classNames, + nodeColors, + nodeIcons, + snakeToNormalCase, } from "../../utils"; import ParameterComponent from "./components/parameterComponent"; import { typesContext } from "../../contexts/typesContext"; @@ -12,198 +12,177 @@ import { useContext, useState, useEffect, useRef } from "react"; import { NodeDataType } from "../../types/flow"; import { alertContext } from "../../contexts/alertContext"; import { useCallback } from "react"; - +import { TabsContext } from "../../contexts/tabsContext"; export default function GenericNode({ - data, - selected, + data, + selected, }: { - data: NodeDataType; - selected: boolean; + data: NodeDataType; + selected: boolean; }) { - const { setErrorData } = useContext(alertContext); - const showError = useRef(true); - const { types, deleteNode } = useContext(typesContext); - const Icon = nodeIcons[types[data.type]]; - const [validationStatus, setValidationStatus] = useState("idle"); - // State for outline color - const [isGreenOutline, setIsGreenOutline] = useState(false); - const [isRedOutline, setIsRedOutline] = useState(false); - const { reactFlowInstance } = useContext(typesContext); - const [params, setParams] = useState([]); + const { setErrorData } = useContext(alertContext); + const showError = useRef(true); + const { types, deleteNode } = useContext(typesContext); + const Icon = nodeIcons[types[data.type]]; + const [validationStatus, setValidationStatus] = useState("idle"); + // State for outline color + const [isValid, setIsValid] = useState(false); + const {save} = useContext(TabsContext) + const { reactFlowInstance } = useContext(typesContext); + const [params, setParams] = useState([]); - useEffect(() => { - if (reactFlowInstance) { - setParams(Object.values(reactFlowInstance.toObject())); - } - }, [reactFlowInstance]); + console.log(); - const debouncedValidateNode = useDebouncedCallback(async () => { - // Check if the validationStatus is "success" - // if (validationStatus === "success") return; + useEffect(() => { + if (reactFlowInstance) { + setParams(Object.values(reactFlowInstance.toObject())); + } + }, [save]); - try { - const response = await fetch(`/validate/node/${data.id}`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(reactFlowInstance.toObject()), - }); + useEffect(() => { + try { + fetch(`/validate/node/${data.id}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(reactFlowInstance.toObject()), + }).then((response) => { + console.log(response.status, response.body); - if (response.status === 200) { - setValidationStatus("success"); - } else if (response.status === 500) { - setValidationStatus("error"); - } - } catch (error) { - console.error("Error validating node:", error); - setValidationStatus("error"); - } - }, 1000); + if (response.status === 200) { + setValidationStatus("success"); + } else if (response.status === 500) { + setValidationStatus("error"); + } + }); + } catch (error) { + console.error("Error validating node:", error); + setValidationStatus("error"); + } + }, [params]); - const validateNode = useCallback(() => { - debouncedValidateNode(); - }, [debouncedValidateNode]); + useEffect(() => { + if (validationStatus === "success") { + setIsValid(true); + } else { + setIsValid(false); + } + }, [validationStatus]); - useEffect(() => { - validateNode(); - }, [validateNode, params]); + if (!Icon) { + if (showError.current) { + setErrorData({ + title: data.type + ? `The ${data.type} node could not be rendered, please review your json file` + : "There was a node that can't be rendered, please review your json file", + }); + showError.current = false; + } + deleteNode(data.id); + return; + } - useEffect(() => { - if (validationStatus === "success") { - setIsGreenOutline(true); - setIsRedOutline(false); - setTimeout(() => { - setIsGreenOutline(false); - }, 1000); - } else if (validationStatus === "error") { - setIsRedOutline(true); - setIsGreenOutline(false); - } else { - setIsGreenOutline(false); - setIsRedOutline(false); - } - }, [validationStatus]); + return ( +
+
+
+ +
{data.type}
+
+ +
- const outlineColor = isGreenOutline - ? "animate-pulse-green" - : isRedOutline - ? "border-red-outline" - : ""; +
+
+ {data.node.description} +
- if (!Icon) { - if (showError.current) { - setErrorData({ - title: data.type - ? `The ${data.type} node could not be rendered, please review your json file` - : "There was a node that can't be rendered, please review your json file", - }); - showError.current = false; - } - deleteNode(data.id); - return; - } - - return ( -
-
-
- -
{data.type}
-
- -
- -
-
- {data.node.description} -
- - <> - {Object.keys(data.node.template) - .filter((t) => t.charAt(0) !== "_") - .map((t: string, idx) => ( -
- {idx === 0 ? ( -
- !key.startsWith("_") && - data.node.template[key].show && - !data.node.template[key].advanced - ).length === 0 - ? "hidden" - : "" - )} - > - Inputs -
- ) : ( - <> - )} - {data.node.template[t].show ? ( - - ) : ( - <> - )} -
- ))} -
- Output -
- - -
-
- ); + <> + {Object.keys(data.node.template) + .filter((t) => t.charAt(0) !== "_") + .map((t: string, idx) => ( +
+ {idx === 0 ? ( +
+ !key.startsWith("_") && + data.node.template[key].show && + !data.node.template[key].advanced + ).length === 0 + ? "hidden" + : "" + )} + > + Inputs +
+ ) : ( + <> + )} + {data.node.template[t].show ? ( + + ) : ( + <> + )} +
+ ))} +
+ Output +
+ + +
+
+ ); } From d4d8418c13e9db9945e3b7ed7d116cc6970c042d Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Fri, 28 Apr 2023 23:10:46 -0300 Subject: [PATCH 88/99] dark mode fixed on chat --- src/frontend/src/modals/chatModal/chatMessage/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/frontend/src/modals/chatModal/chatMessage/index.tsx b/src/frontend/src/modals/chatModal/chatMessage/index.tsx index bbab8e686..85f569829 100644 --- a/src/frontend/src/modals/chatModal/chatMessage/index.tsx +++ b/src/frontend/src/modals/chatModal/chatMessage/index.tsx @@ -14,7 +14,7 @@ export default function ChatMessage({ chat }: { chat: ChatMessageType }) {

}
- + {chat.message} {chat.files && (
@@ -72,7 +72,7 @@ export default function ChatMessage({ chat }: { chat: ChatMessageType }) {
) : (
-
+
{chat.message}
From 659491f9a24919dc2d6310a110a1529f55064d97 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Fri, 28 Apr 2023 23:30:12 -0300 Subject: [PATCH 89/99] feat(langflow): add function support to build_template_from_class function feat(langflow): add function base class to InitializeAgentNode The `build_template_from_class` function in `ChainCreator` now supports adding a function to the chain. This is done by passing `add_function=True` to the function. In `InitializeAgentNode`, the `base_classes` attribute has been updated to include the `function` base class. This allows the node to be used as a function in the chain. --- src/backend/langflow/interface/chains/base.py | 4 +++- src/backend/langflow/template/nodes.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/backend/langflow/interface/chains/base.py b/src/backend/langflow/interface/chains/base.py index 9dc8ded3f..7b8da370b 100644 --- a/src/backend/langflow/interface/chains/base.py +++ b/src/backend/langflow/interface/chains/base.py @@ -37,7 +37,9 @@ class ChainCreator(LangChainTypeCreator): try: if name in get_custom_nodes(self.type_name).keys(): return get_custom_nodes(self.type_name)[name] - return build_template_from_class(name, self.type_to_loader_dict) + return build_template_from_class( + name, self.type_to_loader_dict, add_function=True + ) except ValueError as exc: raise ValueError("Chain not found") from exc except AttributeError as exc: diff --git a/src/backend/langflow/template/nodes.py b/src/backend/langflow/template/nodes.py index f0f2112a6..32dbec48a 100644 --- a/src/backend/langflow/template/nodes.py +++ b/src/backend/langflow/template/nodes.py @@ -216,7 +216,7 @@ class InitializeAgentNode(FrontendNode): ], ) description: str = """Construct a json agent from an LLM and tools.""" - base_classes: list[str] = ["AgentExecutor"] + base_classes: list[str] = ["AgentExecutor", "function"] def to_dict(self): return super().to_dict() From 5aa71e83fba32bafc479517ebe71401d97468232 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Fri, 28 Apr 2023 23:33:49 -0300 Subject: [PATCH 90/99] chore(frontend): update backend proxy to use container name instead of IP address --- src/frontend/package-lock.json | 12 ------------ src/frontend/package.json | 3 +-- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index e24e28bd9..9b90b435e 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -40,7 +40,6 @@ "reactflow": "^11.5.5", "tailwindcss": "^3.2.6", "typescript": "^4.9.5", - "use-debounce": "^9.0.4", "web-vitals": "^2.1.4" } }, @@ -17045,17 +17044,6 @@ "requires-port": "^1.0.0" } }, - "node_modules/use-debounce": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-9.0.4.tgz", - "integrity": "sha512-6X8H/mikbrt0XE8e+JXRtZ8yYVvKkdYRfmIhWZYsP8rcNs9hk3APV8Ua2mFkKRLcJKVdnX2/Vwrmg2GWKUQEaQ==", - "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0" - } - }, "node_modules/use-sync-external-store": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", diff --git a/src/frontend/package.json b/src/frontend/package.json index 0a33f0fd8..55d777687 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -35,7 +35,6 @@ "reactflow": "^11.5.5", "tailwindcss": "^3.2.6", "typescript": "^4.9.5", - "use-debounce": "^9.0.4", "web-vitals": "^2.1.4" }, "scripts": { @@ -62,5 +61,5 @@ "last 1 safari version" ] }, - "proxy": "http://127.0.0.1:7860" + "proxy": "http://backend:7860" } \ No newline at end of file From cccf99278690bca5e46a1121a9133225b7315c16 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Fri, 28 Apr 2023 23:41:49 -0300 Subject: [PATCH 91/99] update thought icon for dark mode --- src/frontend/src/modals/chatModal/chatMessage/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/modals/chatModal/chatMessage/index.tsx b/src/frontend/src/modals/chatModal/chatMessage/index.tsx index 85f569829..70f224f88 100644 --- a/src/frontend/src/modals/chatModal/chatMessage/index.tsx +++ b/src/frontend/src/modals/chatModal/chatMessage/index.tsx @@ -34,7 +34,7 @@ export default function ChatMessage({ chat }: { chat: ChatMessageType }) { onClick={() => setHidden((prev) => !prev)} className="absolute -top-1 -left-2 cursor-pointer" > - +
)} {chat.thought && chat.thought !== "" && !hidden && ( From 93b5a8e3275c6848871ea73d831b5cb739e82f17 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Fri, 28 Apr 2023 23:53:37 -0300 Subject: [PATCH 92/99] sorted side bar --- .../pages/FlowPage/components/extraSidebarComponent/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx index 0eb3876f1..17feb6723 100644 --- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx @@ -51,7 +51,7 @@ export default function ExtraSidebar() { return (
- {Object.keys(data).map((d: keyof APIObjectType, i) => ( + {Object.keys(data).sort().map((d: keyof APIObjectType, i) => (
- {Object.keys(data[d]).map((t: string, k) => ( + {Object.keys(data[d]).sort().map((t: string, k) => (
Date: Sat, 29 Apr 2023 01:29:15 -0300 Subject: [PATCH 93/99] updade package.json and removed debounce --- src/frontend/package.json | 2 +- src/frontend/src/CustomNodes/GenericNode/index.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/frontend/package.json b/src/frontend/package.json index 55d777687..388f8c9f7 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -61,5 +61,5 @@ "last 1 safari version" ] }, - "proxy": "http://backend:7860" + "proxy": "http://127.0.0.1:7860" } \ No newline at end of file diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index 61be1b9ba..fb9771be8 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -1,5 +1,4 @@ import { Cog6ToothIcon, TrashIcon } from "@heroicons/react/24/outline"; -import { useDebouncedCallback } from "use-debounce"; import { classNames, nodeColors, From 3ed0c35bdbf8352632cde4bcfd90bab649db45ee Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Sat, 29 Apr 2023 07:32:01 -0300 Subject: [PATCH 94/99] fix: added deps for linting --- poetry.lock | 56 ++++++++++++++++++++++++++++++++++++-------------- pyproject.toml | 2 ++ 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/poetry.lock b/poetry.lock index ee989ea7f..2b1ed2cc5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. [[package]] name = "aiohttp" @@ -2698,6 +2698,21 @@ pytz = ">=2020.1" [package.extras] test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"] +[[package]] +name = "pandas-stubs" +version = "2.0.0.230412" +description = "Type annotations for pandas" +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pandas_stubs-2.0.0.230412-py3-none-any.whl", hash = "sha256:311ab8b42ee574d9fea5061d1f63aeca297e472de6073ba84bf2a017c6cb1b6b"}, + {file = "pandas_stubs-2.0.0.230412.tar.gz", hash = "sha256:016f567cb9947edd0067ea2665ab00b77fa47e73a65ce1a097de4f499b3485c0"}, +] + +[package.dependencies] +types-pytz = ">=2022.1.1" + [[package]] name = "parso" version = "0.8.3" @@ -4169,10 +4184,6 @@ category = "main" optional = false python-versions = ">=3.8.0" files = [ - {file = "torch-2.0.0-1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:c9090bda7d2eeeecd74f51b721420dbeb44f838d4536cc1b284e879417e3064a"}, - {file = "torch-2.0.0-1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:bd42db2a48a20574d2c33489e120e9f32789c4dc13c514b0c44272972d14a2d7"}, - {file = "torch-2.0.0-1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:8969aa8375bcbc0c2993e7ede0a7f889df9515f18b9b548433f412affed478d9"}, - {file = "torch-2.0.0-1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:ab2da16567cb55b67ae39e32d520d68ec736191d88ac79526ca5874754c32203"}, {file = "torch-2.0.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:7a9319a67294ef02459a19738bbfa8727bb5307b822dadd708bc2ccf6c901aca"}, {file = "torch-2.0.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:9f01fe1f6263f31bd04e1757946fd63ad531ae37f28bb2dbf66f5c826ee089f4"}, {file = "torch-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:527f4ae68df7b8301ee6b1158ca56350282ea633686537b30dbb5d7b4a52622a"}, @@ -4390,15 +4401,6 @@ category = "main" optional = false python-versions = "*" files = [ - {file = "triton-2.0.0-1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38806ee9663f4b0f7cd64790e96c579374089e58f49aac4a6608121aa55e2505"}, - {file = "triton-2.0.0-1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:226941c7b8595219ddef59a1fdb821e8c744289a132415ddd584facedeb475b1"}, - {file = "triton-2.0.0-1-cp36-cp36m-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4c9fc8c89874bc48eb7e7b2107a9b8d2c0bf139778637be5bfccb09191685cfd"}, - {file = "triton-2.0.0-1-cp37-cp37m-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d2684b6a60b9f174f447f36f933e9a45f31db96cb723723ecd2dcfd1c57b778b"}, - {file = "triton-2.0.0-1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9d4978298b74fcf59a75fe71e535c092b023088933b2f1df933ec32615e4beef"}, - {file = "triton-2.0.0-1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:74f118c12b437fb2ca25e1a04759173b517582fcf4c7be11913316c764213656"}, - {file = "triton-2.0.0-1-pp37-pypy37_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9618815a8da1d9157514f08f855d9e9ff92e329cd81c0305003eb9ec25cc5add"}, - {file = "triton-2.0.0-1-pp38-pypy38_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1aca3303629cd3136375b82cb9921727f804e47ebee27b2677fef23005c3851a"}, - {file = "triton-2.0.0-1-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e3e13aa8b527c9b642e3a9defcc0fbd8ffbe1c80d8ac8c15a01692478dc64d8a"}, {file = "triton-2.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f05a7e64e4ca0565535e3d5d3405d7e49f9d308505bb7773d21fb26a4c008c2"}, {file = "triton-2.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb4b99ca3c6844066e516658541d876c28a5f6e3a852286bbc97ad57134827fd"}, {file = "triton-2.0.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47b4d70dc92fb40af553b4460492c31dc7d3a114a979ffb7a5cdedb7eb546c08"}, @@ -4441,6 +4443,30 @@ dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2 doc = ["cairosvg (>=2.5.2,<3.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pillow (>=9.3.0,<10.0.0)"] test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<13.0.0)", "shellingham (>=1.3.0,<2.0.0)"] +[[package]] +name = "types-pillow" +version = "9.5.0.2" +description = "Typing stubs for Pillow" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "types-Pillow-9.5.0.2.tar.gz", hash = "sha256:b3f9f621f259566c19c1deca21901017c8b1e3e200ed2e49e0a2d83c0a5175db"}, + {file = "types_Pillow-9.5.0.2-py3-none-any.whl", hash = "sha256:58fdebd0ffa2353ecccdd622adde23bce89da5c0c8b96c34f2d1eca7b7e42d0e"}, +] + +[[package]] +name = "types-pytz" +version = "2023.3.0.0" +description = "Typing stubs for pytz" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "types-pytz-2023.3.0.0.tar.gz", hash = "sha256:ecdc70d543aaf3616a7e48631543a884f74205f284cefd6649ddf44c6a820aac"}, + {file = "types_pytz-2023.3.0.0-py3-none-any.whl", hash = "sha256:4fc2a7fbbc315f0b6630e0b899fd6c743705abe1094d007b0e612d10da15e0f3"}, +] + [[package]] name = "types-pyyaml" version = "6.0.12.9" @@ -4968,4 +4994,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "a362f8d1e135082294c68b20de4aef52e5ac57d41d606d7e9b5ae62582b5e7df" +content-hash = "c8ba5d9e0208fc55dc2f00efffed26d7a2c7d325568dfd697cc6499d210304d9" diff --git a/pyproject.toml b/pyproject.toml index a37d63865..7ea498898 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,8 @@ pytest = "^7.2.2" types-requests = "^2.28.11" requests = "^2.28.0" pytest-cov = "^4.0.0" +pandas-stubs = "^2.0.0.230412" +types-pillow = "^9.5.0.2" [tool.ruff] From f3d50e9fea4c60a1908d539f8cdbc21fc2461f93 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Sat, 29 Apr 2023 07:37:16 -0300 Subject: [PATCH 95/99] formatting --- src/backend/langflow/cache/base.py | 8 +++----- src/backend/langflow/graph/base.py | 3 ++- src/backend/langflow/template/base.py | 4 ---- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/backend/langflow/cache/base.py b/src/backend/langflow/cache/base.py index ba250da6b..ede0fb06e 100644 --- a/src/backend/langflow/cache/base.py +++ b/src/backend/langflow/cache/base.py @@ -8,12 +8,10 @@ import os import tempfile from collections import OrderedDict from pathlib import Path -from typing import Any -from PIL import Image -import dill -import pandas as pd # type: ignore +from typing import Any, Dict +import dill # type: ignore -CACHE = {} +CACHE: Dict[str, Any] = {} def create_cache_folder(func): diff --git a/src/backend/langflow/graph/base.py b/src/backend/langflow/graph/base.py index ae82d68d1..e661fec69 100644 --- a/src/backend/langflow/graph/base.py +++ b/src/backend/langflow/graph/base.py @@ -162,7 +162,8 @@ class Node: # If the key is "func", then we need to use the run method if key == "func": if not isinstance(result, types.FunctionType): - # func can be PythonFunction(code='\ndef upper_case(text: str) -> str:\n return text.upper()\n') + # func can be + # PythonFunction(code='\ndef upper_case(text: str) -> str:\n return text.upper()\n') # so we need to check if there is an attribute called run if hasattr(result, "run"): result = result.run # type: ignore diff --git a/src/backend/langflow/template/base.py b/src/backend/langflow/template/base.py index 860d2ac1e..bab3a7b2d 100644 --- a/src/backend/langflow/template/base.py +++ b/src/backend/langflow/template/base.py @@ -229,10 +229,6 @@ class FrontendNode(BaseModel): field.required = False if field.value is None: field.value = "" - # If the field.name contains api or api and key, then it might be an api key - # other conditions are to make sure that it is not an input or output variable - if "api" in key.lower() and "key" in key.lower(): - field.required = False if "kwargs" in field.name.lower(): field.advanced = True From 3a5b708d536f7abcd4eb77578b420f4bbc141cd8 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Sat, 29 Apr 2023 07:37:32 -0300 Subject: [PATCH 96/99] deactivate class params check for now --- src/backend/langflow/interface/loading.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/backend/langflow/interface/loading.py b/src/backend/langflow/interface/loading.py index 8fd1b127d..cab23c7e4 100644 --- a/src/backend/langflow/interface/loading.py +++ b/src/backend/langflow/interface/loading.py @@ -25,7 +25,6 @@ from langflow.interface.toolkits.base import toolkits_creator from langflow.interface.types import get_type_list from langflow.interface.utils import load_file_into_dict from langflow.utils import util, validate -from pydantic import BaseModel def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any: @@ -35,11 +34,12 @@ def instantiate_class(node_type: str, base_type: str, params: Dict) -> Any: return custom_agent.initialize(**params) # type: ignore class_object = import_by_type(_type=base_type, name=node_type) + # check if it is a class before using issubclass - if issubclass(class_object, BaseModel): - # validate params - fields = class_object.__fields__ - params = {key: value for key, value in params.items() if key in fields} + # if isinstance(class_object, type) and issubclass(class_object, BaseModel): + # # validate params + # fields = class_object.__fields__ + # params = {key: value for key, value in params.items() if key in fields} if base_type == "agents": # We need to initialize it differently From ffb2aad45d55d120db04709b9673c1aa955c077c Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Sat, 29 Apr 2023 07:38:06 -0300 Subject: [PATCH 97/99] fixes to tests --- src/backend/langflow/template/nodes.py | 1 + tests/test_agents_template.py | 2 +- tests/test_chains_template.py | 14 ++++++++++---- tests/test_graph.py | 1 - tests/test_websocket.py | 3 --- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/backend/langflow/template/nodes.py b/src/backend/langflow/template/nodes.py index acf93c948..7b5ac6d3f 100644 --- a/src/backend/langflow/template/nodes.py +++ b/src/backend/langflow/template/nodes.py @@ -199,6 +199,7 @@ class SeriesCharacterChainNode(FrontendNode): "Chain", "ConversationChain", "SeriesCharacterChain", + "function", ] diff --git a/tests/test_agents_template.py b/tests/test_agents_template.py index a546454eb..4ed9d1153 100644 --- a/tests/test_agents_template.py +++ b/tests/test_agents_template.py @@ -130,7 +130,7 @@ def test_initialize_agent(client: TestClient): agents = json_response["agents"] initialize_agent = agents["initialize_agent"] - assert initialize_agent["base_classes"] == ["AgentExecutor"] + assert initialize_agent["base_classes"] == ["AgentExecutor", "function"] template = initialize_agent["template"] assert template["agent"] == { diff --git a/tests/test_chains_template.py b/tests/test_chains_template.py index cff844c90..13bef2e9c 100644 --- a/tests/test_chains_template.py +++ b/tests/test_chains_template.py @@ -20,7 +20,12 @@ def test_conversation_chain(client: TestClient): chain = chains["ConversationChain"] # Test the base classes, template, memory, verbose, llm, input_key, output_key, and _type objects - assert set(chain["base_classes"]) == {"LLMChain", "ConversationChain", "Chain"} + assert set(chain["base_classes"]) == { + "function", + "LLMChain", + "ConversationChain", + "Chain", + } template = chain["template"] assert template["memory"] == { "required": False, @@ -96,7 +101,7 @@ def test_llm_chain(client: TestClient): chain = chains["LLMChain"] # Test the base classes, template, memory, verbose, llm, input_key, output_key, and _type objects - assert set(chain["base_classes"]) == {"LLMChain", "Chain"} + assert set(chain["base_classes"]) == {"function", "LLMChain", "Chain"} template = chain["template"] assert template["memory"] == { "required": False, @@ -154,7 +159,7 @@ def test_llm_checker_chain(client: TestClient): chain = chains["LLMCheckerChain"] # Test the base classes, template, memory, verbose, llm, input_key, output_key, and _type objects - assert set(chain["base_classes"]) == {"LLMCheckerChain", "Chain"} + assert set(chain["base_classes"]) == {"function", "LLMCheckerChain", "Chain"} template = chain["template"] assert template["memory"] == { "required": False, @@ -231,7 +236,7 @@ def test_llm_math_chain(client: TestClient): chain = chains["LLMMathChain"] # Test the base classes, template, memory, verbose, llm, input_key, output_key, and _type objects - assert set(chain["base_classes"]) == {"LLMMathChain", "Chain"} + assert set(chain["base_classes"]) == {"function", "LLMMathChain", "Chain"} template = chain["template"] assert template["memory"] == { "required": False, @@ -310,6 +315,7 @@ def test_series_character_chain(client: TestClient): # Test the base classes, template, memory, verbose, llm, input_key, output_key, and _type objects assert set(chain["base_classes"]) == { + "function", "LLMChain", "BaseCustomChain", "Chain", diff --git a/tests/test_graph.py b/tests/test_graph.py index 76451fe6a..e109850e3 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -1,4 +1,3 @@ -import json from typing import Type, Union import pytest diff --git a/tests/test_websocket.py b/tests/test_websocket.py index 1137c4813..7ac646cf4 100644 --- a/tests/test_websocket.py +++ b/tests/test_websocket.py @@ -1,6 +1,5 @@ import json from unittest.mock import patch -from langflow.api.schemas import ChatMessage from fastapi.testclient import TestClient @@ -11,8 +10,6 @@ def test_websocket_connection(client: TestClient): def test_chat_history(client: TestClient): - chat_history = [] - # Mock the process_graph function to return a specific value with patch("langflow.api.chat_manager.process_graph") as mock_process_graph: mock_process_graph.return_value = ("Hello, I'm a mock response!", "") From 9a6e7cfc3ff77dd9022582205c0084e9174b8cdf Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Sat, 29 Apr 2023 07:38:17 -0300 Subject: [PATCH 98/99] add test_cache_manager --- tests/test_cache_manager.py | 81 +++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 tests/test_cache_manager.py diff --git a/tests/test_cache_manager.py b/tests/test_cache_manager.py new file mode 100644 index 000000000..8680a43cb --- /dev/null +++ b/tests/test_cache_manager.py @@ -0,0 +1,81 @@ +import pytest +from PIL import Image +import pandas as pd +from io import StringIO +from langflow.cache.manager import CacheManager + + +@pytest.fixture +def cache_manager(): + return CacheManager() + + +def test_cache_manager_attach_detach_notify(cache_manager): + observer_called = False + + def observer(): + nonlocal observer_called + observer_called = True + + cache_manager.attach(observer) + cache_manager.notify() + + assert observer_called + + observer_called = False + cache_manager.detach(observer) + cache_manager.notify() + + assert not observer_called + + +def test_cache_manager_client_context(cache_manager): + with cache_manager.set_client_id("client1"): + cache_manager.add("foo", "bar", "string") + assert cache_manager.get("foo") == { + "obj": "bar", + "type": "string", + "extension": "str", + } + + with cache_manager.set_client_id("client2"): + cache_manager.add("baz", "qux", "string") + assert cache_manager.get("baz") == { + "obj": "qux", + "type": "string", + "extension": "str", + } + + with pytest.raises(KeyError): + cache_manager.get("foo") + + +def test_cache_manager_add_pandas(cache_manager): + df = pd.DataFrame({"col1": [1, 2], "col2": [3, 4]}) + + with cache_manager.set_client_id("client1"): + cache_manager.add_pandas("test_df", df) + cached_df = cache_manager.get("test_df") + assert cached_df["type"] == "pandas" + assert cached_df["extension"] == "csv" + read_df = pd.read_csv(StringIO(cached_df["obj"]), index_col=0) + pd.testing.assert_frame_equal(df, read_df) + + +def test_cache_manager_add_image(cache_manager): + img = Image.new("RGB", (50, 50), color="red") + + with cache_manager.set_client_id("client1"): + cache_manager.add_image("test_image", img) + cached_img = cache_manager.get("test_image") + assert cached_img["type"] == "image" + assert cached_img["extension"] == "png" + assert isinstance(cached_img["obj"], Image.Image) + + +def test_cache_manager_get_last(cache_manager): + with cache_manager.set_client_id("client1"): + cache_manager.add("foo", "bar", "string") + cache_manager.add("baz", "qux", "string") + last_item = cache_manager.get_last() + assert last_item == {"obj": "qux", "type": "string", "extension": "str"} From ce82b7e61159f083123d401e8d33f5eff5738e05 Mon Sep 17 00:00:00 2001 From: Gabriel Almeida Date: Sat, 29 Apr 2023 07:38:27 -0300 Subject: [PATCH 99/99] remove unused imports --- src/backend/langflow/utils/util.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/backend/langflow/utils/util.py b/src/backend/langflow/utils/util.py index 080137c26..874debd8d 100644 --- a/src/backend/langflow/utils/util.py +++ b/src/backend/langflow/utils/util.py @@ -1,5 +1,4 @@ -import asyncio -from functools import partial, wraps +from functools import wraps import importlib import inspect import re