diff --git a/src/frontend/src/components/core/playgroundComponent/chat-view/chat-messages/components/__tests__/chat-message.test.tsx b/src/frontend/src/components/core/playgroundComponent/chat-view/chat-messages/components/__tests__/chat-message.test.tsx
index 9358d91959..efbf4368eb 100644
--- a/src/frontend/src/components/core/playgroundComponent/chat-view/chat-messages/components/__tests__/chat-message.test.tsx
+++ b/src/frontend/src/components/core/playgroundComponent/chat-view/chat-messages/components/__tests__/chat-message.test.tsx
@@ -180,13 +180,13 @@ describe("ChatMessage Component", () => {
chat: {
...mockChat,
message: "",
- files: ["/path/to/file.jpg"],
+ files: ["/path/to/file.pdf"],
},
};
render();
- // 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", () => {
diff --git a/src/frontend/src/components/core/playgroundComponent/chat-view/utils/__tests__/file-utils.test.ts b/src/frontend/src/components/core/playgroundComponent/chat-view/utils/__tests__/file-utils.test.ts
new file mode 100644
index 0000000000..1fd0151d4d
--- /dev/null
+++ b/src/frontend/src/components/core/playgroundComponent/chat-view/utils/__tests__/file-utils.test.ts
@@ -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;
+
+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",
+ );
+ });
+ });
+ });
+});
diff --git a/src/frontend/src/components/core/playgroundComponent/chat-view/utils/file-preview-display.tsx b/src/frontend/src/components/core/playgroundComponent/chat-view/utils/file-preview-display.tsx
index 9cf5ead125..c8df58c1d8 100644
--- a/src/frontend/src/components/core/playgroundComponent/chat-view/utils/file-preview-display.tsx
+++ b/src/frontend/src/components/core/playgroundComponent/chat-view/utils/file-preview-display.tsx
@@ -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(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 ? (
- ) : previewUrl &&
- (file instanceof File ? previewUrl : blobUrl) &&
- !imageError ? (
+ ) : previewUrl && !imageError ? (
{
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 ? (
- ) : previewUrl &&
- (file instanceof File ? previewUrl : blobUrl) &&
- !imageError ? (
+ ) : previewUrl && !imageError ? (
{
setImageError(true);
- console.error(
- "Failed to load image:",
- file instanceof File ? previewUrl : blobUrl,
- );
+ console.error("Failed to load image:", previewUrl);
}}
/>
) : (
diff --git a/src/frontend/src/components/core/playgroundComponent/chat-view/utils/file-utils.ts b/src/frontend/src/components/core/playgroundComponent/chat-view/utils/file-utils.ts
index a6dd42f1ea..f8b1e6399b 100644
--- a/src/frontend/src/components/core/playgroundComponent/chat-view/utils/file-utils.ts
+++ b/src/frontend/src/components/core/playgroundComponent/chat-view/utils/file-utils.ts
@@ -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 {
diff --git a/src/frontend/src/modals/IOModal/components/chatView/fileComponent/components/file-card.tsx b/src/frontend/src/modals/IOModal/components/chatView/fileComponent/components/file-card.tsx
index c92cc0c9a4..0391049996 100644
--- a/src/frontend/src/modals/IOModal/components/chatView/fileComponent/components/file-card.tsx
+++ b/src/frontend/src/modals/IOModal/components/chatView/fileComponent/components/file-card.tsx
@@ -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"
/>