diff --git a/frontend-web/src/components/workspace/messages/message-list-item.tsx b/frontend-web/src/components/workspace/messages/message-list-item.tsx
index 4daa04ce..70e42054 100644
--- a/frontend-web/src/components/workspace/messages/message-list-item.tsx
+++ b/frontend-web/src/components/workspace/messages/message-list-item.tsx
@@ -5,6 +5,7 @@ import {
useCallback,
useContext,
useMemo,
+ useState,
type AnchorHTMLAttributes,
type ImgHTMLAttributes,
} from "react";
@@ -24,6 +25,10 @@ import {
} from "@/components/ai-elements/reasoning";
import { Task, TaskTrigger } from "@/components/ai-elements/task";
import { Badge } from "@/components/ui/badge";
+import {
+ UploadedFilePreviewDialog,
+ useAuthedObjectUrl,
+} from "@/components/workspace/messages/uploaded-file-preview-dialog";
import { resolveArtifactURL } from "@/core/artifacts/utils";
import { useChatAskTimes } from "@/core/chat-timestamps";
import { getBackendBaseURL } from "@/core/config";
@@ -466,8 +471,17 @@ function RichFileCard({
}) {
const { t } = useI18n();
const resolveArtifact = useArtifactResolver(threadId);
+ const [previewOpen, setPreviewOpen] = useState(false);
const isUploading = file.status === "uploading";
const isImage = isImageFile(file.filename);
+ const fileUrl = file.path ? resolveArtifact(file.path) : null;
+ // Thumbnails must be fetched with auth: the artifact route is gated and a
+ // plain sends no Authorization header → 401 (broken image). Fetch
+ // the bytes via apiFetch and display the resulting object URL instead.
+ const { objectUrl: thumbUrl, loading: thumbLoading } = useAuthedObjectUrl(
+ fileUrl,
+ isImage && !isUploading && !!fileUrl,
+ );
if (isUploading) {
return (
@@ -496,50 +510,81 @@ function RichFileCard({
);
}
- if (!file.path) return null;
+ if (!fileUrl) return null;
- const fileUrl = resolveArtifact(file.path);
+ const preview = (
+
-
+ <>
+
+ {preview}
+ >
);
}
return (
-