diff --git a/CHANGELOG.md b/CHANGELOG.md index fb77a4d..b57a111 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to ### Added +- ✨(front) add drag'n drop file - ✨(activation-codes) register users also on Brevo #98 - 📈(posthog) add `sub` field to tracking #95 diff --git a/src/frontend/apps/conversations/src/features/chat/assets/files.svg b/src/frontend/apps/conversations/src/features/chat/assets/files.svg new file mode 100644 index 0000000..26c247e --- /dev/null +++ b/src/frontend/apps/conversations/src/features/chat/assets/files.svg @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/frontend/apps/conversations/src/features/chat/components/InputChat.tsx b/src/frontend/apps/conversations/src/features/chat/components/InputChat.tsx index 3e6153e..0dd244b 100644 --- a/src/frontend/apps/conversations/src/features/chat/components/InputChat.tsx +++ b/src/frontend/apps/conversations/src/features/chat/components/InputChat.tsx @@ -8,6 +8,8 @@ import { LLMModel } from '@/features/chat/api/useLLMConfiguration'; import { useAnalytics } from '@/libs'; import { useResponsiveStore } from '@/stores'; +import FilesIcon from '../assets/files.svg'; + import { AttachmentList } from './AttachmentList'; import { ModelSelector } from './ModelSelector'; import { ScrollDown } from './ScrollDown'; @@ -128,157 +130,243 @@ export const InputChat = ({ } }, [status, input]); + useEffect(() => { + if (!fileUploadEnabled) { + return; + } + + const handleDragEnter = (e: DragEvent) => { + e.preventDefault(); + if (e.dataTransfer?.types.includes('Files')) { + setIsDragActive(true); + } + }; + + const handleDragLeave = (e: DragEvent) => { + e.preventDefault(); + // Only hide when leaving the window completely + if (!e.relatedTarget) { + setIsDragActive(false); + } + }; + + const handleDragOver = (e: DragEvent) => { + e.preventDefault(); + }; + + const handleDrop = (e: DragEvent) => { + e.preventDefault(); + setIsDragActive(false); + + if (!fileUploadEnabled) { + return; + } + + const droppedFiles = e.dataTransfer?.files; + if (droppedFiles && droppedFiles.length > 0) { + setFiles((prev) => { + const dt = new DataTransfer(); + if (prev) { + Array.from(prev).forEach((f) => dt.items.add(f)); + } + Array.from(droppedFiles).forEach((f) => { + if ( + !Array.from(prev || []).some( + (pf) => + pf.name === f.name && + pf.size === f.size && + pf.lastModified === f.lastModified, + ) + ) { + dt.items.add(f); + } + }); + return dt.files; + }); + } + }; + + window.addEventListener('dragenter', handleDragEnter); + window.addEventListener('dragleave', handleDragLeave); + window.addEventListener('dragover', handleDragOver); + window.addEventListener('drop', handleDrop); + + return () => { + window.removeEventListener('dragenter', handleDragEnter); + window.removeEventListener('dragleave', handleDragLeave); + window.removeEventListener('dragover', handleDragOver); + window.removeEventListener('drop', handleDrop); + }; + }, [fileUploadEnabled, setFiles]); + const isInputDisabled = status !== 'ready' || isUploadingFiles; return ( - - {/* Bouton de scroll vers le bas */} - {messagesLength > 1 && - status !== 'streaming' && - status !== 'submitted' && - containerRef && - onScrollToBottom && ( - + {isDragActive && ( + + )} + + {/* Bouton de scroll vers le bas */} + {messagesLength > 1 && + status !== 'streaming' && + status !== 'submitted' && + containerRef && + onScrollToBottom && ( + + + + )} + {/* Message de bienvenue */} + {messagesLength === 0 && ( + - + + {t('What is on your mind?')} + )} - {/* Message de bienvenue */} - {messagesLength === 0 && ( - - - {t('What is on your mind?')} - - - )} -
{ - e.preventDefault(); - setIsDragActive(fileUploadEnabled); - }} - onDragLeave={(e) => { - e.preventDefault(); - setIsDragActive(false); - }} - onDrop={(e) => { - e.preventDefault(); - setIsDragActive(false); - if (!fileUploadEnabled) { - return; - } - if (e.dataTransfer.files?.length) { - setFiles((prev) => { - const dt = new DataTransfer(); - if (prev) { - Array.from(prev).forEach((f) => dt.items.add(f)); - } - Array.from(e.dataTransfer.files).forEach((f) => { - if ( - !Array.from(prev || []).some( - (pf) => - pf.name === f.name && - pf.size === f.size && - pf.lastModified === f.lastModified, - ) - ) { - dt.items.add(f); - } - }); - return dt.files; - }); - } - }} - style={{ width: '100%' }} - > - - { + e.preventDefault(); + setIsDragActive(fileUploadEnabled); + }} + onDragLeave={(e) => { + e.preventDefault(); + setIsDragActive(false); + }} + onDrop={(e) => { + e.preventDefault(); + // File handling is now done by global handler + }} + style={{ width: '100%' }} + > + + -