From 7eeba5f17c343bb82c10e8c647a8dcbd7a2a0eab Mon Sep 17 00:00:00 2001 From: Kacper Date: Tue, 16 Dec 2025 02:49:26 +0100 Subject: [PATCH 1/3] feat: add image paste functionality to DescriptionImageDropZone component - Implemented handlePaste function to process images from clipboard across all OS. - Updated the component to handle pasted images and prevent default paste behavior. - Enhanced user instructions to include pasting images in the UI. Added a utility function to simulate pasting images in tests, ensuring cross-platform compatibility. --- .../ui/description-image-dropzone.tsx | 49 ++++++++++++++++++- apps/app/tests/utils/files/drag-drop.ts | 44 +++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/apps/app/src/components/ui/description-image-dropzone.tsx b/apps/app/src/components/ui/description-image-dropzone.tsx index df685082..4d9df8a2 100644 --- a/apps/app/src/components/ui/description-image-dropzone.tsx +++ b/apps/app/src/components/ui/description-image-dropzone.tsx @@ -268,6 +268,52 @@ export function DescriptionImageDropZone({ [images, onImagesChange] ); + // Handle paste events to detect and process images from clipboard + // Works across all OS (Windows, Linux, macOS) + const handlePaste = useCallback( + (e: React.ClipboardEvent) => { + if (disabled || isProcessing) return; + + const clipboardItems = e.clipboardData?.items; + if (!clipboardItems) return; + + const imageFiles: File[] = []; + + // Iterate through clipboard items to find images + for (let i = 0; i < clipboardItems.length; i++) { + const item = clipboardItems[i]; + + // Check if the item is an image + if (item.type.startsWith("image/")) { + const file = item.getAsFile(); + if (file) { + // Generate a filename for pasted images since they don't have one + const extension = item.type.split("/")[1] || "png"; + const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); + const renamedFile = new File( + [file], + `pasted-image-${timestamp}.${extension}`, + { type: file.type } + ); + imageFiles.push(renamedFile); + } + } + } + + // If we found images, process them and prevent default paste behavior + if (imageFiles.length > 0) { + e.preventDefault(); + + // Create a FileList-like object from the array + const dataTransfer = new DataTransfer(); + imageFiles.forEach((file) => dataTransfer.items.add(file)); + processFiles(dataTransfer.files); + } + // If no images found, let the default paste behavior happen (paste text) + }, + [disabled, isProcessing, processFiles] + ); + return (
{/* Hidden file input */} @@ -313,6 +359,7 @@ export function DescriptionImageDropZone({ placeholder={placeholder} value={value} onChange={(e) => onChange(e.target.value)} + onPaste={handlePaste} disabled={disabled} autoFocus={autoFocus} aria-invalid={error} @@ -326,7 +373,7 @@ export function DescriptionImageDropZone({ {/* Hint text */}

- Drag and drop images here or{" "} + Paste, drag and drop images, or{" "}

{/* Remove button */} - {!disabled && ( + {!disabled && image.id && ( + {image.id && ( + + )} ))} diff --git a/apps/app/src/store/app-store.ts b/apps/app/src/store/app-store.ts index 3c2aeaa4..6992dc2d 100644 --- a/apps/app/src/store/app-store.ts +++ b/apps/app/src/store/app-store.ts @@ -210,11 +210,11 @@ export const DEFAULT_KEYBOARD_SHORTCUTS: KeyboardShortcuts = { }; export interface ImageAttachment { - id: string; + id?: string; // Optional - may not be present in messages loaded from server data: string; // base64 encoded image data mimeType: string; // e.g., "image/png", "image/jpeg" filename: string; - size: number; // file size in bytes + size?: number; // file size in bytes - optional for messages from server } export interface ChatMessage { diff --git a/apps/app/src/types/electron.d.ts b/apps/app/src/types/electron.d.ts index 9f112568..5eb152d0 100644 --- a/apps/app/src/types/electron.d.ts +++ b/apps/app/src/types/electron.d.ts @@ -3,11 +3,11 @@ */ export interface ImageAttachment { - id: string; + id?: string; // Optional - may not be present in messages loaded from server data: string; // base64 encoded image data mimeType: string; // e.g., "image/png", "image/jpeg" filename: string; - size: number; // file size in bytes + size?: number; // file size in bytes - optional for messages from server } export interface Message { From c8c05efb8d90e075c1ad67eae58f2f2bdb1c4f94 Mon Sep 17 00:00:00 2001 From: Kacper Date: Tue, 16 Dec 2025 03:18:10 +0100 Subject: [PATCH 3/3] fix: remove onClick handler causing wierd issue on windows that try to open microsoft store --- apps/app/src/components/views/agent-view.tsx | 137 ++++++++++++------- 1 file changed, 85 insertions(+), 52 deletions(-) diff --git a/apps/app/src/components/views/agent-view.tsx b/apps/app/src/components/views/agent-view.tsx index 3a2ff09b..8386554d 100644 --- a/apps/app/src/components/views/agent-view.tsx +++ b/apps/app/src/components/views/agent-view.tsx @@ -31,7 +31,8 @@ import { } from "@/hooks/use-keyboard-shortcuts"; export function AgentView() { - const { currentProject, setLastSelectedSession, getLastSelectedSession } = useAppStore(); + const { currentProject, setLastSelectedSession, getLastSelectedSession } = + useAppStore(); const shortcuts = useKeyboardShortcutsConfig(); const [input, setInput] = useState(""); const [selectedImages, setSelectedImages] = useState([]); @@ -72,13 +73,16 @@ export function AgentView() { }); // Handle session selection with persistence - const handleSelectSession = useCallback((sessionId: string | null) => { - setCurrentSessionId(sessionId); - // Persist the selection for this project - if (currentProject?.path) { - setLastSelectedSession(currentProject.path, sessionId); - } - }, [currentProject?.path, setLastSelectedSession]); + const handleSelectSession = useCallback( + (sessionId: string | null) => { + setCurrentSessionId(sessionId); + // Persist the selection for this project + if (currentProject?.path) { + setLastSelectedSession(currentProject.path, sessionId); + } + }, + [currentProject?.path, setLastSelectedSession] + ); // Restore last selected session when switching to Agent view or when project changes useEffect(() => { @@ -95,7 +99,10 @@ export function AgentView() { const lastSessionId = getLastSelectedSession(currentProject.path); if (lastSessionId) { - console.log("[AgentView] Restoring last selected session:", lastSessionId); + console.log( + "[AgentView] Restoring last selected session:", + lastSessionId + ); setCurrentSessionId(lastSessionId); } }, [currentProject?.path, getLastSelectedSession]); @@ -418,7 +425,9 @@ export function AgentView() {
-

No Project Selected

+

+ No Project Selected +

Open or create a project to start working with the AI agent.

@@ -480,7 +489,9 @@ export function AgentView() {
-

AI Agent

+

+ AI Agent +

{currentProject.name} {currentSessionId && !isConnected && " - Connecting..."} @@ -497,7 +508,9 @@ export function AgentView() {

)} {agentError && ( - {agentError} + + {agentError} + )} {currentSessionId && messages.length > 0 && (