fix: image not been sent on ChatInputComponent during runtime of building a flow (#3862)

* refactor: Refactor file path rewriting logic

This commit refactors the logic for rewriting file paths in the `rewrite_file_path` function. The function now splits the file path by "/" and checks if it has at least two parts. If it does, it creates a consistent file path by concatenating the last two parts. If not, it returns the original file path. This change improves the consistency of file paths in the codebase.

Refactor the file path rewriting logic in the `rewrite_file_path` function.

* refactor: Refactor file path rewriting logic and treat file paths in InterfaceVertex

* Refactor file path rewriting logic and treat file paths in InterfaceVertex

* Refactor file path rewriting logic and treat file paths in InterfaceVertex

* Refactor file path rewriting logic and treat file paths in InterfaceVertex

*  (general-bugs-shard-3836.spec.ts): update test description to be more specific about the tool used for sending images on chat
📝 (general-bugs-shard-3836.spec.ts): remove unused import of readFileSync from fs module

*  (test_rewrite_file_path.py): add unit tests for the rewrite_file_path function to ensure correct behavior with various file path scenarios

* ♻️ (utils.py): refactor file_path function to handle both forward and backward slashes and extract file path after drive letter if present

* style: fix single quotes, commas, and spaces

---------

Co-authored-by: italojohnny <italojohnnydosanjos@gmail.com>
This commit is contained in:
Cristhian Zanforlin Lousa 2024-09-23 12:19:09 -03:00 committed by GitHub
commit ad97ee9830
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 192 additions and 2 deletions

View file

@ -185,3 +185,19 @@ def log_vertex_build(
logger.debug(f"Logged vertex build: {inserted.build_id}")
except Exception as e:
logger.exception(f"Error logging vertex build: {e}")
def rewrite_file_path(file_path: str):
file_path = file_path.replace("\\", "/")
if ":" in file_path:
file_path = file_path.split(":", 1)[-1]
file_path_split = [part for part in file_path.split("/") if part]
if len(file_path_split) >= 2:
consistent_file_path = f"{file_path_split[-2]}/{file_path_split[-1]}"
else:
consistent_file_path = "/".join(file_path_split)
return [consistent_file_path]

View file

@ -8,7 +8,7 @@ from langchain_core.messages import AIMessage, AIMessageChunk
from loguru import logger
from langflow.graph.schema import CHAT_COMPONENTS, RECORDS_COMPONENTS, InterfaceComponentTypes, ResultData
from langflow.graph.utils import UnbuiltObject, log_transaction, log_vertex_build, serialize_field
from langflow.graph.utils import UnbuiltObject, log_transaction, log_vertex_build, rewrite_file_path, serialize_field
from langflow.graph.vertex.base import Vertex
from langflow.graph.vertex.exceptions import NoComponentInstance
from langflow.graph.vertex.schema import NodeData
@ -256,6 +256,10 @@ class InterfaceVertex(ComponentVertex):
sender = self.params.get("sender", None)
sender_name = self.params.get("sender_name", None)
message = self.params.get(INPUT_FIELD_NAME, None)
files = self.params.get("files", [])
treat_file_path = files is not None and not isinstance(files, list) and isinstance(files, str)
if treat_file_path:
self.params["files"] = rewrite_file_path(files)
files = [{"path": file} if isinstance(file, str) else file for file in self.params.get("files", [])]
if isinstance(message, str):
message = unescape_string(message)
@ -382,6 +386,12 @@ class InterfaceVertex(ComponentVertex):
yield message
complete_message += message
files = self.params.get("files", [])
treat_file_path = files is not None and not isinstance(files, list) and isinstance(files, str)
if treat_file_path:
self.params["files"] = rewrite_file_path(files)
if hasattr(self.params.get("sender_name"), "get_text"):
sender_name = self.params.get("sender_name").get_text()
else:

View file

@ -30,6 +30,13 @@ class MessageBase(SQLModel):
# first check if the record has all the required fields
if message.text is None or not message.sender or not message.sender_name:
raise ValueError("The message does not have the required fields (text, sender, sender_name).")
if message.files:
image_paths = []
for file in message.files:
if hasattr(file, "path") and hasattr(file, "url") and file.path:
session_id = message.session_id
image_paths.append(f"{session_id}{file.path.split(session_id)[1]}")
message.files = image_paths
if isinstance(message.timestamp, str):
timestamp = datetime.fromisoformat(message.timestamp)
else:

View file

@ -0,0 +1,43 @@
from langflow.graph.utils import rewrite_file_path
import pytest
@pytest.mark.parametrize(
"file_path, expected",
[
# Test case 1: Standard path with multiple directories
("/home/user/documents/file.txt", ["documents/file.txt"]),
# Test case 2: Path with only one directory
("/documents/file.txt", ["documents/file.txt"]),
# Test case 3: Path with no directories (just filename)
("file.txt", ["file.txt"]),
# Test case 4: Path with multiple levels and special characters
("/home/user/my-docs/special_file!.pdf", ["my-docs/special_file!.pdf"]),
# Test case 5: Path with trailing slash
("/home/user/documents/", ["user/documents"]),
# Test case 6: Empty path
("", [""]),
# Test case 7: Path with only slashes
("///", [""]),
# Test case 8: Path with dots
("/home/user/../documents/./file.txt", ["./file.txt"]),
# Test case 9: Windows-style path
("C:\\Users\\Documents\\file.txt", ["Documents/file.txt"]),
# Test case 10: Windows path with trailing backslash
("C:\\Users\\Documents\\", ["Users/Documents"]),
# Test case 11: Mixed separators
("C:/Users\\Documents/file.txt", ["Documents/file.txt"]),
# Test case 12: Network path (UNC)
("\\\\server\\share\\file.txt", ["share/file.txt"]),
],
)
def test_rewrite_file_path(file_path, expected):
result = rewrite_file_path(file_path)
assert result == expected
# Additional test for type checking
def test_rewrite_file_path_type():
result = rewrite_file_path("/home/user/file.txt")
assert isinstance(result, list)
assert all(isinstance(item, str) for item in result)

View file

@ -1,4 +1,4 @@
import { expect, test } from "@playwright/test";
import { test } from "@playwright/test";
import * as dotenv from "dotenv";
import { readFileSync } from "fs";
import path from "path";

View file

@ -0,0 +1,114 @@
import { expect, test } from "@playwright/test";
import * as dotenv from "dotenv";
import path from "path";
test("user must be able to send an image on chat using advanced tool on ChatInputComponent", async ({
page,
}) => {
test.skip(
!process?.env?.OPENAI_API_KEY,
"OPENAI_API_KEY required to run this test",
);
if (!process.env.CI) {
dotenv.config({ path: path.resolve(__dirname, "../../.env") });
}
await page.goto("/");
await page.waitForTimeout(1000);
let modalCount = 0;
try {
const modalTitleElement = await page?.getByTestId("modal-title");
if (modalTitleElement) {
modalCount = await modalTitleElement.count();
}
} catch (error) {
modalCount = 0;
}
while (modalCount === 0) {
await page.getByText("New Project", { exact: true }).click();
await page.waitForTimeout(3000);
modalCount = await page.getByTestId("modal-title")?.count();
}
await page.getByRole("heading", { name: "Basic Prompting" }).click();
await page.waitForSelector('[title="fit view"]', {
timeout: 100000,
});
await page.getByTitle("fit view").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
await page.getByTitle("zoom out").click();
let outdatedComponents = await page.getByTestId("icon-AlertTriangle").count();
while (outdatedComponents > 0) {
await page.getByTestId("icon-AlertTriangle").first().click();
await page.waitForTimeout(1000);
outdatedComponents = await page.getByTestId("icon-AlertTriangle").count();
}
await page
.getByTestId("popover-anchor-input-api_key")
.fill(process.env.OPENAI_API_KEY ?? "");
await page.getByTestId("dropdown_str_model_name").click();
await page.getByTestId("gpt-4o-1-option").click();
await page.waitForSelector("text=Chat Input", { timeout: 30000 });
await page.getByText("Chat Input", { exact: true }).click();
await page.getByTestId("more-options-modal").click();
await page.getByTestId("edit-button-modal").click();
await page.getByTestId("showfiles").click();
await page.getByText("Close").last().click();
await page.waitForTimeout(500);
const userQuestion = "What is this image?";
await page.getByTestId("textarea_str_input_value").fill(userQuestion);
const filePath = "tests/assets/chain.png";
await page.click('[data-testid="inputfile_file_files"]');
const [fileChooser] = await Promise.all([
page.waitForEvent("filechooser"),
page.click('[data-testid="inputfile_file_files"]'),
]);
await fileChooser.setFiles(filePath);
await page.keyboard.press("Escape");
await page.getByTestId("button_run_chat output").click();
await page.getByText("built successfully").last().click({
timeout: 15000,
});
await page.getByText("Playground", { exact: true }).click();
await page.waitForTimeout(500);
await page.waitForSelector('[data-testid="icon-LucideSend"]', {
timeout: 100000,
});
await page.waitForSelector("text=chain.png", { timeout: 30000 });
expect(await page.getByAltText("generated image").isVisible()).toBeTruthy();
expect(
await page.getByTestId(`chat-message-User-${userQuestion}`).isVisible(),
).toBeTruthy();
const textContents = await page
.getByTestId("div-chat-message")
.allTextContents();
expect(textContents[0]).toContain("chain");
});