Skip to content

Commit 967f3fa

Browse files
authored
Merge pull request #1 from wuhaolei455/main
feat: support drag and drop file upload
2 parents ee1c084 + 4154277 commit 967f3fa

File tree

1 file changed

+83
-1
lines changed

1 file changed

+83
-1
lines changed

src/components/thread/index.tsx

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ export function Thread() {
127127

128128
const lastError = useRef<string | undefined>(undefined);
129129

130+
const dropRef = useRef<HTMLDivElement>(null);
131+
const [dragging, setDragging] = useState(false);
132+
const formats = ["jpg", "png", "gif", "webp", "bmp", "jpeg", "tiff"];
133+
130134
useEffect(() => {
131135
if (!stream.error) {
132136
lastError.current = undefined;
@@ -247,6 +251,81 @@ export function Thread() {
247251
(m) => m.type === "ai" || m.type === "tool",
248252
);
249253

254+
useEffect(() => {
255+
if (!dropRef.current) return;
256+
257+
const handleDragOver = (e: DragEvent) => {
258+
e.preventDefault();
259+
e.stopPropagation();
260+
};
261+
262+
const handleDrop = async (e: DragEvent) => {
263+
e.preventDefault();
264+
e.stopPropagation();
265+
setDragging(false);
266+
267+
if (!e.dataTransfer) return;
268+
269+
const files = Array.from(e.dataTransfer.files);
270+
271+
if (formats && files.some(file =>
272+
!formats.some(format =>
273+
file.name.toLowerCase().endsWith(format.toLowerCase())
274+
)
275+
)) {
276+
toast.error("Invalid file type. Please upload an image. supported formats: " + formats.join(", "));
277+
return;
278+
}
279+
280+
281+
if (files.length) {
282+
const imageUrls = await Promise.all(
283+
Array.from(files).map((file) => {
284+
return new Promise<MessageContentImageUrl>((resolve) => {
285+
const reader = new FileReader();
286+
reader.onloadend = () => {
287+
resolve({
288+
type: "image_url",
289+
image_url: {
290+
url: reader.result as string,
291+
},
292+
});
293+
};
294+
reader.readAsDataURL(file);
295+
});
296+
}),
297+
);
298+
setImageUrlList([...imageUrlList, ...imageUrls]);
299+
}
300+
};
301+
302+
const handleDragEnter = (e: DragEvent) => {
303+
e.preventDefault();
304+
e.stopPropagation();
305+
setDragging(true);
306+
};
307+
308+
const handleDragLeave = (e: DragEvent) => {
309+
e.preventDefault();
310+
e.stopPropagation();
311+
setDragging(false);
312+
};
313+
314+
const element = dropRef.current;
315+
element.addEventListener("dragover", handleDragOver);
316+
element.addEventListener("drop", handleDrop);
317+
element.addEventListener("dragenter", handleDragEnter);
318+
element.addEventListener("dragleave", handleDragLeave);
319+
320+
return () => {
321+
element.removeEventListener("dragover", handleDragOver);
322+
element.removeEventListener("drop", handleDrop);
323+
element.removeEventListener("dragenter", handleDragEnter);
324+
element.removeEventListener("dragleave", handleDragLeave);
325+
};
326+
});
327+
328+
250329
return (
251330
<div className="flex h-screen w-full overflow-hidden">
252331
<div className="relative hidden lg:flex">
@@ -430,7 +509,10 @@ export function Thread() {
430509

431510
<ScrollToBottom className="animate-in fade-in-0 zoom-in-95 absolute bottom-full left-1/2 mb-4 -translate-x-1/2" />
432511

433-
<div className="bg-muted relative z-10 mx-auto mb-8 w-full max-w-3xl rounded-2xl border shadow-xs">
512+
<div
513+
ref={dropRef}
514+
className="bg-muted relative z-10 mx-auto mb-8 w-full max-w-3xl rounded-2xl border shadow-xs"
515+
>
434516
<form
435517
onSubmit={handleSubmit}
436518
className="mx-auto grid max-w-3xl grid-rows-[1fr_auto] gap-2"

0 commit comments

Comments
 (0)