From fa1fb9cc4d4948535f9cde0c6bdb6bdcb2f50cd4 Mon Sep 17 00:00:00 2001 From: Will Chen Date: Mon, 23 Jun 2025 15:33:21 -0700 Subject: [PATCH] Allow pasting images (#472) Fixes #283 --- src/components/chat/ChatInput.tsx | 2 ++ src/components/chat/HomeChatInput.tsx | 2 ++ src/hooks/useAttachments.ts | 47 ++++++++++++++++++++++++++- 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/components/chat/ChatInput.tsx b/src/components/chat/ChatInput.tsx index f40d8b6..9f7761f 100644 --- a/src/components/chat/ChatInput.tsx +++ b/src/components/chat/ChatInput.tsx @@ -97,6 +97,7 @@ export function ChatInput({ chatId }: { chatId?: number }) { handleDragLeave, handleDrop, clearAttachments, + handlePaste, } = useAttachments(); // Use the hook to fetch the proposal @@ -309,6 +310,7 @@ export function ChatInput({ chatId }: { chatId?: number }) { value={inputValue} onChange={(e) => setInputValue(e.target.value)} onKeyPress={handleKeyPress} + onPaste={handlePaste} placeholder="Ask Dyad to build..." className="flex-1 p-2 focus:outline-none overflow-y-auto min-h-[40px] max-h-[200px]" style={{ resize: "none" }} diff --git a/src/components/chat/HomeChatInput.tsx b/src/components/chat/HomeChatInput.tsx index b4b0558..a313556 100644 --- a/src/components/chat/HomeChatInput.tsx +++ b/src/components/chat/HomeChatInput.tsx @@ -37,6 +37,7 @@ export function HomeChatInput({ handleDragLeave, handleDrop, clearAttachments, + handlePaste, } = useAttachments(); const adjustHeight = () => { @@ -103,6 +104,7 @@ export function HomeChatInput({ value={inputValue} onChange={(e) => setInputValue(e.target.value)} onKeyPress={handleKeyPress} + onPaste={handlePaste} placeholder="Ask Dyad to build..." className="flex-1 p-2 focus:outline-none overflow-y-auto min-h-[40px] max-h-[200px]" style={{ resize: "none" }} diff --git a/src/hooks/useAttachments.ts b/src/hooks/useAttachments.ts index 173c2ba..cdcbfb5 100644 --- a/src/hooks/useAttachments.ts +++ b/src/hooks/useAttachments.ts @@ -1,4 +1,4 @@ -import { useState, useRef } from "react"; +import React, { useState, useRef } from "react"; export function useAttachments() { const [attachments, setAttachments] = useState([]); @@ -45,6 +45,50 @@ export function useAttachments() { setAttachments([]); }; + const addAttachments = (files: File[]) => { + setAttachments((attachments) => [...attachments, ...files]); + }; + + const handlePaste = async (e: React.ClipboardEvent) => { + const clipboardData = e.clipboardData; + if (!clipboardData) return; + + const items = Array.from(clipboardData.items); + const imageItems = items.filter((item) => item.type.startsWith("image/")); + + if (imageItems.length > 0) { + e.preventDefault(); // Prevent default paste behavior for images + + const imageFiles: File[] = []; + // Generate base timestamp once to avoid collisions + const baseTimestamp = new Date().toISOString().replace(/[:.]/g, "-"); + + for (let i = 0; i < imageItems.length; i++) { + const item = imageItems[i]; + const file = item.getAsFile(); + if (file) { + // Create a more descriptive filename with timestamp and counter + const extension = file.type.split("/")[1] || "png"; + const filename = + imageItems.length === 1 + ? `pasted-image-${baseTimestamp}.${extension}` + : `pasted-image-${baseTimestamp}-${i + 1}.${extension}`; + + const newFile = new File([file], filename, { + type: file.type, + }); + imageFiles.push(newFile); + } + } + + if (imageFiles.length > 0) { + addAttachments(imageFiles); + // Show a brief toast or indication that image was pasted + console.log(`Pasted ${imageFiles.length} image(s) from clipboard`); + } + } + }; + return { attachments, fileInputRef, @@ -56,5 +100,6 @@ export function useAttachments() { handleDragLeave, handleDrop, clearAttachments, + handlePaste, }; }