@memori.ai/memori-react 8.11.0 → 8.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/dist/components/AgeVerificationModal/AgeVerificationModal.css +41 -14
  3. package/dist/components/AgeVerificationModal/AgeVerificationModal.js +2 -2
  4. package/dist/components/AgeVerificationModal/AgeVerificationModal.js.map +1 -1
  5. package/dist/components/Auth/Auth.js +36 -8
  6. package/dist/components/Auth/Auth.js.map +1 -1
  7. package/dist/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.css +2 -2
  8. package/dist/components/Chat/Chat.css +37 -3
  9. package/dist/components/Chat/Chat.js +61 -23
  10. package/dist/components/Chat/Chat.js.map +1 -1
  11. package/dist/components/ChatBubble/ChatBubble.css +87 -15
  12. package/dist/components/ChatBubble/ChatBubble.js +129 -19
  13. package/dist/components/ChatBubble/ChatBubble.js.map +1 -1
  14. package/dist/components/ChatHistoryDrawer/ChatHistory.css +5 -1
  15. package/dist/components/ChatInputs/ChatInputs.css +293 -17
  16. package/dist/components/ChatInputs/ChatInputs.d.ts +1 -0
  17. package/dist/components/ChatInputs/ChatInputs.js +48 -27
  18. package/dist/components/ChatInputs/ChatInputs.js.map +1 -1
  19. package/dist/components/ChatTextArea/ChatTextArea.css +75 -31
  20. package/dist/components/ChatTextArea/ChatTextArea.js +47 -18
  21. package/dist/components/ChatTextArea/ChatTextArea.js.map +1 -1
  22. package/dist/components/DateSelector/DateSelector.css +125 -104
  23. package/dist/components/DateSelector/DateSelector.d.ts +1 -1
  24. package/dist/components/DateSelector/DateSelector.js +110 -52
  25. package/dist/components/DateSelector/DateSelector.js.map +1 -1
  26. package/dist/components/FilePreview/FilePreview.css +225 -146
  27. package/dist/components/FilePreview/FilePreview.d.ts +1 -2
  28. package/dist/components/FilePreview/FilePreview.js +20 -6
  29. package/dist/components/FilePreview/FilePreview.js.map +1 -1
  30. package/dist/components/Header/Header.css +2 -2
  31. package/dist/components/Header/Header.js +1 -1
  32. package/dist/components/Header/Header.js.map +1 -1
  33. package/dist/components/LoginDrawer/LoginDrawer.css +37 -5
  34. package/dist/components/LoginDrawer/LoginDrawer.d.ts +1 -2
  35. package/dist/components/LoginDrawer/LoginDrawer.js +2 -9
  36. package/dist/components/LoginDrawer/LoginDrawer.js.map +1 -1
  37. package/dist/components/MediaWidget/MediaItemWidget.js +2 -1
  38. package/dist/components/MediaWidget/MediaItemWidget.js.map +1 -1
  39. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js +1 -1
  40. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js.map +1 -1
  41. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyButtonWithDropdown.js +1 -1
  42. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyButtonWithDropdown.js.map +1 -1
  43. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyMenuItem.js +3 -0
  44. package/dist/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyMenuItem.js.map +1 -1
  45. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js +2 -2
  46. package/dist/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js.map +1 -1
  47. package/dist/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.css +16 -7
  48. package/dist/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.js +6 -4
  49. package/dist/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.js.map +1 -1
  50. package/dist/components/MemoriWidget/MemoriWidget.css +11 -2
  51. package/dist/components/MemoriWidget/MemoriWidget.js +105 -25
  52. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  53. package/dist/components/MicrophoneButton/MicrophoneButton.css +2 -2
  54. package/dist/components/StartPanel/StartPanel.css +8 -0
  55. package/dist/components/UploadButton/UploadButton.css +20 -17
  56. package/dist/components/UploadButton/UploadButton.js +218 -87
  57. package/dist/components/UploadButton/UploadButton.js.map +1 -1
  58. package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js +14 -4
  59. package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
  60. package/dist/components/UploadButton/UploadImages/UploadImages.js +143 -16
  61. package/dist/components/UploadButton/UploadImages/UploadImages.js.map +1 -1
  62. package/dist/components/layouts/chat.css +1 -1
  63. package/dist/components/ui/Drawer.css +8 -0
  64. package/dist/components/ui/Drawer.d.ts +2 -0
  65. package/dist/components/ui/Drawer.js +2 -2
  66. package/dist/components/ui/Drawer.js.map +1 -1
  67. package/dist/components/ui/Tooltip.css +49 -1
  68. package/dist/components/ui/Tooltip.d.ts +1 -1
  69. package/dist/helpers/constants.d.ts +1 -0
  70. package/dist/helpers/constants.js +2 -1
  71. package/dist/helpers/constants.js.map +1 -1
  72. package/dist/helpers/imageCompression.d.ts +7 -0
  73. package/dist/helpers/imageCompression.js +123 -0
  74. package/dist/helpers/imageCompression.js.map +1 -0
  75. package/dist/locales/de.json +13 -5
  76. package/dist/locales/en.json +17 -6
  77. package/dist/locales/es.json +13 -5
  78. package/dist/locales/fr.json +12 -5
  79. package/dist/locales/it.json +16 -6
  80. package/dist/styles.css +4 -4
  81. package/esm/components/AgeVerificationModal/AgeVerificationModal.css +41 -14
  82. package/esm/components/AgeVerificationModal/AgeVerificationModal.js +2 -2
  83. package/esm/components/AgeVerificationModal/AgeVerificationModal.js.map +1 -1
  84. package/esm/components/Auth/Auth.js +36 -8
  85. package/esm/components/Auth/Auth.js.map +1 -1
  86. package/esm/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.css +2 -2
  87. package/esm/components/Chat/Chat.css +37 -3
  88. package/esm/components/Chat/Chat.js +61 -23
  89. package/esm/components/Chat/Chat.js.map +1 -1
  90. package/esm/components/ChatBubble/ChatBubble.css +87 -15
  91. package/esm/components/ChatBubble/ChatBubble.js +130 -20
  92. package/esm/components/ChatBubble/ChatBubble.js.map +1 -1
  93. package/esm/components/ChatHistoryDrawer/ChatHistory.css +5 -1
  94. package/esm/components/ChatInputs/ChatInputs.css +293 -17
  95. package/esm/components/ChatInputs/ChatInputs.d.ts +1 -0
  96. package/esm/components/ChatInputs/ChatInputs.js +49 -28
  97. package/esm/components/ChatInputs/ChatInputs.js.map +1 -1
  98. package/esm/components/ChatTextArea/ChatTextArea.css +75 -31
  99. package/esm/components/ChatTextArea/ChatTextArea.js +49 -20
  100. package/esm/components/ChatTextArea/ChatTextArea.js.map +1 -1
  101. package/esm/components/DateSelector/DateSelector.css +125 -104
  102. package/esm/components/DateSelector/DateSelector.d.ts +1 -1
  103. package/esm/components/DateSelector/DateSelector.js +111 -52
  104. package/esm/components/DateSelector/DateSelector.js.map +1 -1
  105. package/esm/components/FilePreview/FilePreview.css +225 -146
  106. package/esm/components/FilePreview/FilePreview.d.ts +1 -2
  107. package/esm/components/FilePreview/FilePreview.js +21 -7
  108. package/esm/components/FilePreview/FilePreview.js.map +1 -1
  109. package/esm/components/Header/Header.css +2 -2
  110. package/esm/components/Header/Header.js +1 -1
  111. package/esm/components/Header/Header.js.map +1 -1
  112. package/esm/components/LoginDrawer/LoginDrawer.css +37 -5
  113. package/esm/components/LoginDrawer/LoginDrawer.d.ts +1 -2
  114. package/esm/components/LoginDrawer/LoginDrawer.js +2 -9
  115. package/esm/components/LoginDrawer/LoginDrawer.js.map +1 -1
  116. package/esm/components/MediaWidget/MediaItemWidget.js +2 -1
  117. package/esm/components/MediaWidget/MediaItemWidget.js.map +1 -1
  118. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js +1 -1
  119. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.js.map +1 -1
  120. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyButtonWithDropdown.js +1 -1
  121. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyButtonWithDropdown.js.map +1 -1
  122. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyMenuItem.js +3 -0
  123. package/esm/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyMenuItem.js.map +1 -1
  124. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js +2 -2
  125. package/esm/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.js.map +1 -1
  126. package/esm/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.css +16 -7
  127. package/esm/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.js +6 -4
  128. package/esm/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.js.map +1 -1
  129. package/esm/components/MemoriWidget/MemoriWidget.css +11 -2
  130. package/esm/components/MemoriWidget/MemoriWidget.js +105 -25
  131. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  132. package/esm/components/MicrophoneButton/MicrophoneButton.css +2 -2
  133. package/esm/components/StartPanel/StartPanel.css +8 -0
  134. package/esm/components/UploadButton/UploadButton.css +20 -17
  135. package/esm/components/UploadButton/UploadButton.js +219 -88
  136. package/esm/components/UploadButton/UploadButton.js.map +1 -1
  137. package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js +14 -4
  138. package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
  139. package/esm/components/UploadButton/UploadImages/UploadImages.js +143 -16
  140. package/esm/components/UploadButton/UploadImages/UploadImages.js.map +1 -1
  141. package/esm/components/layouts/chat.css +1 -1
  142. package/esm/components/ui/Drawer.css +8 -0
  143. package/esm/components/ui/Drawer.d.ts +2 -0
  144. package/esm/components/ui/Drawer.js +2 -2
  145. package/esm/components/ui/Drawer.js.map +1 -1
  146. package/esm/components/ui/Tooltip.css +49 -1
  147. package/esm/components/ui/Tooltip.d.ts +1 -1
  148. package/esm/helpers/constants.d.ts +1 -0
  149. package/esm/helpers/constants.js +1 -0
  150. package/esm/helpers/constants.js.map +1 -1
  151. package/esm/helpers/imageCompression.d.ts +7 -0
  152. package/esm/helpers/imageCompression.js +119 -0
  153. package/esm/helpers/imageCompression.js.map +1 -0
  154. package/esm/locales/de.json +13 -5
  155. package/esm/locales/en.json +17 -6
  156. package/esm/locales/es.json +13 -5
  157. package/esm/locales/fr.json +12 -5
  158. package/esm/locales/it.json +16 -6
  159. package/esm/styles.css +4 -4
  160. package/package.json +2 -2
  161. package/src/components/AgeVerificationModal/AgeVerificationModal.css +41 -14
  162. package/src/components/AgeVerificationModal/AgeVerificationModal.tsx +3 -1
  163. package/src/components/Auth/Auth.tsx +55 -11
  164. package/src/components/Avatar/Avatar.stories.tsx +3 -0
  165. package/src/components/Avatar/AvatarView/AvatarComponent/positionControls/positionControls.css +2 -2
  166. package/src/components/Chat/Chat.css +37 -3
  167. package/src/components/Chat/Chat.stories.tsx +16 -2
  168. package/src/components/Chat/Chat.tsx +90 -21
  169. package/src/components/Chat/__snapshots__/Chat.test.tsx.snap +1752 -812
  170. package/src/components/ChatBubble/ChatBubble.css +87 -15
  171. package/src/components/ChatBubble/ChatBubble.stories.tsx +16 -2
  172. package/src/components/ChatBubble/ChatBubble.test.tsx +17 -0
  173. package/src/components/ChatBubble/ChatBubble.tsx +237 -33
  174. package/src/components/ChatBubble/__snapshots__/ChatBubble.test.tsx.snap +304 -8
  175. package/src/components/ChatHistoryDrawer/ChatHistory.css +5 -1
  176. package/src/components/ChatInputs/ChatInputs.css +293 -17
  177. package/src/components/ChatInputs/ChatInputs.tsx +156 -86
  178. package/src/components/ChatInputs/__snapshots__/ChatInputs.test.tsx.snap +430 -424
  179. package/src/components/ChatTextArea/ChatTextArea.css +75 -31
  180. package/src/components/ChatTextArea/ChatTextArea.test.tsx +1 -16
  181. package/src/components/ChatTextArea/ChatTextArea.tsx +51 -22
  182. package/src/components/ChatTextArea/__snapshots__/ChatTextArea.test.tsx.snap +9 -72
  183. package/src/components/DateSelector/DateSelector.css +125 -104
  184. package/src/components/DateSelector/DateSelector.stories.tsx +1 -1
  185. package/src/components/DateSelector/DateSelector.test.tsx +137 -23
  186. package/src/components/DateSelector/DateSelector.tsx +203 -177
  187. package/src/components/FilePreview/FilePreview.css +225 -146
  188. package/src/components/FilePreview/FilePreview.tsx +49 -36
  189. package/src/components/FilePreview/__snapshots__/FilePreview.test.tsx.snap +2 -2
  190. package/src/components/Header/Header.css +2 -2
  191. package/src/components/Header/Header.stories.tsx +5 -1
  192. package/src/components/Header/Header.tsx +1 -1
  193. package/src/components/Header/__snapshots__/Header.test.tsx.snap +1 -1
  194. package/src/components/LoginDrawer/LoginDrawer.css +37 -5
  195. package/src/components/LoginDrawer/LoginDrawer.stories.tsx +0 -1
  196. package/src/components/LoginDrawer/LoginDrawer.test.tsx +0 -1
  197. package/src/components/LoginDrawer/LoginDrawer.tsx +0 -19
  198. package/src/components/MediaWidget/MediaItemWidget.tsx +2 -1
  199. package/src/components/MemoriArtifactSystem/ArtifactDrawer.stories.tsx +996 -204
  200. package/src/components/MemoriArtifactSystem/components/ArtifactActions/ArtifactActions.tsx +2 -2
  201. package/src/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyButtonWithDropdown.tsx +1 -1
  202. package/src/components/MemoriArtifactSystem/components/ArtifactActions/components/CopyMenuItem.tsx +3 -0
  203. package/src/components/MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer.tsx +56 -54
  204. package/src/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.css +16 -7
  205. package/src/components/MemoriArtifactSystem/components/ArtifactPreview/ArtifactPreview.tsx +12 -3
  206. package/src/components/MemoriWidget/MemoriWidget.css +11 -2
  207. package/src/components/MemoriWidget/MemoriWidget.stories.tsx +6 -3
  208. package/src/components/MemoriWidget/MemoriWidget.tsx +173 -49
  209. package/src/components/MicrophoneButton/MicrophoneButton.css +2 -2
  210. package/src/components/StartPanel/StartPanel.css +8 -0
  211. package/src/components/UploadButton/UploadButton.css +20 -17
  212. package/src/components/UploadButton/UploadButton.stories.tsx +247 -35
  213. package/src/components/UploadButton/UploadButton.tsx +280 -173
  214. package/src/components/UploadButton/UploadDocuments/UploadDocuments.tsx +19 -4
  215. package/src/components/UploadButton/UploadImages/UploadImages.tsx +196 -35
  216. package/src/components/UploadButton/__snapshots__/UploadButton.test.tsx.snap +10 -1
  217. package/src/components/layouts/FullBody/FullBody.stories.tsx +9 -10
  218. package/src/components/layouts/Totem/Totem.stories.tsx +8 -9
  219. package/src/components/layouts/ZoomedFullBody/ZoomedFullBody.stories.tsx +8 -9
  220. package/src/components/layouts/chat.css +1 -1
  221. package/src/components/layouts/layouts.stories.tsx +10 -9
  222. package/src/components/ui/Drawer.css +8 -0
  223. package/src/components/ui/Drawer.tsx +16 -12
  224. package/src/components/ui/Tooltip.css +49 -1
  225. package/src/components/ui/Tooltip.tsx +1 -1
  226. package/src/helpers/constants.ts +1 -1
  227. package/src/helpers/imageCompression.ts +230 -0
  228. package/src/index.stories.tsx +18 -0
  229. package/src/locales/de.json +13 -5
  230. package/src/locales/en.json +17 -6
  231. package/src/locales/es.json +13 -5
  232. package/src/locales/fr.json +12 -5
  233. package/src/locales/it.json +16 -6
  234. package/src/mocks/data.ts +4 -2
  235. package/src/styles.css +4 -4
  236. package/src/components/SignupForm/SignupForm.test.tsx +0 -40
  237. package/src/components/SignupForm/SignupForm.tsx +0 -457
  238. package/src/components/SignupForm/__snapshots__/SignupForm.test.tsx.snap +0 -247
  239. package/src/components/UploadMenu/UploadMenu.css +0 -47
  240. package/src/components/UploadMenu/UploadMenu.stories.tsx +0 -66
  241. package/src/components/UploadMenu/UploadMenu.test.tsx +0 -34
  242. package/src/components/UploadMenu/UploadMenu.tsx +0 -68
  243. package/src/components/UploadMenu/__snapshots__/UploadMenu.test.tsx.snap +0 -137
@@ -1,4 +1,4 @@
1
- import React, { useState, useRef, useEffect } from 'react';
1
+ import React, { useState, useRef, useEffect, useCallback } from 'react';
2
2
  import { DocumentIcon } from '../icons/Document';
3
3
  import { ImageIcon } from '../icons/Image';
4
4
  import { UploadIcon } from '../icons/Upload';
@@ -9,10 +9,10 @@ import UploadDocuments from './UploadDocuments/UploadDocuments';
9
9
  import UploadImages from './UploadImages/UploadImages';
10
10
  import { useTranslation } from 'react-i18next';
11
11
  import memoriApiClient from '@memori.ai/memori-api-client';
12
+ import Tooltip from '../ui/Tooltip';
12
13
 
13
14
  // Constants
14
- const MAX_IMAGES = 5;
15
- const MAX_DOCUMENTS = 5;
15
+ const MAX_MEDIA = 10;
16
16
 
17
17
  // Props interface
18
18
  interface UploadManagerProps {
@@ -42,29 +42,23 @@ const UploadButton: React.FC<UploadManagerProps> = ({
42
42
  }) => {
43
43
  // State
44
44
  const [isLoading, setIsLoading] = useState(false);
45
- const [menuOpen, setMenuOpen] = useState(false);
46
45
  const [errors, setErrors] = useState<
47
46
  { message: string; severity: 'error' | 'warning' | 'info' }[]
48
47
  >([]);
48
+ const [isDragging, setIsDragging] = useState(false);
49
49
  const { t, i18n } = useTranslation();
50
50
 
51
51
  // Refs
52
- const menuRef = useRef<HTMLDivElement>(null);
53
52
  const buttonRef = useRef<HTMLButtonElement>(null);
54
53
  const documentRef = useRef<HTMLDivElement>(null);
55
54
  const imageRef = useRef<HTMLDivElement>(null);
55
+ const unifiedInputRef = useRef<HTMLInputElement>(null);
56
+ const wrapperRef = useRef<HTMLDivElement>(null);
56
57
 
57
- // Calculate image count and remaining slots
58
- const currentImageCount = documentPreviewFiles.filter(
59
- file => file.type === 'image'
60
- ).length;
61
- const remainingSlots = MAX_IMAGES - currentImageCount;
62
- const currentDocumentCount = documentPreviewFiles.filter(
63
- file => file.type === 'document'
64
- ).length;
65
- const remainingDocumentSlots = MAX_DOCUMENTS - currentDocumentCount;
66
- const hasReachedImageLimit = remainingSlots <= 0;
67
- const hasReachedDocumentLimit = remainingDocumentSlots <= 0;
58
+ // Calculate total media count
59
+ const currentMediaCount = documentPreviewFiles.length;
60
+ const remainingSlots = MAX_MEDIA - currentMediaCount;
61
+ const hasReachedMediaLimit = remainingSlots <= 0;
68
62
 
69
63
  // Error handling
70
64
  const removeError = (errorMessage: string) => {
@@ -79,33 +73,259 @@ const UploadButton: React.FC<UploadManagerProps> = ({
79
73
  setTimeout(() => removeError(error.message), 5000);
80
74
  };
81
75
 
82
- // Menu handling
83
- const toggleMenu = () => {
84
- setMenuOpen(prev => !prev);
76
+ // Check if file is an image
77
+ const isImageFile = (file: File): boolean => {
78
+ const imageTypes = ['image/jpeg', 'image/jpg', 'image/png'];
79
+ const imageExtensions = ['.jpg', '.jpeg', '.png'];
80
+ const fileExt = `.${file.name.split('.').pop()?.toLowerCase()}`;
81
+ return imageTypes.includes(file.type) || imageExtensions.includes(fileExt);
82
+ };
83
+
84
+ // Check if file is a document
85
+ const isDocumentFile = (file: File): boolean => {
86
+ const documentExtensions = ['.pdf', '.txt', '.json', '.xlsx', '.csv', '.md', '.html'];
87
+ const fileExt = `.${file.name.split('.').pop()?.toLowerCase()}`;
88
+ return documentExtensions.includes(fileExt);
89
+ };
90
+
91
+ // Use refs to access latest values in event handlers
92
+ const isMediaAcceptedRef = useRef(isMediaAccepted);
93
+ const currentMediaCountRef = useRef(currentMediaCount);
94
+ const addErrorRef = useRef(addError);
95
+
96
+ useEffect(() => {
97
+ isMediaAcceptedRef.current = isMediaAccepted;
98
+ currentMediaCountRef.current = currentMediaCount;
99
+ addErrorRef.current = addError;
100
+ }, [isMediaAccepted, currentMediaCount, addError]);
101
+
102
+ // Handle unified file selection
103
+ const handleUnifiedFileSelection = useCallback((files: FileList | File[]) => {
104
+ const fileArray = Array.from(files);
105
+ if (fileArray.length === 0) return;
106
+
107
+ const imageFiles: File[] = [];
108
+ const documentFiles: File[] = [];
109
+
110
+ // Separate files by type
111
+ fileArray.forEach(file => {
112
+ if (isImageFile(file)) {
113
+ imageFiles.push(file);
114
+ } else if (isDocumentFile(file)) {
115
+ documentFiles.push(file);
116
+ } else {
117
+ addErrorRef.current({
118
+ message: `File "${file.name}" is not a supported image or document type`,
119
+ severity: 'error',
120
+ });
121
+ }
122
+ });
123
+
124
+ // Calculate total files to be added
125
+ const totalFilesToAdd = imageFiles.length + documentFiles.length;
126
+ const newTotalCount = currentMediaCountRef.current + totalFilesToAdd;
127
+
128
+ // Check if adding these files would exceed the limit
129
+ if (newTotalCount > MAX_MEDIA) {
130
+ addErrorRef.current({
131
+ message: `Maximum ${MAX_MEDIA} media files allowed.`,
132
+ severity: 'error',
133
+ });
134
+ return;
135
+ }
136
+
137
+ // Process images
138
+ if (imageFiles.length > 0) {
139
+ if (!isMediaAcceptedRef.current) {
140
+ addErrorRef.current({
141
+ message:
142
+ t('upload.mediaNotAccepted') ?? 'Media uploads are not accepted',
143
+ severity: 'info',
144
+ });
145
+ } else {
146
+ // Trigger image upload by creating a synthetic event
147
+ const imageInput = imageRef.current?.querySelector('input[type="file"]') as HTMLInputElement;
148
+ if (imageInput) {
149
+ const dataTransfer = new DataTransfer();
150
+ imageFiles.forEach(file => {
151
+ try {
152
+ dataTransfer.items.add(file);
153
+ } catch (err) {
154
+ console.warn('Failed to add image file to DataTransfer:', err);
155
+ }
156
+ });
157
+
158
+ // Only proceed if we successfully added files
159
+ if (dataTransfer.files.length > 0) {
160
+ imageInput.files = dataTransfer.files;
161
+ const changeEvent = new Event('change', { bubbles: true });
162
+ imageInput.dispatchEvent(changeEvent);
163
+ }
164
+ }
165
+ }
166
+ }
167
+
168
+ // Process documents
169
+ if (documentFiles.length > 0) {
170
+ // Trigger document upload by creating a synthetic event
171
+ const documentInput = documentRef.current?.querySelector('input[type="file"]') as HTMLInputElement;
172
+ if (documentInput) {
173
+ const dataTransfer = new DataTransfer();
174
+ documentFiles.forEach(file => {
175
+ try {
176
+ dataTransfer.items.add(file);
177
+ } catch (err) {
178
+ console.warn('Failed to add document file to DataTransfer:', err);
179
+ }
180
+ });
181
+
182
+ // Only proceed if we successfully added files
183
+ if (dataTransfer.files.length > 0) {
184
+ documentInput.files = dataTransfer.files;
185
+ const changeEvent = new Event('change', { bubbles: true });
186
+ documentInput.dispatchEvent(changeEvent);
187
+ }
188
+ }
189
+ }
190
+ }, [t]);
191
+
192
+ // Handle button click - open file chooser directly
193
+ const handleButtonClick = () => {
194
+ if (unifiedInputRef.current) {
195
+ unifiedInputRef.current.click();
196
+ }
85
197
  };
86
198
 
87
- const closeMenu = () => {
88
- setMenuOpen(false);
199
+ // Handle file input change
200
+ const handleFileInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
201
+ const files = e.target.files;
202
+ if (files && files.length > 0) {
203
+ handleUnifiedFileSelection(files);
204
+ }
205
+ // Reset input value to allow selecting the same file again
206
+ if (unifiedInputRef.current) {
207
+ unifiedInputRef.current.value = '';
208
+ }
89
209
  };
90
210
 
91
- // Click outside handler
211
+ // Paste handler for files
92
212
  useEffect(() => {
93
- const handleClickOutside = (event: MouseEvent) => {
94
- if (
95
- menuRef.current &&
96
- buttonRef.current &&
97
- !menuRef.current.contains(event.target as Node) &&
98
- !buttonRef.current.contains(event.target as Node)
99
- ) {
100
- closeMenu();
213
+ const handlePaste = (e: ClipboardEvent) => {
214
+ const clipboardData = e.clipboardData;
215
+ if (!clipboardData) {
216
+ console.log('[UploadButton] handlePaste: No clipboardData available.');
217
+ return;
218
+ }
219
+
220
+ const files: File[] = [];
221
+
222
+ // Helper to check if a file is already in the array
223
+ const isDuplicate = (file: File) => {
224
+ return files.some(f =>
225
+ f.name === file.name &&
226
+ f.size === file.size &&
227
+ f.lastModified === file.lastModified
228
+ );
229
+ };
230
+
231
+ // First, try to get files directly from clipboardData.files (most reliable for multiple files)
232
+ if (clipboardData.files && clipboardData.files.length > 0) {
233
+ const clipboardFiles = Array.from(clipboardData.files);
234
+ console.log(`[UploadButton] handlePaste: clipboardData.files found`, clipboardFiles);
235
+ clipboardFiles.forEach(file => {
236
+ if (!isDuplicate(file)) {
237
+ files.push(file);
238
+ } else {
239
+ console.log(`[UploadButton] handlePaste: Duplicate file skipped from clipboardData.files:`, file);
240
+ }
241
+ });
242
+ }
243
+
244
+ // Also check items array (some browsers populate this instead of or in addition to files)
245
+ const items = clipboardData.items;
246
+ if (items) {
247
+ for (let i = 0; i < items.length; i++) {
248
+ const item = items[i];
249
+ if (item.kind === 'file') {
250
+ const file = item.getAsFile();
251
+ if (file && !isDuplicate(file)) {
252
+ console.log(`[UploadButton] handlePaste: Adding file from items array:`, file);
253
+ files.push(file);
254
+ } else if (file) {
255
+ console.log(`[UploadButton] handlePaste: Duplicate file skipped from items array:`, file);
256
+ }
257
+ }
258
+ }
259
+ }
260
+
261
+ if (files.length > 0) {
262
+ console.log(`[UploadButton] handlePaste: ${files.length} file(s) to process from paste`, files);
263
+ e.preventDefault();
264
+ handleUnifiedFileSelection(files);
265
+ } else {
266
+ console.log('[UploadButton] handlePaste: No files found in paste event.');
267
+ }
268
+ };
269
+
270
+ // Add paste listener to document
271
+ document.addEventListener('paste', handlePaste);
272
+ return () => {
273
+ document.removeEventListener('paste', handlePaste);
274
+ };
275
+ }, [handleUnifiedFileSelection]);
276
+
277
+ // Drag and drop handlers
278
+ useEffect(() => {
279
+ let dragCounter = 0;
280
+
281
+ const handleDragEnter = (e: DragEvent) => {
282
+ e.preventDefault();
283
+ e.stopPropagation();
284
+ dragCounter++;
285
+ if (dragCounter === 1) {
286
+ setIsDragging(true);
287
+ }
288
+ };
289
+
290
+ const handleDragLeave = (e: DragEvent) => {
291
+ e.preventDefault();
292
+ e.stopPropagation();
293
+ dragCounter--;
294
+ if (dragCounter === 0) {
295
+ setIsDragging(false);
101
296
  }
102
297
  };
103
298
 
104
- document.addEventListener('mousedown', handleClickOutside);
299
+ const handleDragOver = (e: DragEvent) => {
300
+ e.preventDefault();
301
+ e.stopPropagation();
302
+ };
303
+
304
+ const handleDrop = (e: DragEvent) => {
305
+ e.preventDefault();
306
+ e.stopPropagation();
307
+ dragCounter = 0;
308
+ setIsDragging(false);
309
+
310
+ const files = e.dataTransfer?.files;
311
+ if (files && files.length > 0) {
312
+ handleUnifiedFileSelection(files);
313
+ }
314
+ };
315
+
316
+ // Add drag and drop listeners to document
317
+ document.addEventListener('dragenter', handleDragEnter);
318
+ document.addEventListener('dragleave', handleDragLeave);
319
+ document.addEventListener('dragover', handleDragOver);
320
+ document.addEventListener('drop', handleDrop);
321
+
105
322
  return () => {
106
- document.removeEventListener('mousedown', handleClickOutside);
323
+ document.removeEventListener('dragenter', handleDragEnter);
324
+ document.removeEventListener('dragleave', handleDragLeave);
325
+ document.removeEventListener('dragover', handleDragOver);
326
+ document.removeEventListener('drop', handleDrop);
107
327
  };
108
- }, []);
328
+ }, [handleUnifiedFileSelection]);
109
329
 
110
330
  // Handler for document files - now supports multiple documents
111
331
  const handleDocumentFiles = (
@@ -161,6 +381,7 @@ ${file.content}
161
381
  '.xlsx',
162
382
  '.csv',
163
383
  '.md',
384
+ '.html',
164
385
  ];
165
386
  const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
166
387
 
@@ -267,56 +488,6 @@ ${file.content}
267
488
  addError(error);
268
489
  };
269
490
 
270
- // When document option is clicked
271
- const handleDocumentClick = () => {
272
- // Check if document limit has been reached
273
- if (hasReachedDocumentLimit) {
274
- addError({
275
- message: `Maximum ${MAX_DOCUMENTS} documents allowed.`,
276
- severity: 'error',
277
- });
278
- closeMenu();
279
- return;
280
- }
281
-
282
- // Find the actual button in the UploadDocuments component and click it
283
- const documentButtonElement = documentRef.current?.querySelector('button');
284
- if (documentButtonElement) {
285
- documentButtonElement.click();
286
- }
287
- closeMenu();
288
- };
289
-
290
- // When image option is clicked
291
- const handleImageClick = () => {
292
- if (!isMediaAccepted) {
293
- addError({
294
- message:
295
- t('upload.mediaNotAccepted') ?? 'Media uploads are not accepted',
296
- severity: 'info',
297
- });
298
- closeMenu();
299
- return;
300
- }
301
-
302
- if (hasReachedImageLimit) {
303
- addError({
304
- message:
305
- t('upload.maxImagesReached', { max: MAX_IMAGES }) ??
306
- `Maximum ${MAX_IMAGES} images already uploaded`,
307
- severity: 'warning',
308
- });
309
- closeMenu();
310
- return;
311
- }
312
-
313
- // If all checks pass, click the button in UploadImages component
314
- const imageButtonElement = imageRef.current?.querySelector('button');
315
- if (imageButtonElement) {
316
- imageButtonElement.click();
317
- }
318
- closeMenu();
319
- };
320
491
 
321
492
  // Set loading state for child components
322
493
  const handleLoadingChange = (loading: boolean) => {
@@ -324,7 +495,23 @@ ${file.content}
324
495
  };
325
496
 
326
497
  return (
327
- <div className="memori--unified-upload-wrapper">
498
+ <div
499
+ className={cx('memori--unified-upload-wrapper', {
500
+ 'memori--dragging': isDragging,
501
+ })}
502
+ ref={wrapperRef}
503
+ >
504
+ {/* Unified file input - accepts both images and documents */}
505
+ <input
506
+ ref={unifiedInputRef}
507
+ type="file"
508
+ accept=".jpg,.jpeg,.png,.pdf,.txt,.json,.xlsx,.csv,.md,.html"
509
+ multiple
510
+ className="memori--upload-file-input"
511
+ onChange={handleFileInputChange}
512
+ style={{ display: 'none' }}
513
+ />
514
+
328
515
  {/* Main upload button */}
329
516
  <button
330
517
  ref={buttonRef}
@@ -337,9 +524,9 @@ ${file.content}
337
524
  'memori--unified-upload-button',
338
525
  { 'memori--error': errors.length > 0 }
339
526
  )}
340
- onClick={toggleMenu}
341
- disabled={isLoading}
342
- title={t('upload.uploadFiles') ?? 'Upload files'}
527
+ onClick={handleButtonClick}
528
+ disabled={isLoading || hasReachedMediaLimit}
529
+ title={t('upload.uploadFiles', { shortcut: /Mac|iPhone|iPod|iPad/i.test(navigator.platform) || navigator.userAgent.includes('Mac') ? 'Cmd' : 'Ctrl' }) ?? 'Upload files (drag & drop)'}
343
530
  >
344
531
  {isLoading ? (
345
532
  <Spin spinning className="memori--upload-icon" />
@@ -348,94 +535,14 @@ ${file.content}
348
535
  )}
349
536
  </button>
350
537
 
351
- {/* Image count indicator - moved here from UploadImages */}
352
- {currentImageCount > 0 && (
353
- <div
354
- className={cx('memori--image-count', {
355
- 'memori--image-count-full': hasReachedImageLimit,
356
- })}
357
- >
358
- {currentImageCount}/{MAX_IMAGES}
359
- </div>
360
- )}
361
-
362
- {/* Document count indicator */}
363
- {currentDocumentCount > 0 && (
538
+ {/* Media count indicator */}
539
+ {currentMediaCount > 0 && (
364
540
  <div
365
541
  className={cx('memori--document-count', {
366
- 'memori--document-count-full': hasReachedDocumentLimit,
542
+ 'memori--document-count-full': hasReachedMediaLimit,
367
543
  })}
368
544
  >
369
- {currentDocumentCount}/{MAX_DOCUMENTS}
370
- </div>
371
- )}
372
-
373
- {/* Floating menu */}
374
- {menuOpen && (
375
- <div className="memori--upload-menu" ref={menuRef}>
376
- <div
377
- className={cx('memori--upload-menu-item', {
378
- 'memori--upload-menu-item--disabled': hasReachedDocumentLimit,
379
- })}
380
- onClick={handleDocumentClick}
381
- title={
382
- hasReachedDocumentLimit
383
- ? t('upload.maxDocumentsReached', { max: MAX_DOCUMENTS }) ??
384
- `Maximum ${MAX_DOCUMENTS} documents already uploaded`
385
- : remainingDocumentSlots === 1
386
- ? t('upload.lastDocumentSlot') ?? 'Upload last document'
387
- : t('upload.uploadDocument', {
388
- remaining: remainingDocumentSlots,
389
- }) ?? `Upload document (${remainingDocumentSlots} remaining)`
390
- }
391
- >
392
- <DocumentIcon className="memori--upload-menu-icon" />
393
- <span>
394
- {t('upload.uploadDocument') ?? 'Upload document'}
395
- {/* {currentDocumentCount > 0 && (
396
- <span className="memori--upload-slots-info">
397
- {hasReachedDocumentLimit
398
- ? ` (${t('upload.maxReached') ?? 'Max reached'})`
399
- : ` (${remainingDocumentSlots} ${
400
- t('upload.remaining') ?? 'remaining'
401
- })`}
402
- </span>
403
- )} */}
404
- </span>
405
- </div>
406
-
407
- <div
408
- className={cx('memori--upload-menu-item', {
409
- 'memori--upload-menu-item--disabled':
410
- !isMediaAccepted || hasReachedImageLimit,
411
- })}
412
- onClick={handleImageClick}
413
- title={
414
- !isMediaAccepted
415
- ? t('upload.mediaNotAccepted') ?? 'Media uploads not accepted'
416
- : hasReachedImageLimit
417
- ? t('upload.maxImagesReached', { max: MAX_IMAGES }) ??
418
- `Maximum ${MAX_IMAGES} images already uploaded`
419
- : remainingSlots === 1
420
- ? t('upload.lastImageSlot') ?? 'Upload last image'
421
- : t('upload.uploadImage', { remaining: remainingSlots }) ??
422
- `Upload image (${remainingSlots} remaining)`
423
- }
424
- >
425
- <ImageIcon className="memori--upload-menu-icon-image" />
426
- <span>
427
- {t('upload.uploadImage') ?? 'Upload image'}
428
- {/* {currentImageCount > 0 && (
429
- <span className="memori--upload-slots-info">
430
- {hasReachedImageLimit
431
- ? ` (${t('upload.maxReached') ?? 'Max reached'})`
432
- : ` (${remainingSlots} ${
433
- t('upload.remaining') ?? 'remaining'
434
- })`}
435
- </span>
436
- )} */}
437
- </span>
438
- </div>
545
+ {currentMediaCount}/{MAX_MEDIA}
439
546
  </div>
440
547
  )}
441
548
 
@@ -443,7 +550,7 @@ ${file.content}
443
550
  <div className="memori--hidden-uploader" ref={documentRef}>
444
551
  <UploadDocuments
445
552
  setDocumentPreviewFiles={handleDocumentFiles}
446
- maxDocuments={MAX_DOCUMENTS}
553
+ maxDocuments={MAX_MEDIA}
447
554
  documentPreviewFiles={documentPreviewFiles}
448
555
  onLoadingChange={handleLoadingChange}
449
556
  onDocumentError={handleDocumentError}
@@ -461,7 +568,7 @@ ${file.content}
461
568
  documentPreviewFiles={documentPreviewFiles}
462
569
  isMediaAccepted={isMediaAccepted}
463
570
  onLoadingChange={handleLoadingChange}
464
- maxImages={MAX_IMAGES}
571
+ maxImages={MAX_MEDIA}
465
572
  memoriID={memoriID}
466
573
  onImageError={handleImageError}
467
574
  onValidateImageFile={validateImageFile}
@@ -220,7 +220,7 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
220
220
 
221
221
  if (fileExt === 'pdf') {
222
222
  text = await extractTextFromPDF(file);
223
- } else if (['txt', 'md', 'json', 'csv'].includes(fileExt)) {
223
+ } else if (['txt', 'md', 'json', 'csv', 'html'].includes(fileExt)) {
224
224
  text = await file.text();
225
225
  } else if (fileExt === 'xlsx') {
226
226
  text = await extractTextFromXLSX(file);
@@ -258,6 +258,21 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
258
258
  const files = Array.from(e.target.files || []);
259
259
  if (files.length === 0) return;
260
260
 
261
+ // Check current total media count (images + documents)
262
+ const currentMediaCount = documentPreviewFiles.length;
263
+
264
+ // Check if adding these files would exceed the total media limit
265
+ if (maxDocuments && currentMediaCount + files.length > maxDocuments) {
266
+ onDocumentError?.({
267
+ message: `Maximum ${maxDocuments} media files allowed. You can upload ${Math.max(0, maxDocuments - currentMediaCount)} more file${maxDocuments - currentMediaCount !== 1 ? 's' : ''}.`,
268
+ severity: 'error',
269
+ });
270
+ if (documentInputRef.current) {
271
+ documentInputRef.current.value = '';
272
+ }
273
+ return;
274
+ }
275
+
261
276
  setIsLoading(true);
262
277
 
263
278
  // Process each file
@@ -335,7 +350,8 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
335
350
  <input
336
351
  ref={documentInputRef}
337
352
  type="file"
338
- accept=".pdf,.txt,.md,.json,.xlsx,.csv"
353
+ accept=".pdf,.txt,.md,.json,.xlsx,.csv,.html"
354
+ multiple
339
355
  className="memori--upload-file-input"
340
356
  onChange={handleDocumentUpload}
341
357
  />
@@ -355,8 +371,7 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
355
371
  disabled={
356
372
  isLoading ||
357
373
  (maxDocuments &&
358
- documentPreviewFiles.filter((file: any) => file.type !== 'image')
359
- .length >= maxDocuments) ||
374
+ documentPreviewFiles.length >= maxDocuments) ||
360
375
  false
361
376
  }
362
377
  title="Upload documents"