@memori.ai/memori-react 7.33.4 → 7.34.1

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 (63) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/components/ChatHistoryDrawer/ChatHistory.js +49 -29
  3. package/dist/components/ChatHistoryDrawer/ChatHistory.js.map +1 -1
  4. package/dist/components/MemoriWidget/MemoriWidget.js +6 -1
  5. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  6. package/dist/components/UploadButton/UploadButton.css +32 -12
  7. package/dist/components/UploadButton/UploadButton.js +96 -20
  8. package/dist/components/UploadButton/UploadButton.js.map +1 -1
  9. package/dist/components/UploadButton/UploadDocuments/UploadDocuments.d.ts +12 -0
  10. package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js +80 -57
  11. package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
  12. package/dist/components/UploadButton/UploadImages/UploadImages.d.ts +5 -0
  13. package/dist/components/UploadButton/UploadImages/UploadImages.js +10 -42
  14. package/dist/components/UploadButton/UploadImages/UploadImages.js.map +1 -1
  15. package/dist/helpers/constants.d.ts +3 -0
  16. package/dist/helpers/constants.js +4 -1
  17. package/dist/helpers/constants.js.map +1 -1
  18. package/dist/helpers/utils.d.ts +1 -0
  19. package/dist/helpers/utils.js +10 -1
  20. package/dist/helpers/utils.js.map +1 -1
  21. package/dist/index.js +8 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/locales/en.json +3 -1
  24. package/dist/locales/it.json +2 -0
  25. package/esm/components/ChatHistoryDrawer/ChatHistory.js +49 -29
  26. package/esm/components/ChatHistoryDrawer/ChatHistory.js.map +1 -1
  27. package/esm/components/MemoriWidget/MemoriWidget.js +7 -2
  28. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  29. package/esm/components/UploadButton/UploadButton.css +32 -12
  30. package/esm/components/UploadButton/UploadButton.js +96 -20
  31. package/esm/components/UploadButton/UploadButton.js.map +1 -1
  32. package/esm/components/UploadButton/UploadDocuments/UploadDocuments.d.ts +12 -0
  33. package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js +81 -58
  34. package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
  35. package/esm/components/UploadButton/UploadImages/UploadImages.d.ts +5 -0
  36. package/esm/components/UploadButton/UploadImages/UploadImages.js +10 -42
  37. package/esm/components/UploadButton/UploadImages/UploadImages.js.map +1 -1
  38. package/esm/helpers/constants.d.ts +3 -0
  39. package/esm/helpers/constants.js +3 -0
  40. package/esm/helpers/constants.js.map +1 -1
  41. package/esm/helpers/utils.d.ts +1 -0
  42. package/esm/helpers/utils.js +8 -0
  43. package/esm/helpers/utils.js.map +1 -1
  44. package/esm/index.js +8 -0
  45. package/esm/index.js.map +1 -1
  46. package/esm/locales/en.json +3 -1
  47. package/esm/locales/it.json +2 -0
  48. package/package.json +2 -2
  49. package/src/__snapshots__/index.test.tsx.snap +41 -0
  50. package/src/components/ChatHistoryDrawer/ChatHistory.stories.tsx +40 -1
  51. package/src/components/ChatHistoryDrawer/ChatHistory.tsx +106 -58
  52. package/src/components/MemoriWidget/MemoriWidget.tsx +13 -1
  53. package/src/components/UploadButton/UploadButton.css +32 -12
  54. package/src/components/UploadButton/UploadButton.tsx +145 -21
  55. package/src/components/UploadButton/UploadDocuments/UploadDocuments.tsx +105 -87
  56. package/src/components/UploadButton/UploadImages/UploadImages.tsx +12 -76
  57. package/src/components/UploadButton/__snapshots__/UploadButton.test.tsx.snap +0 -6
  58. package/src/helpers/constants.ts +5 -0
  59. package/src/helpers/utils.ts +13 -0
  60. package/src/index.test.tsx +65 -0
  61. package/src/index.tsx +16 -0
  62. package/src/locales/en.json +3 -1
  63. package/src/locales/it.json +2 -0
@@ -1,4 +1,4 @@
1
- import React, { useState, useRef } from 'react';
1
+ import React, { useState, useRef, useEffect } from 'react';
2
2
  import cx from 'classnames';
3
3
  import { UploadIcon } from '../../icons/Upload';
4
4
  import Spin from '../../ui/Spin';
@@ -7,14 +7,9 @@ import { DocumentIcon } from '../../icons/Document';
7
7
  import CloseIcon from '../../icons/Close';
8
8
  import Button from '../../ui/Button';
9
9
  import Modal from '../../ui/Modal';
10
+ import { MAX_DOCUMENT_CONTENT_LENGTH, MAX_TOTAL_MESSAGE_PAYLOAD } from '../../../helpers/constants';
10
11
 
11
12
  // Types
12
- type UploadError = {
13
- message: string;
14
- severity: 'error' | 'warning' | 'info';
15
- fileId?: string;
16
- };
17
-
18
13
  type PreviewFile = {
19
14
  name: string;
20
15
  id: string;
@@ -26,8 +21,6 @@ type PreviewFile = {
26
21
  };
27
22
 
28
23
  // Constants
29
- const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
30
- const MAX_TEXT_LENGTH = 100000; // 100,000 characters
31
24
  const PDF_JS_VERSION = '3.11.174';
32
25
  const WORKER_URL = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${PDF_JS_VERSION}/pdf.worker.min.js`;
33
26
  const PDF_JS_URL = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${PDF_JS_VERSION}/pdf.min.js`;
@@ -46,68 +39,63 @@ interface UploadDocumentsProps {
46
39
  setDocumentPreviewFiles: (files: { name: string; id: string; content: string; mimeType: string }[]) => void;
47
40
  maxDocuments?: number;
48
41
  documentPreviewFiles: any;
42
+ onLoadingChange?: (loading: boolean) => void;
43
+ onDocumentError?: (error: { message: string; severity: 'error' | 'warning' | 'info' }) => void;
44
+ onValidateFile?: (file: File) => boolean;
45
+ onValidatePayloadSize?: (newDocuments: { name: string; id: string; content: string; mimeType: string }[]) => boolean;
49
46
  }
50
47
 
51
48
  const UploadDocuments: React.FC<UploadDocumentsProps> = ({
52
49
  setDocumentPreviewFiles,
53
50
  maxDocuments,
54
51
  documentPreviewFiles,
52
+ onLoadingChange,
53
+ onDocumentError,
54
+ onValidateFile,
55
+ onValidatePayloadSize,
55
56
  }) => {
56
57
  // State
57
58
  const [isLoading, setIsLoading] = useState(false);
58
- const [errors, setErrors] = useState<UploadError[]>([]);
59
59
  const [selectedFile, setSelectedFile] = useState<PreviewFile | null>(null);
60
60
 
61
61
  // Refs
62
62
  const documentInputRef = useRef<HTMLInputElement>(null);
63
63
 
64
- // Error handling
65
- const clearErrors = () => setErrors([]);
66
-
67
- const removeError = (errorMessage: string) => {
68
- setErrors(prev => prev.filter(e => e.message !== errorMessage));
69
- };
70
-
71
- const addError = (error: UploadError) => {
72
- setErrors(prev => [...prev, error]);
73
- setTimeout(() => removeError(error.message), 5000);
74
- };
64
+ // Update loading state in parent component
65
+ useEffect(() => {
66
+ if (onLoadingChange) {
67
+ onLoadingChange(isLoading);
68
+ }
69
+ }, [isLoading, onLoadingChange]);
75
70
 
76
71
  // Document upload
77
72
  const validateDocumentFile = (file: File): boolean => {
78
- const fileExt = `.${file.name.split('.').pop()?.toLowerCase()}`;
79
- const ALLOWED_FILE_TYPES = ['.pdf', '.txt', '.json', '.xlsx', '.csv', '.md']; // Added .md
80
-
81
- if (!ALLOWED_FILE_TYPES.includes(fileExt)) {
82
- addError({
83
- message: `File type "${fileExt}" is not supported. Please use: ${ALLOWED_FILE_TYPES.join(', ')}`,
84
- severity: 'error',
85
- fileId: file.name,
86
- });
87
- return false;
73
+ if (onValidateFile) {
74
+ return onValidateFile(file);
88
75
  }
76
+ return true;
77
+ };
89
78
 
90
- if (file.size > MAX_FILE_SIZE) {
91
- addError({
92
- message: `File "${file.name}" exceeds ${MAX_FILE_SIZE / 1024 / 1024}MB limit`,
93
- severity: 'error',
94
- fileId: file.name,
95
- });
96
- return false;
79
+ // Validate total payload size
80
+ const validatePayloadSize = (newDocuments: { name: string; id: string; content: string; mimeType: string }[]): boolean => {
81
+ if (onValidatePayloadSize) {
82
+ return onValidatePayloadSize(newDocuments);
97
83
  }
98
-
99
84
  return true;
100
85
  };
101
86
 
102
87
  const extractTextFromPDF = async (file: File): Promise<string> => {
88
+ console.log('Extracting text from PDF:', file.name);
103
89
  try {
104
90
  // Load PDF.js if not already loaded
105
91
  if (!window.pdfjsLib) {
92
+ console.log('Loading PDF.js library...');
106
93
  await new Promise((resolve, reject) => {
107
94
  const script = document.createElement('script');
108
95
  script.src = PDF_JS_URL;
109
96
  script.onload = () => {
110
97
  window.pdfjsLib.GlobalWorkerOptions.workerSrc = WORKER_URL;
98
+ console.log('PDF.js loaded successfully');
111
99
  resolve(true);
112
100
  };
113
101
  script.onerror = reject;
@@ -118,10 +106,12 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
118
106
  // Extract text from PDF
119
107
  const arrayBuffer = await file.arrayBuffer();
120
108
  const pdf = await window.pdfjsLib.getDocument({ data: arrayBuffer }).promise;
109
+ console.log('PDF loaded, pages:', pdf.numPages);
121
110
  let text = '';
122
111
 
123
112
  // Iterate through each page and extract text
124
113
  for (let i = 1; i <= pdf.numPages; i++) {
114
+ console.log('Processing page', i);
125
115
  const page = await pdf.getPage(i);
126
116
  const content = await page.getTextContent();
127
117
  const pageText = content.items
@@ -131,15 +121,19 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
131
121
  text += pageText + '\n';
132
122
  }
133
123
 
124
+ console.log('PDF text extraction complete');
134
125
  return text;
135
126
  } catch (error) {
127
+ console.error('PDF extraction failed:', error);
136
128
  throw new Error(`PDF extraction failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
137
129
  }
138
130
  };
139
131
 
140
132
  const extractTextFromXLSX = async (file: File): Promise<string> => {
133
+ console.log('Extracting text from XLSX:', file.name);
141
134
  try {
142
135
  if (!window.XLSX) {
136
+ console.log('Loading XLSX library...');
143
137
  await new Promise((resolve, reject) => {
144
138
  const script = document.createElement('script');
145
139
  script.src = XLSX_URL;
@@ -147,6 +141,7 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
147
141
  script.onerror = reject;
148
142
  document.head.appendChild(script);
149
143
  });
144
+ console.log('XLSX library loaded successfully');
150
145
  }
151
146
 
152
147
  const arrayBuffer = await file.arrayBuffer();
@@ -157,9 +152,11 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
157
152
  cellText: true,
158
153
  cellDates: true,
159
154
  });
155
+ console.log('XLSX workbook loaded, sheets:', workbook.SheetNames);
160
156
 
161
157
  let text = '';
162
158
  for (const sheetName of workbook.SheetNames) {
159
+ console.log('Processing sheet:', sheetName);
163
160
  const worksheet = workbook.Sheets[sheetName];
164
161
  const data = window.XLSX.utils.sheet_to_json(worksheet, { header: 1, raw: false });
165
162
 
@@ -191,13 +188,16 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
191
188
  text += `Sheet: ${sheetName}\n${formattedText.join('\n')}\n\n`;
192
189
  }
193
190
 
191
+ console.log('XLSX text extraction complete');
194
192
  return text;
195
193
  } catch (error) {
194
+ console.error('XLSX extraction failed:', error);
196
195
  throw new Error(`XLSX extraction failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
197
196
  }
198
197
  };
199
198
 
200
199
  const processDocumentFile = async (file: File): Promise<string | null> => {
200
+ console.log('Processing document file:', file.name);
201
201
  const fileExt = file.name.split('.').pop()?.toLowerCase() || '';
202
202
 
203
203
  try {
@@ -211,62 +211,93 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
211
211
  text = await extractTextFromXLSX(file);
212
212
  }
213
213
 
214
- if (text && text.length > MAX_TEXT_LENGTH) {
215
- addError({
216
- message: `File "${file.name}" content exceeds ${MAX_TEXT_LENGTH} characters and was truncated`,
214
+ if (text && text.length > MAX_DOCUMENT_CONTENT_LENGTH) {
215
+ console.warn('Document content exceeds length limit:', text.length, '>', MAX_DOCUMENT_CONTENT_LENGTH);
216
+ onDocumentError?.({
217
+ message: `File "${file.name}" content exceeds ${MAX_DOCUMENT_CONTENT_LENGTH} characters and was truncated`,
217
218
  severity: 'warning',
218
- fileId: file.name,
219
219
  });
220
- text = text.substring(0, MAX_TEXT_LENGTH) + "\n\n[Content truncated due to size limits]";
220
+ text = text.substring(0, MAX_DOCUMENT_CONTENT_LENGTH) + "\n\n[Content truncated due to size limits]";
221
221
  }
222
222
 
223
+ console.log('Document processing complete');
223
224
  return text;
224
225
  } catch (error) {
226
+ console.error('Document processing failed:', error);
225
227
  throw new Error(`Failed to process "${file.name}": ${error instanceof Error ? error.message : 'Unknown error'}`);
226
228
  }
227
229
  };
228
230
 
229
231
  const handleDocumentUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
232
+ console.log('Document upload started');
230
233
  const files = Array.from(e.target.files || []);
231
234
  if (files.length === 0) return;
232
235
 
233
- // Only process the first file
234
- const file = files[0];
235
-
236
236
  setIsLoading(true);
237
- clearErrors();
238
237
 
239
- if (!validateDocumentFile(file)) {
240
- setIsLoading(false);
241
- if (documentInputRef.current) {
242
- documentInputRef.current.value = '';
238
+ // Process each file
239
+ const processedFiles: { name: string; id: string; content: string; mimeType: string }[] = [];
240
+
241
+ for (const file of files) {
242
+ console.log('Processing file:', file.name);
243
+ if (!validateDocumentFile(file)) {
244
+ continue;
243
245
  }
244
- return;
245
- }
246
246
 
247
- const fileId = Math.random().toString(36).substr(2, 9);
248
-
249
- try {
250
- const text = await processDocumentFile(file);
247
+ const fileId = Math.random().toString(36).substr(2, 9);
251
248
 
252
- if (text) {
249
+ try {
250
+ const text = await processDocumentFile(file);
253
251
 
254
- // Replace document preview files with the new one
255
- setDocumentPreviewFiles([{
256
- name: file.name,
257
- id: fileId,
258
- content: text,
259
- mimeType: file.type,
260
- }]);
252
+ if (text) {
253
+ processedFiles.push({
254
+ name: file.name,
255
+ id: fileId,
256
+ content: text,
257
+ mimeType: file.type,
258
+ });
259
+ }
260
+ } catch (error) {
261
+ console.error('File processing error:', error);
262
+ onDocumentError?.({
263
+ message: `${error instanceof Error ? error.message : 'Unknown error'}`,
264
+ severity: 'error',
265
+ });
261
266
  }
262
- } catch (error) {
263
- addError({
264
- message: `${error instanceof Error ? error.message : 'Unknown error'}`,
265
- severity: 'error',
266
- fileId: file.name,
267
- });
268
267
  }
269
268
 
269
+ // Add new documents to existing ones
270
+ if (processedFiles.length > 0) {
271
+ console.log('Successfully processed files:', processedFiles.length);
272
+ // Validate total payload size
273
+ if (!validatePayloadSize(processedFiles)) {
274
+ setIsLoading(false);
275
+ if (documentInputRef.current) {
276
+ documentInputRef.current.value = '';
277
+ }
278
+ return;
279
+ }
280
+
281
+ const existingDocuments = documentPreviewFiles.filter(
282
+ (file: any) => file.type === 'document'
283
+ );
284
+ const existingImages = documentPreviewFiles.filter(
285
+ (file: any) => file.type === 'image'
286
+ );
287
+
288
+ console.log('existingDocuments', existingDocuments);
289
+ console.log('processedFiles', processedFiles);
290
+ console.log('existingImages', existingImages);
291
+ setDocumentPreviewFiles([
292
+ ...existingDocuments,
293
+ ...processedFiles.map(file => ({
294
+ ...file,
295
+ type: 'document'
296
+ })),
297
+ ]);
298
+ }
299
+
300
+ console.log('Document upload complete');
270
301
  setIsLoading(false);
271
302
  if (documentInputRef.current) {
272
303
  documentInputRef.current.value = '';
@@ -275,6 +306,7 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
275
306
 
276
307
  return (
277
308
  <div className="memori--document-upload-wrapper">
309
+
278
310
  {/* Hidden file input */}
279
311
  <input
280
312
  ref={documentInputRef}
@@ -293,7 +325,7 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
293
325
  'memori-share-button--button',
294
326
  'memori--conversation-button',
295
327
  'memori--document-upload-button',
296
- { 'memori--error': errors.length > 0 }
328
+ { 'memori--error': false } // Removed errors.length > 0
297
329
  )}
298
330
  onClick={() => documentInputRef.current?.click()}
299
331
  disabled={isLoading || (maxDocuments && documentPreviewFiles.filter((file: any) => file.type !== 'image').length >= maxDocuments) || false}
@@ -331,20 +363,6 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
331
363
  </div>
332
364
  </Modal>
333
365
 
334
- {/* Error messages container */}
335
- <div className="memori--error-message-container">
336
- {errors.map((error, index) => (
337
- <Alert
338
- key={`${error.message}-${index}`}
339
- open={true}
340
- type={error.severity}
341
- title={'Upload notification'}
342
- description={error.message}
343
- onClose={() => removeError(error.message)}
344
- width="350px"
345
- />
346
- ))}
347
- </div>
348
366
  </div>
349
367
  );
350
368
  };
@@ -10,12 +10,6 @@ import { useTranslation } from 'react-i18next';
10
10
  import Button from '../../ui/Button';
11
11
 
12
12
  // Types
13
- type UploadError = {
14
- message: string;
15
- severity: 'error' | 'warning' | 'info';
16
- fileId?: string;
17
- };
18
-
19
13
  type PreviewFile = {
20
14
  name: string;
21
15
  id: string;
@@ -28,9 +22,6 @@ type PreviewFile = {
28
22
  title?: string;
29
23
  };
30
24
 
31
- // Constants
32
- const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
33
-
34
25
  // Props interface
35
26
  interface UploadImagesProps {
36
27
  authToken?: string;
@@ -42,10 +33,10 @@ interface UploadImagesProps {
42
33
  onLoadingChange?: (loading: boolean) => void;
43
34
  maxImages?: number;
44
35
  memoriID?: string;
36
+ onImageError?: (error: { message: string; severity: 'error' | 'warning' | 'info' }) => void;
37
+ onValidateImageFile?: (file: File) => boolean;
45
38
  }
46
39
 
47
- const ALLOWED_FILE_TYPES = ['.jpg', '.jpeg', '.png'];
48
-
49
40
  const UploadImages: React.FC<UploadImagesProps> = ({
50
41
  authToken = '',
51
42
  sessionID = '',
@@ -56,6 +47,8 @@ const UploadImages: React.FC<UploadImagesProps> = ({
56
47
  onLoadingChange,
57
48
  maxImages = 5,
58
49
  memoriID = '',
50
+ onImageError,
51
+ onValidateImageFile,
59
52
  }) => {
60
53
  const { t, i18n } = useTranslation();
61
54
  // Client
@@ -66,7 +59,6 @@ const UploadImages: React.FC<UploadImagesProps> = ({
66
59
 
67
60
  // State
68
61
  const [isLoading, setIsLoading] = useState(false);
69
- const [errors, setErrors] = useState<UploadError[]>([]);
70
62
  const [selectedFile, setSelectedFile] = useState<File | null>(null);
71
63
  const [filePreview, setFilePreview] = useState<string | null>(null);
72
64
  const [imageTitle, setImageTitle] = useState('');
@@ -82,18 +74,6 @@ const UploadImages: React.FC<UploadImagesProps> = ({
82
74
  }
83
75
  }, [isLoading, onLoadingChange]);
84
76
 
85
- // Error handling
86
- const clearErrors = () => setErrors([]);
87
-
88
- const removeError = (errorMessage: string) => {
89
- setErrors(prev => prev.filter(e => e.message !== errorMessage));
90
- };
91
-
92
- const addError = (error: UploadError) => {
93
- setErrors(prev => [...prev, error]);
94
- setTimeout(() => removeError(error.message), 5000);
95
- };
96
-
97
77
  // Check current image count
98
78
  const currentImageCount = documentPreviewFiles.filter(
99
79
  (file: any) => file.type === 'image'
@@ -101,33 +81,9 @@ const UploadImages: React.FC<UploadImagesProps> = ({
101
81
 
102
82
  // Image upload
103
83
  const validateImageFile = (file: File): boolean => {
104
- const fileExt = `.${file.name.split('.').pop()?.toLowerCase()}`;
105
-
106
- if (
107
- !ALLOWED_FILE_TYPES.includes(fileExt) &&
108
- !file.type.startsWith('image/')
109
- ) {
110
- addError({
111
- message: `File type "${fileExt}" is not supported. Please use: ${ALLOWED_FILE_TYPES.join(
112
- ', '
113
- )}`,
114
- severity: 'error',
115
- fileId: file.name,
116
- });
117
- return false;
84
+ if (onValidateImageFile) {
85
+ return onValidateImageFile(file);
118
86
  }
119
-
120
- if (file.size > DEFAULT_MAX_FILE_SIZE) {
121
- addError({
122
- message: `File "${file.name}" exceeds ${
123
- DEFAULT_MAX_FILE_SIZE / 1024 / 1024
124
- }MB limit`,
125
- severity: 'error',
126
- fileId: file.name,
127
- });
128
- return false;
129
- }
130
-
131
87
  return true;
132
88
  };
133
89
 
@@ -137,7 +93,7 @@ const UploadImages: React.FC<UploadImagesProps> = ({
137
93
 
138
94
  // Check if adding this file would exceed the limit
139
95
  if (currentImageCount >= maxImages) {
140
- addError({
96
+ onImageError?.({
141
97
  message:
142
98
  t('upload.maxImagesReached') ??
143
99
  `Maximum ${maxImages} images allowed.`,
@@ -146,8 +102,6 @@ const UploadImages: React.FC<UploadImagesProps> = ({
146
102
  return;
147
103
  }
148
104
 
149
- clearErrors();
150
-
151
105
  const file = files[0]; // Only handle the first file
152
106
 
153
107
  if (!validateImageFile(file)) {
@@ -261,14 +215,13 @@ const UploadImages: React.FC<UploadImagesProps> = ({
261
215
  ]
262
216
  );
263
217
  } catch (error) {
264
- addError({
218
+ onImageError?.({
265
219
  message: t('upload.uploadFailed') ?? 'Upload failed',
266
220
  severity: 'error',
267
- fileId: selectedFile.name,
268
221
  });
269
222
  }
270
223
  } else {
271
- addError({
224
+ onImageError?.({
272
225
  message:
273
226
  t('upload.apiClientNotConfigured') ??
274
227
  'API client not configured properly for media upload',
@@ -280,20 +233,18 @@ const UploadImages: React.FC<UploadImagesProps> = ({
280
233
  };
281
234
 
282
235
  reader.onerror = () => {
283
- addError({
236
+ onImageError?.({
284
237
  message: t('upload.fileReadingFailed') ?? 'File reading failed',
285
238
  severity: 'error',
286
- fileId: selectedFile.name,
287
239
  });
288
240
  setIsLoading(false);
289
241
  };
290
242
 
291
243
  reader.readAsDataURL(selectedFile);
292
244
  } catch (error) {
293
- addError({
245
+ onImageError?.({
294
246
  message: t('upload.uploadFailed') ?? 'Upload failed',
295
247
  severity: 'error',
296
- fileId: selectedFile.name,
297
248
  });
298
249
  setIsLoading(false);
299
250
  }
@@ -312,7 +263,7 @@ const UploadImages: React.FC<UploadImagesProps> = ({
312
263
  <input
313
264
  ref={imageInputRef}
314
265
  type="file"
315
- accept={ALLOWED_FILE_TYPES.join(',')}
266
+ accept=".jpg,.jpeg,.png"
316
267
  className="memori--upload-file-input"
317
268
  onChange={handleImageUpload}
318
269
  disabled={
@@ -404,21 +355,6 @@ const UploadImages: React.FC<UploadImagesProps> = ({
404
355
  </div>
405
356
  </div>
406
357
  </Modal>
407
-
408
- {/* Error messages container */}
409
- <div className="memori--error-message-container">
410
- {errors.map((error, index) => (
411
- <Alert
412
- key={`${error.message}-${index}`}
413
- open={true}
414
- type={error.severity}
415
- title={'Upload notification'}
416
- description={error.message}
417
- onClose={() => removeError(error.message)}
418
- width="350px"
419
- />
420
- ))}
421
- </div>
422
358
  </div>
423
359
  );
424
360
  };
@@ -96,9 +96,6 @@ exports[`renders UploadButton unchanged 1`] = `
96
96
  />
97
97
  </svg>
98
98
  </button>
99
- <div
100
- class="memori--error-message-container"
101
- />
102
99
  </div>
103
100
  </div>
104
101
  <div
@@ -149,9 +146,6 @@ exports[`renders UploadButton unchanged 1`] = `
149
146
  />
150
147
  </svg>
151
148
  </button>
152
- <div
153
- class="memori--error-message-container"
154
- />
155
149
  </div>
156
150
  </div>
157
151
  <div
@@ -186,3 +186,8 @@ export const boardOfExpertsLoadingSentences: {
186
186
 
187
187
  export const MAX_MSG_CHARS = 4000;
188
188
  export const MAX_MSG_WORDS = 300;
189
+
190
+ // Document upload limits
191
+ export const MAX_DOCUMENTS_PER_MESSAGE = 5;
192
+ export const MAX_TOTAL_MESSAGE_PAYLOAD = 100000; // 100KB total payload limit
193
+ export const MAX_DOCUMENT_CONTENT_LENGTH = 100000; // 100KB per document content
@@ -201,6 +201,19 @@ export const stripOutputTags = (text: string): string => {
201
201
  return stripOutputTags(strippedText);
202
202
  };
203
203
 
204
+ export const stripReasoningTags = (text: string) => {
205
+ const reasoningTagRegex = /<think.*?<\/think>/gs;
206
+
207
+ if (!reasoningTagRegex.test(text)) {
208
+ return text;
209
+ }
210
+
211
+ const strippedText = text.replace(reasoningTagRegex, '');
212
+
213
+ // Recursively strip nested reasoning tags
214
+ return stripOutputTags(strippedText);
215
+ };
216
+
204
217
  export const stripHTML = (text: string) => {
205
218
  const el = document.createElement('div');
206
219
  el.innerHTML = text;
@@ -0,0 +1,65 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import { memori, tenant, integration } from './mocks/data';
4
+ import Memori from './index';
5
+
6
+ // Mock window.location
7
+ const mockLocation = {
8
+ hostname: 'localhost',
9
+ };
10
+
11
+ Object.defineProperty(window, 'location', {
12
+ value: mockLocation,
13
+ writable: true,
14
+ });
15
+
16
+ it('renders client', () => {
17
+ const { container } = render(
18
+ <Memori
19
+ memoriID={memori.memoriID}
20
+ ownerUserID={memori.ownerUserID}
21
+ tenantID={tenant.tenantID}
22
+ integration={{
23
+ ...integration,
24
+ customData: JSON.stringify({}),
25
+ }}
26
+ />
27
+ );
28
+ expect(container).toMatchSnapshot();
29
+ });
30
+
31
+ it('renders client with whiteListedDomains on allowed domains', () => {
32
+ mockLocation.hostname = 'memori.ai';
33
+ const { container } = render(
34
+ <Memori
35
+ memoriID={memori.memoriID}
36
+ ownerUserID={memori.ownerUserID}
37
+ tenantID={tenant.tenantID}
38
+ integration={{
39
+ ...integration,
40
+ customData: JSON.stringify({
41
+ whiteListedDomains: ['localhost', 'memori.ai'],
42
+ }),
43
+ }}
44
+ />
45
+ );
46
+ expect(container).toMatchSnapshot();
47
+ });
48
+
49
+ it('renders client with whiteListedDomains on not allowed domains', () => {
50
+ mockLocation.hostname = 'aisuru.com';
51
+ const { container } = render(
52
+ <Memori
53
+ memoriID={memori.memoriID}
54
+ ownerUserID={memori.ownerUserID}
55
+ tenantID={tenant.tenantID}
56
+ integration={{
57
+ ...integration,
58
+ customData: JSON.stringify({
59
+ whiteListedDomains: ['localhost', 'memori.ai'],
60
+ }),
61
+ }}
62
+ />
63
+ );
64
+ expect(container).toMatchSnapshot();
65
+ });
package/src/index.tsx CHANGED
@@ -259,6 +259,22 @@ const Memori: React.FC<Props> = ({
259
259
  const layoutIntegrationConfig = safeParseJSON(
260
260
  layoutIntegration?.customData ?? '{}'
261
261
  );
262
+
263
+ const whiteListedDomains = layoutIntegrationConfig.whiteListedDomains;
264
+ if (whiteListedDomains) {
265
+ // check if we are client side
266
+ if (typeof window !== 'undefined') {
267
+ // check if the current domain is in the whiteListedDomains with Regex
268
+ if (
269
+ !whiteListedDomains.some((domain: string) =>
270
+ new RegExp(domain).test(window.location.hostname)
271
+ )
272
+ ) {
273
+ return null;
274
+ }
275
+ }
276
+ }
277
+
262
278
  const initialContextVars =
263
279
  context ?? getParsedContext(layoutIntegrationConfig.contextVars);
264
280
  const initialQuestionLayout =