fix: Fix image preview for Windows paths in playground (#12083)
* fix image sent on windows playground * [autofix.ci] apply automated fixes * fix jest --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
351b943a1b
commit
9ce2b894fe
@@ -180,13 +180,13 @@ describe("ChatMessage Component", () => {
|
||||
chat: {
|
||||
...mockChat,
|
||||
message: "",
|
||||
files: ["/path/to/file.jpg"],
|
||||
files: ["/path/to/file.pdf"],
|
||||
},
|
||||
};
|
||||
|
||||
render(<ChatMessage {...propsWithFiles} />);
|
||||
// Should render UserMessage with file preview (shows loading icon for files)
|
||||
expect(screen.getByTestId("loading-icon")).toBeInTheDocument();
|
||||
// Should render UserMessage with file preview (non-image files show File icon)
|
||||
expect(screen.getByTestId("forwarded-icon")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders bot message when no text and no files", () => {
|
||||
|
||||
@@ -0,0 +1,549 @@
|
||||
import { getBaseUrl } from "@/customization/utils/urls";
|
||||
import {
|
||||
extractFileInfo,
|
||||
formatFileName,
|
||||
getFileDisplayName,
|
||||
getFilePreviewUrl,
|
||||
isImageFile,
|
||||
} from "../file-utils";
|
||||
|
||||
// Mock the getBaseUrl function
|
||||
jest.mock("@/customization/utils/urls", () => ({
|
||||
getBaseUrl: jest.fn(() => "http://localhost:3000/api/v1/"),
|
||||
}));
|
||||
|
||||
const mockGetBaseUrl = getBaseUrl as jest.MockedFunction<typeof getBaseUrl>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockGetBaseUrl.mockReturnValue("http://localhost:3000/api/v1/");
|
||||
});
|
||||
|
||||
describe("file-utils", () => {
|
||||
describe("isImageFile", () => {
|
||||
describe("File object detection", () => {
|
||||
it("should_return_true_for_browser_File_with_image_mime_type", () => {
|
||||
const file = new File(["content"], "test.jpg", {
|
||||
type: "image/jpeg",
|
||||
});
|
||||
expect(isImageFile(file)).toBe(true);
|
||||
});
|
||||
|
||||
it("should_return_true_for_browser_File_with_image_prefix", () => {
|
||||
const file = new File(["content"], "test.png", {
|
||||
type: "image/png",
|
||||
});
|
||||
expect(isImageFile(file)).toBe(true);
|
||||
});
|
||||
|
||||
it("should_return_false_for_browser_File_with_text_type", () => {
|
||||
const file = new File(["content"], "test.txt", {
|
||||
type: "text/plain",
|
||||
});
|
||||
expect(isImageFile(file)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Windows path normalization", () => {
|
||||
it("should_detect_image_from_Windows_backslash_path", () => {
|
||||
const windowsPath = "C:\\Users\\test\\image.jpg";
|
||||
expect(isImageFile(windowsPath)).toBe(true);
|
||||
});
|
||||
|
||||
it("should_detect_image_from_nested_Windows_path", () => {
|
||||
const windowsPath = "flow123\\subfolder\\image.png";
|
||||
expect(isImageFile(windowsPath)).toBe(true);
|
||||
});
|
||||
|
||||
it("should_detect_image_from_mixed_path_separators", () => {
|
||||
const mixedPath = "flow123/subfolder\\image.gif";
|
||||
expect(isImageFile(mixedPath)).toBe(true);
|
||||
});
|
||||
|
||||
it("should_reject_non_image_from_Windows_path", () => {
|
||||
const windowsPath = "C:\\Users\\test\\document.pdf";
|
||||
expect(isImageFile(windowsPath)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Unix path handling", () => {
|
||||
it("should_detect_image_from_Unix_forward_slash_path", () => {
|
||||
const unixPath = "/home/user/image.jpg";
|
||||
expect(isImageFile(unixPath)).toBe(true);
|
||||
});
|
||||
|
||||
it("should_detect_image_from_flow_path", () => {
|
||||
const flowPath = "flow123/image.webp";
|
||||
expect(isImageFile(flowPath)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Object with path and type properties", () => {
|
||||
it("should_prioritize_path_extension_over_type_property", () => {
|
||||
const fileObj = {
|
||||
path: "flow123\\image.jpg",
|
||||
type: "text/plain", // Conflicting type
|
||||
};
|
||||
expect(isImageFile(fileObj)).toBe(true);
|
||||
});
|
||||
|
||||
it("should_fall_back_to_type_property_when_path_extension_invalid", () => {
|
||||
const fileObj = {
|
||||
path: "flow123\\unknown",
|
||||
type: "image/png",
|
||||
};
|
||||
expect(isImageFile(fileObj)).toBe(true);
|
||||
});
|
||||
|
||||
it("should_normalize_Windows_paths_in_object", () => {
|
||||
const fileObj = {
|
||||
path: "C:\\temp\\folder\\image.bmp",
|
||||
type: "application/octet-stream",
|
||||
};
|
||||
expect(isImageFile(fileObj)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Edge cases", () => {
|
||||
it("should_handle_empty_string_path", () => {
|
||||
expect(isImageFile("")).toBe(false);
|
||||
});
|
||||
|
||||
it("should_handle_path_without_extension", () => {
|
||||
expect(isImageFile("just-a-filename")).toBe(false);
|
||||
});
|
||||
|
||||
it("should_handle_path_with_multiple_dots", () => {
|
||||
expect(isImageFile("file.backup.jpg")).toBe(true);
|
||||
});
|
||||
|
||||
it("should_handle_uppercase_extension", () => {
|
||||
expect(isImageFile("image.JPG")).toBe(true);
|
||||
expect(isImageFile("C:\\Images\\photo.PNG")).toBe(true);
|
||||
});
|
||||
|
||||
it("should_handle_null_or_undefined_input", () => {
|
||||
expect(isImageFile(null as any)).toBe(false);
|
||||
expect(isImageFile(undefined as any)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getFileDisplayName", () => {
|
||||
describe("File object handling", () => {
|
||||
it("should_return_file_name_from_browser_File", () => {
|
||||
const file = new File(["content"], "test-image.jpg");
|
||||
expect(getFileDisplayName(file)).toBe("test-image.jpg");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Windows path normalization", () => {
|
||||
it("should_extract_filename_from_Windows_absolute_path", () => {
|
||||
const windowsPath = "C:\\Users\\username\\Documents\\report.pdf";
|
||||
expect(getFileDisplayName(windowsPath)).toBe("report.pdf");
|
||||
});
|
||||
|
||||
it("should_extract_filename_from_Windows_relative_path", () => {
|
||||
const windowsPath = "folder\\subfolder\\image.png";
|
||||
expect(getFileDisplayName(windowsPath)).toBe("image.png");
|
||||
});
|
||||
|
||||
it("should_handle_mixed_path_separators", () => {
|
||||
const mixedPath = "flow/data\\files\\document.txt";
|
||||
expect(getFileDisplayName(mixedPath)).toBe("document.txt");
|
||||
});
|
||||
|
||||
it("should_handle_single_backslash_path", () => {
|
||||
const path = "flow123\\image.jpg";
|
||||
expect(getFileDisplayName(path)).toBe("image.jpg");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Unix path handling", () => {
|
||||
it("should_extract_filename_from_Unix_path", () => {
|
||||
const unixPath = "/home/user/documents/file.txt";
|
||||
expect(getFileDisplayName(unixPath)).toBe("file.txt");
|
||||
});
|
||||
|
||||
it("should_extract_filename_from_relative_Unix_path", () => {
|
||||
const unixPath = "folder/subfolder/image.png";
|
||||
expect(getFileDisplayName(unixPath)).toBe("image.png");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Object with name and path properties", () => {
|
||||
it("should_prefer_name_property_when_available", () => {
|
||||
const fileObj = {
|
||||
name: "custom-name.jpg",
|
||||
path: "C:\\long\\path\\different-name.jpg",
|
||||
};
|
||||
expect(getFileDisplayName(fileObj)).toBe("custom-name.jpg");
|
||||
});
|
||||
|
||||
it("should_extract_from_path_when_no_name_property", () => {
|
||||
const fileObj = {
|
||||
path: "flow123\\folder\\image.png",
|
||||
};
|
||||
expect(getFileDisplayName(fileObj)).toBe("image.png");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Edge cases", () => {
|
||||
it("should_handle_empty_string", () => {
|
||||
expect(getFileDisplayName("")).toBe("");
|
||||
});
|
||||
|
||||
it("should_return_original_string_when_no_separators", () => {
|
||||
expect(getFileDisplayName("filename")).toBe("filename");
|
||||
});
|
||||
|
||||
it("should_handle_path_ending_with_separator", () => {
|
||||
// When path ends with \, it gets normalized to / and split creates empty string at end
|
||||
expect(getFileDisplayName("folder\\")).toBe("folder\\"); // Falls back to original
|
||||
expect(getFileDisplayName("folder/")).toBe("folder/"); // Falls back to original
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getFilePreviewUrl", () => {
|
||||
describe("File object handling", () => {
|
||||
it("should_return_object_URL_for_browser_File", () => {
|
||||
// Mock URL.createObjectURL
|
||||
const mockObjectURL = "blob:http://localhost/123-456";
|
||||
global.URL.createObjectURL = jest.fn(() => mockObjectURL);
|
||||
|
||||
const file = new File(["content"], "test.jpg", {
|
||||
type: "image/jpeg",
|
||||
});
|
||||
|
||||
expect(getFilePreviewUrl(file)).toBe(mockObjectURL);
|
||||
expect(URL.createObjectURL).toHaveBeenCalledWith(file);
|
||||
});
|
||||
|
||||
it("should_return_null_for_non_image_File", () => {
|
||||
const file = new File(["content"], "test.txt", {
|
||||
type: "text/plain",
|
||||
});
|
||||
|
||||
expect(getFilePreviewUrl(file)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Windows path normalization and URL construction", () => {
|
||||
it("should_normalize_Windows_path_and_create_URL", () => {
|
||||
const windowsPath = "flow123\\subfolder\\image.jpg";
|
||||
const expected =
|
||||
"http://localhost:3000/api/v1/files/images/flow123/subfolder/image.jpg";
|
||||
|
||||
expect(getFilePreviewUrl(windowsPath)).toBe(expected);
|
||||
});
|
||||
|
||||
it("should_handle_absolute_Windows_path", () => {
|
||||
// Note: This tests the current behavior, but absolute paths shouldn't typically be used
|
||||
const windowsPath = "C:\\temp\\flow123\\image.png";
|
||||
const expected =
|
||||
"http://localhost:3000/api/v1/files/images/C%3A/temp/flow123/image.png";
|
||||
|
||||
expect(getFilePreviewUrl(windowsPath)).toBe(expected);
|
||||
});
|
||||
|
||||
it("should_encode_special_characters_in_path_segments", () => {
|
||||
const pathWithSpaces = "flow 123\\folder name\\image file.jpg";
|
||||
const expected =
|
||||
"http://localhost:3000/api/v1/files/images/flow%20123/folder%20name/image%20file.jpg";
|
||||
|
||||
expect(getFilePreviewUrl(pathWithSpaces)).toBe(expected);
|
||||
});
|
||||
|
||||
it("should_handle_mixed_path_separators", () => {
|
||||
const mixedPath = "flow123/data\\images\\photo.png";
|
||||
const expected =
|
||||
"http://localhost:3000/api/v1/files/images/flow123/data/images/photo.png";
|
||||
|
||||
expect(getFilePreviewUrl(mixedPath)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Unix path handling", () => {
|
||||
it("should_create_URL_for_Unix_path", () => {
|
||||
const unixPath = "flow123/images/photo.jpg";
|
||||
const expected =
|
||||
"http://localhost:3000/api/v1/files/images/flow123/images/photo.jpg";
|
||||
|
||||
expect(getFilePreviewUrl(unixPath)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Object with path property", () => {
|
||||
it("should_normalize_Windows_path_in_object", () => {
|
||||
const fileObj = {
|
||||
path: "flow123\\images\\photo.jpg",
|
||||
type: "image/jpeg",
|
||||
};
|
||||
const expected =
|
||||
"http://localhost:3000/api/v1/files/images/flow123/images/photo.jpg";
|
||||
|
||||
expect(getFilePreviewUrl(fileObj)).toBe(expected);
|
||||
});
|
||||
|
||||
it("should_return_null_for_non_image_object", () => {
|
||||
const fileObj = {
|
||||
path: "flow123\\documents\\file.txt",
|
||||
type: "text/plain",
|
||||
};
|
||||
|
||||
expect(getFilePreviewUrl(fileObj)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Edge cases", () => {
|
||||
it("should_return_null_for_empty_path_string", () => {
|
||||
expect(getFilePreviewUrl("")).toBeNull();
|
||||
});
|
||||
|
||||
it("should_return_null_for_whitespace_only_path", () => {
|
||||
expect(getFilePreviewUrl(" ")).toBeNull();
|
||||
});
|
||||
|
||||
it("should_handle_path_with_only_spaces", () => {
|
||||
expect(getFilePreviewUrl(" ")).toBeNull();
|
||||
});
|
||||
|
||||
it("should_return_null_for_object_with_empty_path", () => {
|
||||
const fileObj = {
|
||||
path: "",
|
||||
type: "image/jpeg",
|
||||
};
|
||||
|
||||
expect(getFilePreviewUrl(fileObj)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Base URL variations", () => {
|
||||
it("should_work_with_different_base_URLs", () => {
|
||||
mockGetBaseUrl.mockReturnValue("https://example.com/api/");
|
||||
|
||||
const path = "flow123\\image.jpg";
|
||||
const expected =
|
||||
"https://example.com/api/files/images/flow123/image.jpg";
|
||||
|
||||
expect(getFilePreviewUrl(path)).toBe(expected);
|
||||
});
|
||||
|
||||
it("should_handle_base_URL_without_trailing_slash", () => {
|
||||
mockGetBaseUrl.mockReturnValue("http://localhost:8000/api");
|
||||
|
||||
const path = "flow123\\image.jpg";
|
||||
const expected =
|
||||
"http://localhost:8000/apifiles/images/flow123/image.jpg";
|
||||
|
||||
expect(getFilePreviewUrl(path)).toBe(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatFileName", () => {
|
||||
it("should_return_unchanged_when_under_limit", () => {
|
||||
const name = "short.jpg";
|
||||
expect(formatFileName(name)).toBe("short.jpg");
|
||||
});
|
||||
|
||||
it("should_truncate_long_basename_with_ellipsis", () => {
|
||||
const name = "this-is-a-very-long-filename-that-should-be-truncated.jpg";
|
||||
const result = formatFileName(name, 25);
|
||||
|
||||
expect(result).toContain("...");
|
||||
expect(result).toContain(".jpg");
|
||||
expect(result).toContain("this-is-a-very-long-filen");
|
||||
});
|
||||
|
||||
it("should_preserve_short_basename_even_when_over_total_limit", () => {
|
||||
const name = "short.extension";
|
||||
expect(formatFileName(name, 10)).toBe("short.extension");
|
||||
});
|
||||
|
||||
it("should_handle_files_without_extension", () => {
|
||||
const name = "filename-without-extension-very-long";
|
||||
const result = formatFileName(name, 25);
|
||||
|
||||
// When no extension is found, lastIndexOf('.') returns -1,
|
||||
// so baseName becomes entire string, fileExtension becomes the last part
|
||||
expect(result).toContain("...");
|
||||
expect(result.length).toBeGreaterThan(25);
|
||||
});
|
||||
|
||||
it("should_use_custom_max_length", () => {
|
||||
const name = "moderately-long-filename.jpg";
|
||||
const result = formatFileName(name, 15);
|
||||
|
||||
expect(result).toContain("...");
|
||||
expect(result).toContain(".jpg");
|
||||
});
|
||||
});
|
||||
|
||||
describe("extractFileInfo", () => {
|
||||
describe("File object handling", () => {
|
||||
it("should_extract_info_from_browser_File", () => {
|
||||
const file = new File(["content"], "test.jpg", {
|
||||
type: "image/jpeg",
|
||||
});
|
||||
|
||||
const result = extractFileInfo(file);
|
||||
|
||||
expect(result).toEqual({
|
||||
name: "test.jpg",
|
||||
type: "image/jpeg",
|
||||
path: "test.jpg",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Windows path normalization", () => {
|
||||
it("should_extract_info_from_Windows_path_string", () => {
|
||||
const windowsPath = "C:\\Users\\test\\image.jpg";
|
||||
|
||||
const result = extractFileInfo(windowsPath);
|
||||
|
||||
expect(result).toEqual({
|
||||
name: "image.jpg",
|
||||
type: "jpg",
|
||||
path: "C:\\Users\\test\\image.jpg",
|
||||
});
|
||||
});
|
||||
|
||||
it("should_handle_relative_Windows_path", () => {
|
||||
const windowsPath = "flow123\\subfolder\\document.pdf";
|
||||
|
||||
const result = extractFileInfo(windowsPath);
|
||||
|
||||
expect(result).toEqual({
|
||||
name: "document.pdf",
|
||||
type: "pdf",
|
||||
path: "flow123\\subfolder\\document.pdf",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Unix path handling", () => {
|
||||
it("should_extract_info_from_Unix_path", () => {
|
||||
const unixPath = "flow123/folder/image.png";
|
||||
|
||||
const result = extractFileInfo(unixPath);
|
||||
|
||||
expect(result).toEqual({
|
||||
name: "image.png",
|
||||
type: "png",
|
||||
path: "flow123/folder/image.png",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Object with properties", () => {
|
||||
it("should_extract_info_from_file_object", () => {
|
||||
const fileObj = {
|
||||
path: "flow123\\image.jpg",
|
||||
type: "image/jpeg",
|
||||
name: "my-image.jpg",
|
||||
};
|
||||
|
||||
const result = extractFileInfo(fileObj);
|
||||
|
||||
expect(result).toEqual({
|
||||
name: "my-image.jpg",
|
||||
type: "image/jpeg",
|
||||
path: "flow123\\image.jpg",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Edge cases", () => {
|
||||
it("should_handle_path_without_extension", () => {
|
||||
const result = extractFileInfo("just-filename");
|
||||
|
||||
expect(result).toEqual({
|
||||
name: "just-filename",
|
||||
type: "just-filename", // split(".").pop() returns the whole string when no dots
|
||||
path: "just-filename",
|
||||
});
|
||||
});
|
||||
|
||||
it("should_handle_empty_path", () => {
|
||||
const result = extractFileInfo("");
|
||||
|
||||
expect(result).toEqual({
|
||||
name: "",
|
||||
type: "",
|
||||
path: "",
|
||||
});
|
||||
});
|
||||
|
||||
it("should_handle_path_with_multiple_dots", () => {
|
||||
const result = extractFileInfo("file.backup.tar.gz");
|
||||
|
||||
expect(result).toEqual({
|
||||
name: "file.backup.tar.gz",
|
||||
type: "gz",
|
||||
path: "file.backup.tar.gz",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Cross-platform compatibility tests", () => {
|
||||
describe("Mixed environment scenarios", () => {
|
||||
it("should_consistently_handle_paths_from_Windows_and_Unix", () => {
|
||||
const windowsPath = "flow123\\images\\photo.jpg";
|
||||
const unixPath = "flow123/images/photo.jpg";
|
||||
|
||||
// Both should be treated as images
|
||||
expect(isImageFile(windowsPath)).toBe(true);
|
||||
expect(isImageFile(unixPath)).toBe(true);
|
||||
|
||||
// Both should extract the same filename
|
||||
expect(getFileDisplayName(windowsPath)).toBe("photo.jpg");
|
||||
expect(getFileDisplayName(unixPath)).toBe("photo.jpg");
|
||||
|
||||
// Both should generate equivalent URLs (normalized to forward slashes)
|
||||
const windowsUrl = getFilePreviewUrl(windowsPath);
|
||||
const unixUrl = getFilePreviewUrl(unixPath);
|
||||
expect(windowsUrl).toBe(unixUrl);
|
||||
});
|
||||
|
||||
it("should_handle_complex_nested_paths_consistently", () => {
|
||||
const complexWindowsPath =
|
||||
"project\\data\\2024\\Q1\\reports\\image.png";
|
||||
const complexUnixPath = "project/data/2024/Q1/reports/image.png";
|
||||
|
||||
expect(isImageFile(complexWindowsPath)).toBe(true);
|
||||
expect(isImageFile(complexUnixPath)).toBe(true);
|
||||
|
||||
const windowsUrl = getFilePreviewUrl(complexWindowsPath);
|
||||
const unixUrl = getFilePreviewUrl(complexUnixPath);
|
||||
expect(windowsUrl).toBe(unixUrl);
|
||||
});
|
||||
});
|
||||
|
||||
describe("CI environment compatibility", () => {
|
||||
it("should_work_in_Linux_CI_environment", () => {
|
||||
// Simulate Linux environment test
|
||||
const unixStylePath = "flow123/uploads/test-image.jpg";
|
||||
|
||||
expect(isImageFile(unixStylePath)).toBe(true);
|
||||
expect(getFileDisplayName(unixStylePath)).toBe("test-image.jpg");
|
||||
expect(getFilePreviewUrl(unixStylePath)).toContain(
|
||||
"flow123/uploads/test-image.jpg",
|
||||
);
|
||||
});
|
||||
|
||||
it("should_work_in_Windows_CI_environment", () => {
|
||||
// Simulate Windows environment test
|
||||
const windowsStylePath = "flow123\\uploads\\test-image.jpg";
|
||||
|
||||
expect(isImageFile(windowsStylePath)).toBe(true);
|
||||
expect(getFileDisplayName(windowsStylePath)).toBe("test-image.jpg");
|
||||
expect(getFilePreviewUrl(windowsStylePath)).toContain(
|
||||
"flow123/uploads/test-image.jpg",
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { ForwardedIconComponent } from "@/components/common/genericIconComponent";
|
||||
import Loading from "@/components/ui/loading";
|
||||
import { customGetAccessToken } from "@/customization/utils/custom-get-access-token";
|
||||
import { getFetchCredentials } from "@/customization/utils/get-fetch-credentials";
|
||||
import { cn } from "@/utils/utils";
|
||||
import {
|
||||
extractFileInfo,
|
||||
@@ -59,68 +57,27 @@ export default function FilePreviewDisplay({
|
||||
className,
|
||||
}: FilePreviewDisplayProps) {
|
||||
const [imageError, setImageError] = useState(false);
|
||||
const [blobUrl, setBlobUrl] = useState<string | null>(null);
|
||||
const [isLoadingBlob, setIsLoadingBlob] = useState(false);
|
||||
const previewUrl = getFilePreviewUrl(file);
|
||||
const fileInfo = extractFileInfo(file);
|
||||
|
||||
// Reset error state when file changes
|
||||
useEffect(() => {
|
||||
setImageError(false);
|
||||
setBlobUrl(null);
|
||||
}, [file]);
|
||||
|
||||
// For server file paths (not File objects), fetch and convert to blob URL
|
||||
// Cleanup blob URLs for File objects
|
||||
useEffect(() => {
|
||||
if (
|
||||
previewUrl &&
|
||||
!(file instanceof File) &&
|
||||
!blobUrl &&
|
||||
!isLoadingBlob &&
|
||||
!imageError
|
||||
) {
|
||||
setIsLoadingBlob(true);
|
||||
|
||||
// Prepare fetch options with credentials and auth headers
|
||||
const accessToken = customGetAccessToken();
|
||||
const fetchOptions: RequestInit = {
|
||||
credentials: getFetchCredentials(),
|
||||
};
|
||||
|
||||
if (accessToken) {
|
||||
fetchOptions.headers = {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
};
|
||||
}
|
||||
|
||||
fetch(previewUrl, fetchOptions)
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch image: ${response.status} ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
return response.blob();
|
||||
})
|
||||
.then((blob) => {
|
||||
const url = URL.createObjectURL(blob);
|
||||
setBlobUrl(url);
|
||||
setIsLoadingBlob(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("Failed to load image as blob:", err);
|
||||
setImageError(true);
|
||||
setIsLoadingBlob(false);
|
||||
});
|
||||
}
|
||||
|
||||
// Cleanup blob URL on unmount or when file changes
|
||||
return () => {
|
||||
if (blobUrl) {
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
// Only cleanup blob URLs from File objects
|
||||
if (
|
||||
file instanceof File &&
|
||||
previewUrl &&
|
||||
previewUrl.startsWith("blob:")
|
||||
) {
|
||||
URL.revokeObjectURL(previewUrl);
|
||||
}
|
||||
};
|
||||
}, [file, previewUrl, blobUrl, isLoadingBlob, imageError]);
|
||||
}, [file, previewUrl]);
|
||||
|
||||
// Compact variant (for input-wrapper)
|
||||
if (variant === "compact") {
|
||||
@@ -132,21 +89,17 @@ export default function FilePreviewDisplay({
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{loading || isLoadingBlob ? (
|
||||
{loading ? (
|
||||
<Loading className="h-4 w-4" />
|
||||
) : previewUrl &&
|
||||
(file instanceof File ? previewUrl : blobUrl) &&
|
||||
!imageError ? (
|
||||
) : previewUrl && !imageError ? (
|
||||
<img
|
||||
src={(file instanceof File ? previewUrl : blobUrl) ?? undefined}
|
||||
src={previewUrl}
|
||||
alt={fileInfo.name}
|
||||
className="h-full w-full rounded-md object-cover"
|
||||
crossOrigin={file instanceof File ? undefined : "use-credentials"}
|
||||
onError={() => {
|
||||
setImageError(true);
|
||||
console.error(
|
||||
"Failed to load image:",
|
||||
file instanceof File ? previewUrl : blobUrl,
|
||||
);
|
||||
console.error("Failed to load image:", previewUrl);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
@@ -176,21 +129,17 @@ export default function FilePreviewDisplay({
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{loading || isLoadingBlob ? (
|
||||
{loading ? (
|
||||
<Loading className="h-4 w-4" />
|
||||
) : previewUrl &&
|
||||
(file instanceof File ? previewUrl : blobUrl) &&
|
||||
!imageError ? (
|
||||
) : previewUrl && !imageError ? (
|
||||
<img
|
||||
src={(file instanceof File ? previewUrl : blobUrl) ?? undefined}
|
||||
src={previewUrl}
|
||||
alt={fileInfo.name}
|
||||
className="h-full w-full rounded-md object-cover"
|
||||
crossOrigin={file instanceof File ? undefined : "use-credentials"}
|
||||
onError={() => {
|
||||
setImageError(true);
|
||||
console.error(
|
||||
"Failed to load image:",
|
||||
file instanceof File ? previewUrl : blobUrl,
|
||||
);
|
||||
console.error("Failed to load image:", previewUrl);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -30,13 +30,17 @@ export function isImageFile(
|
||||
);
|
||||
} else if (typeof file === "string") {
|
||||
// File path string - extract extension
|
||||
const extension = file.split(".").pop()?.toLowerCase() || "";
|
||||
// Normalize Windows paths first
|
||||
const normalizedPath = file.replace(/\\/g, "/");
|
||||
const extension = normalizedPath.split(".").pop()?.toLowerCase() || "";
|
||||
return IMAGE_TYPES.has(extension);
|
||||
} else if (file && typeof file === "object") {
|
||||
// Object with type or path property
|
||||
// For server files, check path extension first (most reliable)
|
||||
if ("path" in file && file.path) {
|
||||
const extension = file.path.split(".").pop()?.toLowerCase() || "";
|
||||
// Normalize Windows paths first
|
||||
const normalizedPath = file.path.replace(/\\/g, "/");
|
||||
const extension = normalizedPath.split(".").pop()?.toLowerCase() || "";
|
||||
if (IMAGE_TYPES.has(extension)) {
|
||||
return true;
|
||||
}
|
||||
@@ -64,12 +68,14 @@ export function getFileDisplayName(
|
||||
if (file instanceof File) {
|
||||
return file.name;
|
||||
} else if (typeof file === "string") {
|
||||
// Extract name from path
|
||||
return file.split("/").pop() || file;
|
||||
// Extract name from path (normalize Windows paths first)
|
||||
const normalizedPath = file.replace(/\\/g, "/");
|
||||
return normalizedPath.split("/").pop() || file;
|
||||
} else if ("name" in file) {
|
||||
return file.name;
|
||||
} else if ("path" in file) {
|
||||
return file.path.split("/").pop() || file.path;
|
||||
const normalizedPath = file.path.replace(/\\/g, "/");
|
||||
return normalizedPath.split("/").pop() || file.path;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -108,9 +114,9 @@ export function getFilePreviewUrl(
|
||||
// Browser File object - create object URL
|
||||
return URL.createObjectURL(file);
|
||||
} else if (typeof file === "string") {
|
||||
// Server file path string - path format is "flow_id/filename"
|
||||
// Encode each path segment to handle spaces and special characters
|
||||
const path = file.trim();
|
||||
// Server file path string - path format is "flow_id/filename" or "flow_id\filename" on Windows
|
||||
// Normalize Windows backslashes to forward slashes
|
||||
const path = file.trim().replace(/\\/g, "/");
|
||||
if (!path) return null;
|
||||
const encodedPath = path
|
||||
.split("/")
|
||||
@@ -119,9 +125,9 @@ export function getFilePreviewUrl(
|
||||
// Explicitly use /api/v1/files/images/ prefix for server file paths
|
||||
return `${getBaseUrl()}files/images/${encodedPath}`;
|
||||
} else if ("path" in file) {
|
||||
// Server file path object - path format is "flow_id/filename"
|
||||
// Encode each path segment to handle spaces and special characters
|
||||
const path = file.path.trim();
|
||||
// Server file path object - path format is "flow_id/filename" or "flow_id\filename" on Windows
|
||||
// Normalize Windows backslashes to forward slashes
|
||||
const path = file.path.trim().replace(/\\/g, "/");
|
||||
if (!path) return null;
|
||||
const encodedPath = path
|
||||
.split("/")
|
||||
@@ -149,8 +155,9 @@ export function extractFileInfo(
|
||||
path: file.name, // For File objects, path is just the name
|
||||
};
|
||||
} else if (typeof file === "string") {
|
||||
const name = file.split("/").pop() || file;
|
||||
const type = file.split(".").pop() || "";
|
||||
const normalizedPath = file.replace(/\\/g, "/");
|
||||
const name = normalizedPath.split("/").pop() || file;
|
||||
const type = normalizedPath.split(".").pop() || "";
|
||||
return { name, type, path: file };
|
||||
} else {
|
||||
return {
|
||||
|
||||
@@ -29,6 +29,7 @@ export default function FileCard({
|
||||
|
||||
const fileWrapperClasses = getClasses(isHovered);
|
||||
|
||||
// Use direct URL like in v1.7.2 - the server handles authentication via cookies
|
||||
const imgSrc = `${getBaseUrl()}files/images/${path}`;
|
||||
|
||||
if (showFile) {
|
||||
@@ -45,6 +46,7 @@ export default function FileCard({
|
||||
src={imgSrc}
|
||||
alt="generated image"
|
||||
className="m-0 h-auto w-auto rounded-lg border border-border p-0 transition-all"
|
||||
crossOrigin="use-credentials"
|
||||
/>
|
||||
<DownloadButton
|
||||
isHovered={isHovered}
|
||||
|
||||
Reference in New Issue
Block a user