diff --git a/web/app/ai-chat/page.js b/web/app/ai-chat/page.js index 09ac5ba3..ce8895b8 100644 --- a/web/app/ai-chat/page.js +++ b/web/app/ai-chat/page.js @@ -8,6 +8,66 @@ import { createStompClient } from "@/lib/chatSocket"; const API_BASE = ""; +function isImageFilename(name) { + return /\.(jpe?g|png|gif|webp|bmp|svg)$/i.test(name || ""); +} + +function AttachmentPreview({ url, name, token }) { + const [blobUrl, setBlobUrl] = useState(null); + const isImage = isImageFilename(name); + + useEffect(() => { + if (!url || !token) return; + let objectUrl; + fetch(url, { headers: { Authorization: `Bearer ${token}` } }) + .then((r) => (r.ok ? r.blob() : null)) + .then((blob) => { + if (blob) { + objectUrl = URL.createObjectURL(blob); + setBlobUrl(objectUrl); + } + }) + .catch(() => {}); + return () => { if (objectUrl) URL.revokeObjectURL(objectUrl); }; + }, [url, token]); + + if (isImage) { + return ( +
+ {blobUrl ? ( + {name window.open(blobUrl, "_blank")} + /> + ) : ( + 📎 Loading image… + )} +
+ ); + } + + return ( +
+ { + e.preventDefault(); + if (!blobUrl) return; + const a = document.createElement("a"); + a.href = blobUrl; + a.download = name || "attachment"; + a.click(); + }} + > + 📎 {name || "Attachment"} + +
+ ); +} + function AiChatPage() { const { user, token, loading: authLoading } = useAuth(); const router = useRouter(); @@ -501,16 +561,7 @@ function AiChatPage() { ))} {msg.attachmentUrl && ( -
- - 📎 {msg.attachmentName || "Attachment"} - -
+ )}
{msg.timestamp ? new Date(msg.timestamp).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : ""} diff --git a/web/app/chat/page.js b/web/app/chat/page.js index f5b8432f..1957fe78 100644 --- a/web/app/chat/page.js +++ b/web/app/chat/page.js @@ -8,6 +8,66 @@ import { createStompClient } from "@/lib/chatSocket"; const API_BASE = ""; +function isImageFilename(name) { + return /\.(jpe?g|png|gif|webp|bmp|svg)$/i.test(name || ""); +} + +function AttachmentPreview({ url, name, token }) { + const [blobUrl, setBlobUrl] = useState(null); + const isImage = isImageFilename(name); + + useEffect(() => { + if (!url || !token) return; + let objectUrl; + fetch(url, { headers: { Authorization: `Bearer ${token}` } }) + .then((r) => (r.ok ? r.blob() : null)) + .then((blob) => { + if (blob) { + objectUrl = URL.createObjectURL(blob); + setBlobUrl(objectUrl); + } + }) + .catch(() => {}); + return () => { if (objectUrl) URL.revokeObjectURL(objectUrl); }; + }, [url, token]); + + if (isImage) { + return ( +
+ {blobUrl ? ( + {name window.open(blobUrl, "_blank")} + /> + ) : ( + 📎 Loading image… + )} +
+ ); + } + + return ( +
+ { + e.preventDefault(); + if (!blobUrl) return; + const a = document.createElement("a"); + a.href = blobUrl; + a.download = name || "attachment"; + a.click(); + }} + > + 📎 {name || "Attachment"} + +
+ ); +} + function ChatPage() { const { user, token, loading: authLoading } = useAuth(); const router = useRouter(); @@ -532,16 +592,7 @@ function ChatPage() { ))} {msg.attachmentUrl && ( -
- - 📎 {msg.attachmentName || "Attachment"} - -
+ )}
{msg.timestamp ? new Date(msg.timestamp).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : ""} diff --git a/web/app/globals.css b/web/app/globals.css index f54ed79b..eab50e73 100644 --- a/web/app/globals.css +++ b/web/app/globals.css @@ -683,8 +683,8 @@ body { } .info-page { - min-height: 100vh; background: linear-gradient(to bottom, #f9f9f9, #ffffff); + padding-bottom: 4rem; } .info-hero {