From 2e116b87018b76ef8e45a1105e1f3ff85d8bdf14 Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Thu, 16 Apr 2026 21:31:04 +0200 Subject: [PATCH] Add drag-and-drop functionality for chat attachments and update message sending logic --- src/App.jsx | 76 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 64 insertions(+), 12 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 6ae427c..04df045 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1457,9 +1457,52 @@ async function regenerateFromIndex(index, overrideUserText = null) { } }; + const handleChatDragEnter = (event) => { + if (activeSidebarMode !== 'chats' || !hasFilePayload(event)) return + event.preventDefault() + imageDragDepthRef.current += 1 + if (selectedModelSupportsVision && eventHasImageFiles(event)) { + setIsChatDragActive(true) + } + } + + const handleChatDragOver = (event) => { + if (activeSidebarMode !== 'chats' || !hasFilePayload(event)) return + event.preventDefault() + event.dataTransfer.dropEffect = selectedModelSupportsVision ? 'copy' : 'none' + if (selectedModelSupportsVision && eventHasImageFiles(event) && !isChatDragActive) { + setIsChatDragActive(true) + } + } + + const handleChatDragLeave = (event) => { + if (activeSidebarMode !== 'chats' || !hasFilePayload(event)) return + imageDragDepthRef.current = Math.max(0, imageDragDepthRef.current - 1) + if (imageDragDepthRef.current === 0) { + setIsChatDragActive(false) + } + } + + const handleChatDrop = async (event) => { + if (activeSidebarMode !== 'chats' || !hasFilePayload(event)) return + event.preventDefault() + imageDragDepthRef.current = 0 + setIsChatDragActive(false) + if (!selectedModelSupportsVision) { + return + } + await appendComposerImageFiles(event.dataTransfer?.files) + textareaRef.current?.focus() + } + async function sendMessage() { - if (isSending || !input.trim() || !model) return + const trimmedInput = input.trim() + if (isSending || (!trimmedInput && composerAttachments.length === 0) || !model) return + if (composerAttachments.length > 0 && !selectedModelSupportsVision) { + window.alert('The selected model does not support image inputs.') + return + } let targetSessionId = activeSessionId let isNewChat = false @@ -1473,7 +1516,13 @@ async function sendMessage() { isNewChat = currentSession && currentSession.name === "New Chat" && currentSession.messages.length === 0 } - const userMsg = { role: 'user', content: input.trim(), id: `msg-${Date.now()}-${Math.random()}` } + const outgoingAttachments = composerAttachments.map(({ id, ...attachment }) => ({ ...attachment })) + const userMsg = { + role: 'user', + content: trimmedInput, + attachments: outgoingAttachments, + id: `msg-${Date.now()}-${Math.random()}` + } justSentMessage.current = true lastSentSessionRef.current = targetSessionId setUserScrolledUp(targetSessionId, false) @@ -1491,25 +1540,26 @@ async function sendMessage() { ) ) setInput('') + setComposerAttachments([]) }) requestAnimationFrame(() => scrollToBottom('auto', targetSessionId)) const requestController = beginCancelableRequest(targetSessionId) try { let historyForSearch = [] - try { + if (userMsg.content) try { const existing = (chatSessions.find(s => s.session_id === targetSessionId)?.messages) || [] const lastFew = existing.slice(-8).map(m => ({ role: m.role, content: m.content || '' })) historyForSearch = [...lastFew, { role: 'user', content: userMsg.content }] } catch {} - let enrichedPrompt = userMsg.content + let enrichedPrompt = userMsg.content || null let citationSources = [] const contextBlocks = [] const selectedLibrary = getChatLibraryForSession(targetSessionId) - if (selectedLibrary?.states?.is_indexed) { + if (userMsg.content && selectedLibrary?.states?.is_indexed) { try { const localContext = await fetchLocalLibraryContext(selectedLibrary.slug, userMsg.content, requestController.signal) if (localContext.contextBlock) { @@ -1524,7 +1574,7 @@ async function sendMessage() { } } - if (webSearchEnabled) { + if (userMsg.content && webSearchEnabled) { try { const resp = await fetch(`${backendApiUrl}/websearch`, { method: 'POST', @@ -1553,7 +1603,7 @@ async function sendMessage() { } citationSources = [...new Set(citationSources)] - if (contextBlocks.length > 0) { + if (userMsg.content && contextBlocks.length > 0) { enrichedPrompt = `${userMsg.content}\n\n${contextBlocks.join('\n\n')}` } @@ -1578,9 +1628,10 @@ async function sendMessage() { session_id: targetSessionId, model, message: userMsg.content, - enriched_message: contextBlocks.length > 0 ? enrichedPrompt : null, + enriched_message: userMsg.content && contextBlocks.length > 0 ? enrichedPrompt : null, stream: true, - sources: citationSources || [] + sources: citationSources || [], + attachments: outgoingAttachments, }) }) if (!res.ok) throw new Error(`HTTP ${res.status}`) @@ -1641,9 +1692,10 @@ async function sendMessage() { session_id: targetSessionId, model, message: userMsg.content, - enriched_message: contextBlocks.length > 0 ? enrichedPrompt : null, + enriched_message: userMsg.content && contextBlocks.length > 0 ? enrichedPrompt : null, stream: false, - sources: citationSources || [] + sources: citationSources || [], + attachments: outgoingAttachments, }) }) if (!res.ok) throw new Error(`HTTP ${res.status}`) @@ -1688,7 +1740,7 @@ async function sendMessage() { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_id: targetSessionId, - message: userMsg.content, + message: userMsg.content || (outgoingAttachments.length > 0 ? 'Image attachment' : userMsg.content), model }) })