@memori.ai/memori-react 8.38.5 → 8.38.6

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 (86) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/components/Chat/Chat.js +11 -23
  3. package/dist/components/Chat/Chat.js.map +1 -1
  4. package/dist/components/ChatHistoryDrawer/ChatResumeDrawer.css +1 -1
  5. package/dist/components/ChatHistoryDrawer/ChatResumeDrawer.d.ts +0 -1
  6. package/dist/components/ChatHistoryDrawer/ChatResumeDrawer.js +0 -1
  7. package/dist/components/ChatHistoryDrawer/ChatResumeDrawer.js.map +1 -1
  8. package/dist/components/FilePreview/FilePreview.js +20 -2
  9. package/dist/components/FilePreview/FilePreview.js.map +1 -1
  10. package/dist/components/MediaWidget/MediaItemWidget.js +9 -6
  11. package/dist/components/MediaWidget/MediaItemWidget.js.map +1 -1
  12. package/dist/components/MediaWidget/MediaItemWidget.utils.d.ts +1 -0
  13. package/dist/components/MediaWidget/MediaItemWidget.utils.js +27 -7
  14. package/dist/components/MediaWidget/MediaItemWidget.utils.js.map +1 -1
  15. package/dist/components/MobileSessionPanel/MobileSessionPanel.css +30 -4
  16. package/dist/components/MobileSessionPanel/MobileSessionPanel.d.ts +3 -2
  17. package/dist/components/MobileSessionPanel/MobileSessionPanel.js +16 -13
  18. package/dist/components/MobileSessionPanel/MobileSessionPanel.js.map +1 -1
  19. package/dist/components/UploadButton/UploadButton.js +20 -6
  20. package/dist/components/UploadButton/UploadButton.js.map +1 -1
  21. package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js +45 -8
  22. package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
  23. package/dist/components/layouts/WebsiteAssistant/WebsiteAssistant.js +5 -1
  24. package/dist/components/layouts/WebsiteAssistant/WebsiteAssistant.js.map +1 -1
  25. package/dist/components/layouts/WebsiteAssistant/website-assistant.css +1 -3
  26. package/dist/components/layouts/fullpage.css +55 -21
  27. package/dist/helpers/constants.d.ts +3 -0
  28. package/dist/helpers/constants.js +24 -1
  29. package/dist/helpers/constants.js.map +1 -1
  30. package/dist/helpers/usePressTooltip.d.ts +13 -0
  31. package/dist/helpers/usePressTooltip.js +23 -0
  32. package/dist/helpers/usePressTooltip.js.map +1 -0
  33. package/dist/helpers/utils.d.ts +15 -0
  34. package/dist/helpers/utils.js +45 -1
  35. package/dist/helpers/utils.js.map +1 -1
  36. package/dist/version.d.ts +1 -1
  37. package/dist/version.js +1 -1
  38. package/esm/components/Chat/Chat.js +12 -24
  39. package/esm/components/Chat/Chat.js.map +1 -1
  40. package/esm/components/ChatHistoryDrawer/ChatResumeDrawer.css +1 -1
  41. package/esm/components/ChatHistoryDrawer/ChatResumeDrawer.d.ts +0 -1
  42. package/esm/components/ChatHistoryDrawer/ChatResumeDrawer.js +0 -1
  43. package/esm/components/ChatHistoryDrawer/ChatResumeDrawer.js.map +1 -1
  44. package/esm/components/FilePreview/FilePreview.js +22 -4
  45. package/esm/components/FilePreview/FilePreview.js.map +1 -1
  46. package/esm/components/MediaWidget/MediaItemWidget.js +11 -8
  47. package/esm/components/MediaWidget/MediaItemWidget.js.map +1 -1
  48. package/esm/components/MediaWidget/MediaItemWidget.utils.d.ts +1 -0
  49. package/esm/components/MediaWidget/MediaItemWidget.utils.js +25 -6
  50. package/esm/components/MediaWidget/MediaItemWidget.utils.js.map +1 -1
  51. package/esm/components/MobileSessionPanel/MobileSessionPanel.css +30 -4
  52. package/esm/components/MobileSessionPanel/MobileSessionPanel.d.ts +3 -2
  53. package/esm/components/MobileSessionPanel/MobileSessionPanel.js +17 -14
  54. package/esm/components/MobileSessionPanel/MobileSessionPanel.js.map +1 -1
  55. package/esm/components/UploadButton/UploadButton.js +20 -6
  56. package/esm/components/UploadButton/UploadButton.js.map +1 -1
  57. package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js +45 -8
  58. package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
  59. package/esm/components/layouts/WebsiteAssistant/WebsiteAssistant.js +5 -1
  60. package/esm/components/layouts/WebsiteAssistant/WebsiteAssistant.js.map +1 -1
  61. package/esm/components/layouts/WebsiteAssistant/website-assistant.css +1 -3
  62. package/esm/components/layouts/fullpage.css +55 -21
  63. package/esm/helpers/constants.d.ts +3 -0
  64. package/esm/helpers/constants.js +23 -0
  65. package/esm/helpers/constants.js.map +1 -1
  66. package/esm/helpers/usePressTooltip.d.ts +13 -0
  67. package/esm/helpers/usePressTooltip.js +20 -0
  68. package/esm/helpers/usePressTooltip.js.map +1 -0
  69. package/esm/helpers/utils.d.ts +15 -0
  70. package/esm/helpers/utils.js +39 -0
  71. package/esm/helpers/utils.js.map +1 -1
  72. package/esm/version.d.ts +1 -1
  73. package/esm/version.js +1 -1
  74. package/package.json +1 -1
  75. package/src/components/Chat/Chat.tsx +19 -44
  76. package/src/components/FilePreview/FilePreview.tsx +26 -4
  77. package/src/components/MediaWidget/MediaItemWidget.tsx +19 -7
  78. package/src/components/MediaWidget/MediaItemWidget.utils.test.ts +45 -2
  79. package/src/components/MediaWidget/MediaItemWidget.utils.ts +37 -6
  80. package/src/components/UploadButton/UploadButton.tsx +27 -12
  81. package/src/components/UploadButton/UploadDocuments/UploadDocuments.tsx +48 -9
  82. package/src/components/UploadButton/__snapshots__/UploadButton.test.tsx.snap +2 -2
  83. package/src/helpers/constants.ts +29 -1
  84. package/src/helpers/utils.test.ts +101 -0
  85. package/src/helpers/utils.ts +66 -0
  86. package/src/version.ts +1 -1
@@ -5,6 +5,8 @@ import { DocumentIcon } from '../../icons/Document';
5
5
  import Modal from '../../ui/Modal';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import memoriApiClient from '@memori.ai/memori-api-client';
8
+ import { officeNativeExtensions } from '../../../helpers/constants';
9
+ import { isOfficeNativeFilename } from '../../../helpers/utils';
8
10
  // Types
9
11
  type PreviewFile = {
10
12
  name: string;
@@ -235,17 +237,20 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
235
237
 
236
238
  const processDocumentFile = async (
237
239
  file: File
238
- ): Promise<{ text: string | null }> => {
239
- const fileExt = file.name.split('.').pop()?.toLowerCase() || '';
240
+ ): Promise<{ text: string | null; uploadAsOriginal?: boolean }> => {
241
+ if (isOfficeNativeFilename(file.name)) {
242
+ return { text: null, uploadAsOriginal: true };
243
+ }
240
244
 
245
+ const ext = file.name.split('.').pop()?.toLowerCase() || '';
241
246
  try {
242
247
  let text: string | null = null;
243
248
 
244
- if (fileExt === 'pdf') {
249
+ if (ext === 'pdf') {
245
250
  text = await extractTextFromPDF(file);
246
- } else if (['txt', 'md', 'json', 'csv', 'html'].includes(fileExt)) {
251
+ } else if (['txt', 'md', 'json', 'csv', 'html'].includes(ext)) {
247
252
  text = await file.text();
248
- } else if (fileExt === 'xlsx') {
253
+ } else if (ext === 'xlsx') {
249
254
  text = await extractTextFromXLSX(file);
250
255
  }
251
256
 
@@ -295,7 +300,12 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
295
300
  throw new Error(response.resultMessage || 'Upload failed');
296
301
  }
297
302
 
298
- return response.asset?.assetURL;
303
+ const assetURL = response.asset?.assetURL;
304
+ if (!assetURL) {
305
+ throw new Error('Upload failed: missing asset URL');
306
+ }
307
+
308
+ return assetURL;
299
309
  };
300
310
 
301
311
  const handleDocumentUpload = async (
@@ -360,9 +370,38 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
360
370
  const fileId = Math.random().toString(36).substr(2, 9);
361
371
 
362
372
  try {
363
- const { text } = await processDocumentFile(file);
373
+ const { text, uploadAsOriginal } = await processDocumentFile(file);
374
+
375
+ if (uploadAsOriginal) {
376
+ // Office native format: upload the original file as asset, no text extraction
377
+ let assetUrl: string | undefined;
378
+ try {
379
+ assetUrl = await uploadAssetFile(file);
380
+ } catch (uploadError) {
381
+ console.error('Office asset upload failed:', uploadError);
382
+ onDocumentError?.({
383
+ message: t('upload.officeAssetUploadFailed', {
384
+ fileName: file.name,
385
+ defaultValue: `"${file.name}" could not be uploaded and was not added.`,
386
+ }),
387
+ severity: 'error',
388
+ });
389
+ }
364
390
 
365
- if (text) {
391
+ if (!assetUrl) {
392
+ activeCount--;
393
+ onLoadingChange?.(true, activeCount);
394
+ continue;
395
+ }
396
+
397
+ processedFiles.push({
398
+ name: file.name,
399
+ id: fileId,
400
+ content: '',
401
+ mimeType: file.type,
402
+ textAssetUrl: assetUrl,
403
+ });
404
+ } else if (text) {
366
405
  const baseName = file.name.replace(/\.[^/.]+$/, '') || file.name;
367
406
  const textFile = new File([text], `${baseName}.txt`, {
368
407
  type: 'text/plain',
@@ -429,7 +468,7 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
429
468
  <input
430
469
  ref={documentInputRef}
431
470
  type="file"
432
- accept=".pdf,.txt,.md,.json,.xlsx,.csv,.html"
471
+ accept={`.pdf,.txt,.md,.json,.xlsx,.csv,.html,${officeNativeExtensions.join(',')}`}
433
472
  multiple
434
473
  className="memori--upload-file-input"
435
474
  onChange={handleDocumentUpload}
@@ -6,7 +6,7 @@ exports[`renders UploadButton unchanged 1`] = `
6
6
  class="memori--unified-upload-wrapper"
7
7
  >
8
8
  <input
9
- accept=".jpg,.jpeg,.png,.pdf,.txt,.json,.xlsx,.csv,.md,.html"
9
+ accept=".jpg,.jpeg,.png,.pdf,.txt,.json,.xlsx,.csv,.md,.html,.doc,.docx,.xls,.xltx,.potx"
10
10
  class="memori--upload-file-input"
11
11
  multiple=""
12
12
  style="display: none;"
@@ -52,7 +52,7 @@ exports[`renders UploadButton unchanged 1`] = `
52
52
  class="memori--document-upload-wrapper"
53
53
  >
54
54
  <input
55
- accept=".pdf,.txt,.md,.json,.xlsx,.csv,.html"
55
+ accept=".pdf,.txt,.md,.json,.xlsx,.csv,.html,.doc,.docx,.xls,.xltx,.potx"
56
56
  class="memori--upload-file-input"
57
57
  multiple=""
58
58
  type="file"
@@ -35,7 +35,7 @@ export const getGroupedChatLanguages = () => {
35
35
  popularLanguageCodes.includes(lang.value)
36
36
  );
37
37
  const all = chatLanguages.filter(lang => !popularLanguageCodes.includes(lang.value));
38
- return {
38
+ return {
39
39
  popular,
40
40
  all,
41
41
  };
@@ -43,6 +43,15 @@ export const getGroupedChatLanguages = () => {
43
43
 
44
44
  export const uiLanguages = ['en', 'it', 'fr', 'es', 'de'];
45
45
 
46
+ /** Extensions uploaded as original Office binaries (no text extraction) */
47
+ export const officeNativeExtensions = [
48
+ '.doc',
49
+ '.docx',
50
+ '.xls',
51
+ '.xltx',
52
+ '.potx',
53
+ ] as const;
54
+
46
55
  export const allowedMediaTypes = [
47
56
  'image/jpeg',
48
57
  'image/png',
@@ -62,6 +71,25 @@ export const allowedMediaTypes = [
62
71
  'model/gltf-binary',
63
72
  ];
64
73
 
74
+ /** Short badge labels for Office document cards */
75
+ export const officeMimeShortLabels: Record<string, string> = {
76
+ 'application/msword': 'Word',
77
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'Word',
78
+ 'application/vnd.ms-excel': 'Excel',
79
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'Excel',
80
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.template': 'Excel',
81
+ 'application/vnd.openxmlformats-officedocument.presentationml.template': 'PPT',
82
+ };
83
+
84
+ export const officeExtensionShortLabels: Record<string, string> = {
85
+ DOC: 'Word',
86
+ DOCX: 'Word',
87
+ XLS: 'Excel',
88
+ XLSX: 'Excel',
89
+ XLTX: 'Excel',
90
+ POTX: 'PPT',
91
+ };
92
+
65
93
  export const anonTag = '👤';
66
94
 
67
95
  export const prismSyntaxLangs = [
@@ -4,6 +4,11 @@ import {
4
4
  stripMarkdown,
5
5
  stripOutputTags,
6
6
  escapeHTML,
7
+ extractAttachmentLinks,
8
+ extractAttachmentLink,
9
+ isAssetOnlyDocumentAttachment,
10
+ isOfficeNativeFilename,
11
+ parseDocumentAttachmentsFromMessage,
7
12
  } from './utils';
8
13
 
9
14
  describe('Utils/difference', () => {
@@ -141,6 +146,102 @@ describe('utils/stripOutputTags', () => {
141
146
  });
142
147
  });
143
148
 
149
+ describe('utils/attachment helpers', () => {
150
+ it('extracts multiple attachment links in order', () => {
151
+ const input = [
152
+ '<document_attachment filename="a.docx" type="application/vnd.openxmlformats-officedocument.wordprocessingml.document">',
153
+ '</document_attachment>',
154
+ '<attachment_link>',
155
+ 'https://assets.example.com/a.docx',
156
+ '</attachment_link>',
157
+ '<document_attachment filename="b.pdf" type="application/pdf">',
158
+ 'pdf text',
159
+ '</document_attachment>',
160
+ '<attachment_link>https://assets.example.com/b.txt</attachment_link>',
161
+ ].join('\n');
162
+
163
+ expect(extractAttachmentLinks(input)).toEqual([
164
+ 'https://assets.example.com/a.docx',
165
+ 'https://assets.example.com/b.txt',
166
+ ]);
167
+ });
168
+
169
+ it('extracts a single attachment link', () => {
170
+ const input =
171
+ '<attachment_link>\nhttps://assets.example.com/file.docx\n</attachment_link>';
172
+ expect(extractAttachmentLink(input)).toBe(
173
+ 'https://assets.example.com/file.docx'
174
+ );
175
+ });
176
+
177
+ it('detects office native filenames', () => {
178
+ expect(isOfficeNativeFilename('report.doc')).toBe(true);
179
+ expect(isOfficeNativeFilename('report.docx')).toBe(true);
180
+ expect(isOfficeNativeFilename('budget.xls')).toBe(true);
181
+ expect(isOfficeNativeFilename('template.XLTX')).toBe(true);
182
+ expect(isOfficeNativeFilename('slides.potx')).toBe(true);
183
+ expect(isOfficeNativeFilename('notes.pdf')).toBe(false);
184
+ expect(isOfficeNativeFilename('data.xlsx')).toBe(false);
185
+ });
186
+
187
+ it('detects asset-only document attachments', () => {
188
+ expect(
189
+ isAssetOnlyDocumentAttachment({
190
+ content: '',
191
+ url: 'https://assets.example.com/file.docx',
192
+ })
193
+ ).toBe(true);
194
+ expect(
195
+ isAssetOnlyDocumentAttachment({
196
+ content: 'extracted text',
197
+ url: 'https://assets.example.com/file.txt',
198
+ })
199
+ ).toBe(false);
200
+ });
201
+
202
+ it('pairs each document attachment with its adjacent link', () => {
203
+ const input = [
204
+ '<attachment_link>https://assets.example.com/orphan.txt</attachment_link>',
205
+ '<document_attachment filename="a.docx" type="application/vnd.openxmlformats-officedocument.wordprocessingml.document">',
206
+ '</document_attachment>',
207
+ '<attachment_link>https://assets.example.com/a.docx</attachment_link>',
208
+ '<document_attachment filename="b.pdf" type="application/pdf">',
209
+ 'pdf text',
210
+ '</document_attachment>',
211
+ '<attachment_link>https://assets.example.com/b.txt</attachment_link>',
212
+ ].join('\n');
213
+
214
+ expect(parseDocumentAttachmentsFromMessage(input)).toEqual([
215
+ {
216
+ filename: 'a.docx',
217
+ type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
218
+ content: '',
219
+ url: 'https://assets.example.com/a.docx',
220
+ },
221
+ {
222
+ filename: 'b.pdf',
223
+ type: 'application/pdf',
224
+ content: 'pdf text',
225
+ url: 'https://assets.example.com/b.txt',
226
+ },
227
+ ]);
228
+ });
229
+
230
+ it('returns an empty url when no adjacent attachment link exists', () => {
231
+ const input =
232
+ '<document_attachment filename="a.docx" type="application/vnd.openxmlformats-officedocument.wordprocessingml.document"></document_attachment>';
233
+
234
+ expect(parseDocumentAttachmentsFromMessage(input)).toEqual([
235
+ {
236
+ filename: 'a.docx',
237
+ type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
238
+ content: '',
239
+ url: '',
240
+ },
241
+ ]);
242
+ });
243
+ });
244
+
144
245
  describe('utils/parsing combined', () => {
145
246
  it('should remove output tag from real message', () => {
146
247
  const result = escapeHTML(
@@ -1,6 +1,7 @@
1
1
  import { useState, useEffect, useRef, useMemo } from 'react';
2
2
  import { Material, MeshStandardMaterial, SkinnedMesh } from 'three';
3
3
  import * as THREE from 'three';
4
+ import { officeNativeExtensions } from './constants';
4
5
 
5
6
  export const hasTouchscreen = (): boolean => {
6
7
  let hasTouchScreen = false;
@@ -237,6 +238,71 @@ export const stripMarkdown = (text: string) => {
237
238
  return text;
238
239
  };
239
240
 
241
+ export const OFFICE_NATIVE_EXTENSIONS = officeNativeExtensions;
242
+
243
+ export const isOfficeNativeFilename = (filename: string): boolean => {
244
+ const ext = `.${filename.split('.').pop()?.toLowerCase() || ''}`;
245
+ return (officeNativeExtensions as readonly string[]).includes(ext);
246
+ };
247
+
248
+ export type ParsedDocumentAttachment = {
249
+ filename: string;
250
+ type: string;
251
+ content: string;
252
+ url: string;
253
+ };
254
+
255
+ const DOCUMENT_ATTACHMENT_REGEX =
256
+ /<document_attachment filename="([^"]+)" type="([^"]+)">([\s\S]*?)<\/document_attachment>/g;
257
+
258
+ const ATTACHMENT_LINK_AFTER_REGEX =
259
+ /<attachment_link>\s*([\s\S]*?)\s*<\/attachment_link>/;
260
+
261
+ export const parseDocumentAttachmentsFromMessage = (
262
+ text: string
263
+ ): ParsedDocumentAttachment[] => {
264
+ if (!text) return [];
265
+
266
+ const attachments: ParsedDocumentAttachment[] = [];
267
+ const regex = new RegExp(DOCUMENT_ATTACHMENT_REGEX.source, 'g');
268
+ let match;
269
+
270
+ while ((match = regex.exec(text)) !== null) {
271
+ const [, filename, type, content] = match;
272
+ const afterTag = text.slice(match.index + match[0].length);
273
+ const linkMatch = afterTag.match(ATTACHMENT_LINK_AFTER_REGEX);
274
+ const rawUrl = linkMatch?.[1]?.trim() || '';
275
+ const url = /^https?:\/\//.test(rawUrl) ? rawUrl : '';
276
+
277
+ attachments.push({
278
+ filename,
279
+ type,
280
+ content: content.trim(),
281
+ url,
282
+ });
283
+ }
284
+
285
+ return attachments;
286
+ };
287
+
288
+ export const extractAttachmentLinks = (content: string): string[] => {
289
+ return parseDocumentAttachmentsFromMessage(content).map(
290
+ attachment => attachment.url
291
+ );
292
+ };
293
+
294
+ export const extractAttachmentLink = (content: string): string | null => {
295
+ const match = content?.match(
296
+ /<attachment_link>\s*(https?:\/\/[^\s<]+)\s*<\/attachment_link>/
297
+ );
298
+ return match ? match[1].trim() : null;
299
+ };
300
+
301
+ export const isAssetOnlyDocumentAttachment = (attachment: {
302
+ content?: string | null;
303
+ url?: string | null;
304
+ }): boolean => !attachment.content?.trim() && !!attachment.url?.trim();
305
+
240
306
  export const stripDocumentAttachmentTags = (text: string): string => {
241
307
  const documentAttachmentTagRegex = /<document_attachment filename="([^"]+)" type="([^"]+)">([\s\S]*?)<\/document_attachment>/g;
242
308
  return text
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // This file is auto-generated. Do not edit manually.
2
- export const version = '8.38.5';
2
+ export const version = '8.38.6';