From 926dad4556dc247f0afe51cb9b1f22b758d724f4 Mon Sep 17 00:00:00 2001 From: pinkhoodie <36429880+pinkhoodie@users.noreply.github.com> Date: Fri, 30 May 2025 13:46:07 +0000 Subject: [PATCH] Update ChatBar.tsx to allow for copy/paste and drag/drop file uploads (#7153) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ## PR-Codex overview This PR enhances the `ChatBar` component by adding image upload validation, drag-and-drop functionality, and user feedback through toast notifications. It improves user experience by ensuring only valid images are uploaded and prompts users to sign in if they attempt to upload without permission. ### Detailed summary - Added `showSigninToUploadImagesToast` function for user notifications. - Introduced `isDragOver` state for drag-and-drop feedback. - Updated `handleImageUpload` to validate file types and sizes. - Implemented drag-and-drop event handlers for image uploads. - Added clipboard paste support for image uploads. - Modified `ImageUploadButton` to use validated files directly. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` ## Summary by CodeRabbit - **New Features** - Added support for image uploads via drag-and-drop and clipboard paste in the chat bar. - Enhanced visual feedback during drag-and-drop image uploads. - Added prompts for users to sign in when attempting image uploads without authorization. - **Bug Fixes** - Improved validation for image uploads, including file size and total image count limits. --- .../nebula-app/(app)/components/ChatBar.tsx | 123 +++++++++++++----- 1 file changed, 93 insertions(+), 30 deletions(-) diff --git a/apps/dashboard/src/app/nebula-app/(app)/components/ChatBar.tsx b/apps/dashboard/src/app/nebula-app/(app)/components/ChatBar.tsx index bbf73828657..bb056f78902 100644 --- a/apps/dashboard/src/app/nebula-app/(app)/components/ChatBar.tsx +++ b/apps/dashboard/src/app/nebula-app/(app)/components/ChatBar.tsx @@ -53,6 +53,12 @@ export type WalletMeta = { const maxAllowedImagesPerMessage = 4; +function showSigninToUploadImagesToast() { + toast.error("Sign in to upload images to Nebula", { + position: "top-right", + }); +} + export function ChatBar(props: { sendMessage: (message: NebulaUserMessage) => void; isChatStreaming: boolean; @@ -76,6 +82,7 @@ export function ChatBar(props: { const [images, setImages] = useState< Array<{ file: File; b64: string | undefined }> >([]); + const [isDragOver, setIsDragOver] = useState(false); function handleSubmit(message: string) { const userMessage: NebulaUserMessage = { @@ -104,10 +111,45 @@ export function ChatBar(props: { }, }); - async function handleImageUpload(images: File[]) { + const supportedFileTypes = ["image/jpeg", "image/png", "image/webp"]; + + async function handleImageUpload(files: File[]) { + const totalFiles = files.length + images.length; + + if (totalFiles > maxAllowedImagesPerMessage) { + toast.error( + `You can only upload up to ${maxAllowedImagesPerMessage} images at a time`, + { + position: "top-right", + }, + ); + return; + } + + const validFiles: File[] = []; + + for (const file of files) { + if (!supportedFileTypes.includes(file.type)) { + toast.error("Unsupported file type", { + description: `File: ${file.name}`, + position: "top-right", + }); + continue; + } + + if (file.size <= 5 * 1024 * 1024) { + validFiles.push(file); + } else { + toast.error("Image is larger than 5MB", { + description: `File: ${file.name}`, + position: "top-right", + }); + } + } + try { const urls = await Promise.all( - images.map(async (image) => { + validFiles.map(async (image) => { const b64 = await uploadImageMutation.mutateAsync(image); return { file: image, b64: b64 }; }), @@ -126,9 +168,44 @@ export function ChatBar(props: {
{ + setIsDragOver(false); + e.preventDefault(); + if (!props.allowImageUpload) { + showSigninToUploadImagesToast(); + return; + } + const files = Array.from(e.dataTransfer.files); + if (files.length > 0) handleImageUpload(files); + }} + onDragOver={(e) => { + e.preventDefault(); + setIsDragOver(true); + if (!props.allowImageUpload) { + return; + } + }} + onDragEnter={(e) => { + e.preventDefault(); + setIsDragOver(true); + if (!props.allowImageUpload) { + return; + } + }} + onDragLeave={(e) => { + e.preventDefault(); + if (!props.allowImageUpload) { + return; + } + // Only set drag over to false if we're leaving the container entirely + if (!e.currentTarget.contains(e.relatedTarget as Node)) { + setIsDragOver(false); + } + }} > {images.length > 0 && ( setMessage(e.target.value)} + onPaste={(e) => { + const files = Array.from(e.clipboardData.files); + if (files.length > 0) { + e.preventDefault(); + if (!props.allowImageUpload) { + showSigninToUploadImagesToast(); + return; + } + handleImageUpload(files); + } + }} onKeyDown={(e) => { // ignore if shift key is pressed to allow entering new lines if (e.shiftKey) { @@ -257,34 +345,9 @@ export function ChatBar(props: { { - const totalFiles = files.length + images.length; - - if (totalFiles > maxAllowedImagesPerMessage) { - toast.error( - `You can only upload up to ${maxAllowedImagesPerMessage} images at a time`, - { - position: "top-right", - }, - ); - return; - } - - const validFiles: File[] = []; - - for (const file of files) { - if (file.size <= 5 * 1024 * 1024) { - validFiles.push(file); - } else { - toast.error("Image is larger than 5MB", { - description: `File: ${file.name}`, - position: "top-right", - }); - } - } - - handleImageUpload(validFiles); + handleImageUpload(files); }} variant="ghost" className="!h-auto w-auto shrink-0 gap-2 p-2"