From 5f1cb67ffc114867d01f3ee9a9fe8eea4a5b7481 Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Mon, 11 Aug 2025 22:19:35 +0700 Subject: [PATCH 01/21] feat: enable attachment UI --- src-tauri/tauri.conf.json | 3 +- web-app/src/containers/ChatInput.tsx | 69 ++++++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index c2e37e483..c5dcb9c1b 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -35,7 +35,8 @@ "effects": ["fullScreenUI", "mica", "tabbed", "blur", "acrylic"], "state": "active", "radius": 8 - } + }, + "dragDropEnabled": false } ], "security": { diff --git a/web-app/src/containers/ChatInput.tsx b/web-app/src/containers/ChatInput.tsx index 59cdaa3cd..dc3935ff6 100644 --- a/web-app/src/containers/ChatInput.tsx +++ b/web-app/src/containers/ChatInput.tsx @@ -70,6 +70,7 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => { }> >([]) const [connectedServers, setConnectedServers] = useState([]) + const [isDragOver, setIsDragOver] = useState(false) // Check for connected MCP servers useEffect(() => { @@ -281,6 +282,54 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => { } } + const handleDragEnter = (e: React.DragEvent) => { + e.preventDefault() + e.stopPropagation() + setIsDragOver(true) + } + + const handleDragLeave = (e: React.DragEvent) => { + e.preventDefault() + e.stopPropagation() + // Only set dragOver to false if we're leaving the drop zone entirely + // In Tauri, relatedTarget can be null, so we need to handle that case + const relatedTarget = e.relatedTarget as Node | null + if (!relatedTarget || !e.currentTarget.contains(relatedTarget)) { + setIsDragOver(false) + } + } + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault() + e.stopPropagation() + // Ensure drag state is maintained during drag over + setIsDragOver(true) + } + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault() + e.stopPropagation() + setIsDragOver(false) + + // Check if dataTransfer exists (it might not in some Tauri scenarios) + if (!e.dataTransfer) { + console.warn('No dataTransfer available in drop event') + return + } + + const files = e.dataTransfer.files + if (files && files.length > 0) { + // Create a synthetic event to reuse existing file handling logic + const syntheticEvent = { + target: { + files: files, + }, + } as React.ChangeEvent + + handleFileChange(syntheticEvent) + } + } + return (
@@ -305,8 +354,13 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => {
{uploadedFiles.length > 0 && (
@@ -372,8 +426,14 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => { }} onKeyDown={(e) => { // e.keyCode 229 is for IME input with Safari - const isComposing = e.nativeEvent.isComposing || e.keyCode === 229; - if (e.key === 'Enter' && !e.shiftKey && prompt.trim() && !isComposing) { + const isComposing = + e.nativeEvent.isComposing || e.keyCode === 229 + if ( + e.key === 'Enter' && + !e.shiftKey && + prompt.trim() && + !isComposing + ) { e.preventDefault() // Submit the message when Enter is pressed without Shift handleSendMesage(prompt) @@ -414,7 +474,7 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => { )} {/* File attachment - always available */}
@@ -422,6 +482,7 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => { type="file" ref={fileInputRef} className="hidden" + multiple onChange={handleFileChange} />
From e9bd0f0bec4180be6dcbd76cb1fc973cf1b7a278 Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Mon, 11 Aug 2025 22:22:18 +0700 Subject: [PATCH 02/21] chore: update alignment --- web-app/src/containers/ChatInput.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web-app/src/containers/ChatInput.tsx b/web-app/src/containers/ChatInput.tsx index dc3935ff6..0cd9c523e 100644 --- a/web-app/src/containers/ChatInput.tsx +++ b/web-app/src/containers/ChatInput.tsx @@ -460,7 +460,7 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => {
@@ -474,7 +474,7 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => { )} {/* File attachment - always available */}
@@ -645,6 +645,7 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => {
+ {message && (
From cef3e122ff284e25edb7d5b339bc7456113a4dbc Mon Sep 17 00:00:00 2001 From: Faisal Amir Date: Thu, 14 Aug 2025 13:39:38 +0700 Subject: [PATCH 03/21] chore: send attachment file when send message --- web-app/src/containers/ChatInput.tsx | 17 ++- web-app/src/containers/ThreadContent.tsx | 152 ++++++++++++++++++++--- web-app/src/hooks/useChat.ts | 16 ++- web-app/src/lib/completion.ts | 68 +++++++--- web-app/src/lib/messages.ts | 117 ++++++++++++++--- 5 files changed, 316 insertions(+), 54 deletions(-) diff --git a/web-app/src/containers/ChatInput.tsx b/web-app/src/containers/ChatInput.tsx index 0cd9c523e..e711804a8 100644 --- a/web-app/src/containers/ChatInput.tsx +++ b/web-app/src/containers/ChatInput.tsx @@ -100,11 +100,16 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => { setMessage('Please select a model to start chatting.') return } - if (!prompt.trim()) { + if (!prompt.trim() && uploadedFiles.length === 0) { return } setMessage('') - sendMessage(prompt) + sendMessage( + prompt, + true, + uploadedFiles.length > 0 ? uploadedFiles : undefined + ) + setUploadedFiles([]) } useEffect(() => { @@ -629,9 +634,13 @@ const ChatInput = ({ model, className, initialMessage }: ChatInputProps) => { ) : (