Skip to content

Commit 926dad4

Browse files
committed
Update ChatBar.tsx to allow for copy/paste and drag/drop file uploads (#7153)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## 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}` <!-- end pr-codex --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## 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. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 0639d0d commit 926dad4

File tree

1 file changed

+93
-30
lines changed
  • apps/dashboard/src/app/nebula-app/(app)/components

1 file changed

+93
-30
lines changed

apps/dashboard/src/app/nebula-app/(app)/components/ChatBar.tsx

Lines changed: 93 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ export type WalletMeta = {
5353

5454
const maxAllowedImagesPerMessage = 4;
5555

56+
function showSigninToUploadImagesToast() {
57+
toast.error("Sign in to upload images to Nebula", {
58+
position: "top-right",
59+
});
60+
}
61+
5662
export function ChatBar(props: {
5763
sendMessage: (message: NebulaUserMessage) => void;
5864
isChatStreaming: boolean;
@@ -76,6 +82,7 @@ export function ChatBar(props: {
7682
const [images, setImages] = useState<
7783
Array<{ file: File; b64: string | undefined }>
7884
>([]);
85+
const [isDragOver, setIsDragOver] = useState(false);
7986

8087
function handleSubmit(message: string) {
8188
const userMessage: NebulaUserMessage = {
@@ -104,10 +111,45 @@ export function ChatBar(props: {
104111
},
105112
});
106113

107-
async function handleImageUpload(images: File[]) {
114+
const supportedFileTypes = ["image/jpeg", "image/png", "image/webp"];
115+
116+
async function handleImageUpload(files: File[]) {
117+
const totalFiles = files.length + images.length;
118+
119+
if (totalFiles > maxAllowedImagesPerMessage) {
120+
toast.error(
121+
`You can only upload up to ${maxAllowedImagesPerMessage} images at a time`,
122+
{
123+
position: "top-right",
124+
},
125+
);
126+
return;
127+
}
128+
129+
const validFiles: File[] = [];
130+
131+
for (const file of files) {
132+
if (!supportedFileTypes.includes(file.type)) {
133+
toast.error("Unsupported file type", {
134+
description: `File: ${file.name}`,
135+
position: "top-right",
136+
});
137+
continue;
138+
}
139+
140+
if (file.size <= 5 * 1024 * 1024) {
141+
validFiles.push(file);
142+
} else {
143+
toast.error("Image is larger than 5MB", {
144+
description: `File: ${file.name}`,
145+
position: "top-right",
146+
});
147+
}
148+
}
149+
108150
try {
109151
const urls = await Promise.all(
110-
images.map(async (image) => {
152+
validFiles.map(async (image) => {
111153
const b64 = await uploadImageMutation.mutateAsync(image);
112154
return { file: image, b64: b64 };
113155
}),
@@ -126,9 +168,44 @@ export function ChatBar(props: {
126168
<DynamicHeight transition="height 200ms ease">
127169
<div
128170
className={cn(
129-
"overflow-hidden rounded-2xl border border-border bg-card",
171+
"overflow-hidden rounded-2xl border border-border bg-card transition-colors",
172+
isDragOver && "border-nebula-pink-foreground bg-nebula-pink/5",
130173
props.className,
131174
)}
175+
onDrop={(e) => {
176+
setIsDragOver(false);
177+
e.preventDefault();
178+
if (!props.allowImageUpload) {
179+
showSigninToUploadImagesToast();
180+
return;
181+
}
182+
const files = Array.from(e.dataTransfer.files);
183+
if (files.length > 0) handleImageUpload(files);
184+
}}
185+
onDragOver={(e) => {
186+
e.preventDefault();
187+
setIsDragOver(true);
188+
if (!props.allowImageUpload) {
189+
return;
190+
}
191+
}}
192+
onDragEnter={(e) => {
193+
e.preventDefault();
194+
setIsDragOver(true);
195+
if (!props.allowImageUpload) {
196+
return;
197+
}
198+
}}
199+
onDragLeave={(e) => {
200+
e.preventDefault();
201+
if (!props.allowImageUpload) {
202+
return;
203+
}
204+
// Only set drag over to false if we're leaving the container entirely
205+
if (!e.currentTarget.contains(e.relatedTarget as Node)) {
206+
setIsDragOver(false);
207+
}
208+
}}
132209
>
133210
{images.length > 0 && (
134211
<ImagePreview
@@ -146,6 +223,17 @@ export function ChatBar(props: {
146223
placeholder={props.placeholder}
147224
value={message}
148225
onChange={(e) => setMessage(e.target.value)}
226+
onPaste={(e) => {
227+
const files = Array.from(e.clipboardData.files);
228+
if (files.length > 0) {
229+
e.preventDefault();
230+
if (!props.allowImageUpload) {
231+
showSigninToUploadImagesToast();
232+
return;
233+
}
234+
handleImageUpload(files);
235+
}
236+
}}
149237
onKeyDown={(e) => {
150238
// ignore if shift key is pressed to allow entering new lines
151239
if (e.shiftKey) {
@@ -257,34 +345,9 @@ export function ChatBar(props: {
257345
<ImageUploadButton
258346
multiple
259347
value={undefined}
260-
accept="image/jpeg,image/png,image/webp"
348+
accept={supportedFileTypes.join(",")}
261349
onChange={(files) => {
262-
const totalFiles = files.length + images.length;
263-
264-
if (totalFiles > maxAllowedImagesPerMessage) {
265-
toast.error(
266-
`You can only upload up to ${maxAllowedImagesPerMessage} images at a time`,
267-
{
268-
position: "top-right",
269-
},
270-
);
271-
return;
272-
}
273-
274-
const validFiles: File[] = [];
275-
276-
for (const file of files) {
277-
if (file.size <= 5 * 1024 * 1024) {
278-
validFiles.push(file);
279-
} else {
280-
toast.error("Image is larger than 5MB", {
281-
description: `File: ${file.name}`,
282-
position: "top-right",
283-
});
284-
}
285-
}
286-
287-
handleImageUpload(validFiles);
350+
handleImageUpload(files);
288351
}}
289352
variant="ghost"
290353
className="!h-auto w-auto shrink-0 gap-2 p-2"

0 commit comments

Comments
 (0)