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?')}
-
-
- )}
-
-
+
+
+ >
);
};