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 && (
)}
+
+ {/* Display attached images for user messages */}
+ {message.role === "user" && message.images && message.images.length > 0 && (
+
+
+
+ {message.images.length} image{message.images.length > 1 ? 's' : ''} attached
+
+
+ {message.images.map((image, index) => {
+ // Construct proper data URL from base64 data and mime type
+ const dataUrl = image.data.startsWith('data:')
+ ? image.data
+ : `data:${image.mimeType || 'image/png'};base64,${image.data}`;
+ return (
+
+

{
+ e.stopPropagation();
+ // Open image in a larger view (could be enhanced with a modal)
+ window.open(dataUrl, '_blank');
+ }}
+ />
+
+ {image.filename || `Image ${index + 1}`}
+
+
+ );
+ })}
+
+
+ )}
+
{image.filename}
-
- {formatFileSize(image.size)}
-
+ {image.size !== undefined && (
+
+ {formatFileSize(image.size)}
+
+ )}
{/* Remove button */}
-
+ {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 && (