feat: Add image upload support with file path handling (#8830)

*  (data.py): introduce a new function get_file_paths to resolve file paths before creating image URLs in HumanMessage contents

*  (general-bugs-agent-images-playground.spec.ts): add test case for user to send images in the playground with the agent component

* 📝 (general-bugs-agent-images-playground.spec.ts): update file path to image file to fix test failure due to incorrect file path

* [autofix.ci] apply automated fixes

* fix: autofix.ci / Update Starter Projects (pull_request)

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: italojohnny <italojohnnydosanjos@gmail.com>
This commit is contained in:
Cristhian Zanforlin Lousa 2025-07-04 10:54:22 -03:00 committed by GitHub
commit 557f7b31aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 216 additions and 121 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -169,8 +169,11 @@ class Data(BaseModel):
files = self.data.get("files", [])
if sender == MESSAGE_SENDER_USER:
if files:
from langflow.schema.image import get_file_paths
contents = [{"type": "text", "text": text}]
for file_path in files:
resolved_file_paths = get_file_paths(files)
for file_path in resolved_file_paths:
image_url = create_data_url(file_path)
contents.append({"type": "image_url", "image_url": {"url": image_url}})
human_message = HumanMessage(content=contents)

View file

@ -0,0 +1,92 @@
import { expect, test } from "@playwright/test";
import dotenv from "dotenv";
import { readFileSync } from "fs";
import path from "path";
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
test(
"user must be able to send images in the playground with the agent component",
{ tag: ["@release", "@components"] },
async ({ page }) => {
test.skip(
!process?.env?.ANTHROPIC_API_KEY,
"ANTHROPIC_API_KEY required to run this test",
);
if (!process.env.CI) {
dotenv.config({ path: path.resolve(__dirname, "../../.env") });
}
await awaitBootstrapTest(page);
await page.getByTestId("side_nav_options_all-templates").click();
await page.getByRole("heading", { name: "Simple Agent" }).first().click();
await page.getByTestId("value-dropdown-dropdown_str_agent_llm").click();
await page.waitForTimeout(200);
await page.getByText("Anthropic").last().click();
await page
.getByTestId("popover-anchor-input-api_key")
.fill(process.env.ANTHROPIC_API_KEY || "");
await page.getByTestId("playground-btn-flow-io").click();
// Read the image file as a binary string
const filePath = "tests/assets/chain.png";
const fileContent = readFileSync(filePath, "base64");
// Create the DataTransfer and File objects within the browser context
const dataTransfer = await page.evaluateHandle(
({ fileContent }) => {
const dt = new DataTransfer();
const byteCharacters = atob(fileContent);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
const file = new File([byteArray], "chain.png", { type: "image/png" });
dt.items.add(file);
return dt;
},
{ fileContent },
);
await page.waitForSelector('[data-testid="input-chat-playground"]', {
timeout: 100000,
});
// Locate the target element
const element = await page.getByTestId("input-chat-playground");
// Dispatch the drop event on the target element
await element.dispatchEvent("drop", { dataTransfer });
await page
.getByTestId("input-chat-playground")
.fill("tell me a small history about the image");
await page.waitForSelector('[data-testid="button-send"]', {
timeout: 100000,
});
await page.getByTestId("button-send").click();
await page.waitForSelector("text=chain.png", { timeout: 30000 });
await page.getByText("chain.png").isVisible();
await page.waitForTimeout(5000);
const textFromLlm = await page
.locator(".markdown.prose")
.last()
.textContent();
expect(textFromLlm?.toLowerCase()).toContain("chain");
const lengthOfTextFromLlm = textFromLlm?.length;
expect(lengthOfTextFromLlm).toBeGreaterThan(100);
},
);