@memori.ai/memori-react 8.38.4 → 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 (107) hide show
  1. package/CHANGELOG.md +24 -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 +12 -3
  5. package/dist/components/ChatHistoryDrawer/ChatResumeDrawer.d.ts +0 -1
  6. package/dist/components/ChatHistoryDrawer/ChatResumeDrawer.js +18 -8
  7. package/dist/components/ChatHistoryDrawer/ChatResumeDrawer.js.map +1 -1
  8. package/dist/components/DrawerFooter/DrawerFooter.js +1 -1
  9. package/dist/components/DrawerFooter/DrawerFooter.js.map +1 -1
  10. package/dist/components/FilePreview/FilePreview.js +20 -2
  11. package/dist/components/FilePreview/FilePreview.js.map +1 -1
  12. package/dist/components/MediaWidget/MediaItemWidget.js +9 -6
  13. package/dist/components/MediaWidget/MediaItemWidget.js.map +1 -1
  14. package/dist/components/MediaWidget/MediaItemWidget.utils.d.ts +1 -0
  15. package/dist/components/MediaWidget/MediaItemWidget.utils.js +27 -7
  16. package/dist/components/MediaWidget/MediaItemWidget.utils.js.map +1 -1
  17. package/dist/components/MobileSessionPanel/MobileSessionPanel.css +48 -4
  18. package/dist/components/MobileSessionPanel/MobileSessionPanel.d.ts +7 -2
  19. package/dist/components/MobileSessionPanel/MobileSessionPanel.js +178 -58
  20. package/dist/components/MobileSessionPanel/MobileSessionPanel.js.map +1 -1
  21. package/dist/components/PositionPopover/PositionPopover.js +2 -1
  22. package/dist/components/PositionPopover/PositionPopover.js.map +1 -1
  23. package/dist/components/UploadButton/UploadButton.js +40 -11
  24. package/dist/components/UploadButton/UploadButton.js.map +1 -1
  25. package/dist/components/UploadButton/UploadDocuments/UploadDocuments.d.ts +0 -2
  26. package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js +53 -30
  27. package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
  28. package/dist/components/layouts/WebsiteAssistant/WebsiteAssistant.js +6 -2
  29. package/dist/components/layouts/WebsiteAssistant/WebsiteAssistant.js.map +1 -1
  30. package/dist/components/layouts/WebsiteAssistant/website-assistant.css +1 -3
  31. package/dist/components/layouts/fullpage.css +79 -28
  32. package/dist/components/ui/Tooltip.js +3 -3
  33. package/dist/components/ui/Tooltip.js.map +1 -1
  34. package/dist/helpers/constants.d.ts +3 -0
  35. package/dist/helpers/constants.js +24 -1
  36. package/dist/helpers/constants.js.map +1 -1
  37. package/dist/helpers/usePressTooltip.d.ts +13 -0
  38. package/dist/helpers/usePressTooltip.js +23 -0
  39. package/dist/helpers/usePressTooltip.js.map +1 -0
  40. package/dist/helpers/utils.d.ts +15 -0
  41. package/dist/helpers/utils.js +45 -1
  42. package/dist/helpers/utils.js.map +1 -1
  43. package/dist/version.d.ts +1 -1
  44. package/dist/version.js +1 -1
  45. package/esm/components/Chat/Chat.js +12 -24
  46. package/esm/components/Chat/Chat.js.map +1 -1
  47. package/esm/components/ChatHistoryDrawer/ChatResumeDrawer.css +12 -3
  48. package/esm/components/ChatHistoryDrawer/ChatResumeDrawer.d.ts +0 -1
  49. package/esm/components/ChatHistoryDrawer/ChatResumeDrawer.js +18 -8
  50. package/esm/components/ChatHistoryDrawer/ChatResumeDrawer.js.map +1 -1
  51. package/esm/components/DrawerFooter/DrawerFooter.js +1 -1
  52. package/esm/components/DrawerFooter/DrawerFooter.js.map +1 -1
  53. package/esm/components/FilePreview/FilePreview.js +22 -4
  54. package/esm/components/FilePreview/FilePreview.js.map +1 -1
  55. package/esm/components/MediaWidget/MediaItemWidget.js +11 -8
  56. package/esm/components/MediaWidget/MediaItemWidget.js.map +1 -1
  57. package/esm/components/MediaWidget/MediaItemWidget.utils.d.ts +1 -0
  58. package/esm/components/MediaWidget/MediaItemWidget.utils.js +25 -6
  59. package/esm/components/MediaWidget/MediaItemWidget.utils.js.map +1 -1
  60. package/esm/components/MobileSessionPanel/MobileSessionPanel.css +48 -4
  61. package/esm/components/MobileSessionPanel/MobileSessionPanel.d.ts +7 -2
  62. package/esm/components/MobileSessionPanel/MobileSessionPanel.js +179 -60
  63. package/esm/components/MobileSessionPanel/MobileSessionPanel.js.map +1 -1
  64. package/esm/components/PositionPopover/PositionPopover.js +2 -1
  65. package/esm/components/PositionPopover/PositionPopover.js.map +1 -1
  66. package/esm/components/UploadButton/UploadButton.js +40 -11
  67. package/esm/components/UploadButton/UploadButton.js.map +1 -1
  68. package/esm/components/UploadButton/UploadDocuments/UploadDocuments.d.ts +0 -2
  69. package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js +53 -30
  70. package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
  71. package/esm/components/layouts/WebsiteAssistant/WebsiteAssistant.js +6 -2
  72. package/esm/components/layouts/WebsiteAssistant/WebsiteAssistant.js.map +1 -1
  73. package/esm/components/layouts/WebsiteAssistant/website-assistant.css +1 -3
  74. package/esm/components/layouts/fullpage.css +79 -28
  75. package/esm/components/ui/Tooltip.js +3 -3
  76. package/esm/components/ui/Tooltip.js.map +1 -1
  77. package/esm/helpers/constants.d.ts +3 -0
  78. package/esm/helpers/constants.js +23 -0
  79. package/esm/helpers/constants.js.map +1 -1
  80. package/esm/helpers/usePressTooltip.d.ts +13 -0
  81. package/esm/helpers/usePressTooltip.js +20 -0
  82. package/esm/helpers/usePressTooltip.js.map +1 -0
  83. package/esm/helpers/utils.d.ts +15 -0
  84. package/esm/helpers/utils.js +39 -0
  85. package/esm/helpers/utils.js.map +1 -1
  86. package/esm/version.d.ts +1 -1
  87. package/esm/version.js +1 -1
  88. package/package.json +1 -1
  89. package/src/components/Chat/Chat.tsx +19 -44
  90. package/src/components/FilePreview/FilePreview.tsx +26 -4
  91. package/src/components/MediaWidget/MediaItemWidget.tsx +19 -7
  92. package/src/components/MediaWidget/MediaItemWidget.utils.test.ts +45 -2
  93. package/src/components/MediaWidget/MediaItemWidget.utils.ts +37 -6
  94. package/src/components/UploadButton/UploadButton.tsx +154 -104
  95. package/src/components/UploadButton/UploadDocuments/UploadDocuments.tsx +54 -42
  96. package/src/components/UploadButton/__snapshots__/UploadButton.test.tsx.snap +2 -2
  97. package/src/components/ui/Tooltip.tsx +3 -2
  98. package/src/helpers/constants.ts +29 -1
  99. package/src/helpers/utils.test.ts +101 -0
  100. package/src/helpers/utils.ts +66 -0
  101. package/src/version.ts +1 -1
  102. package/dist/helpers/userMessage.d.ts +0 -2
  103. package/dist/helpers/userMessage.js +0 -23
  104. package/dist/helpers/userMessage.js.map +0 -1
  105. package/esm/helpers/userMessage.d.ts +0 -2
  106. package/esm/helpers/userMessage.js +0 -18
  107. package/esm/helpers/userMessage.js.map +0 -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;
@@ -39,7 +41,6 @@ interface UploadDocumentsProps {
39
41
  id: string;
40
42
  content: string;
41
43
  mimeType: string;
42
- sourceUrl?: string;
43
44
  textAssetUrl?: string;
44
45
  }[]
45
46
  ) => void;
@@ -63,8 +64,6 @@ interface UploadDocumentsProps {
63
64
  mimeType: string;
64
65
  }[]
65
66
  ) => boolean | { valid: boolean; message?: string };
66
- /** Per-document content character limit. */
67
- maxDocumentContentLength?: number;
68
67
  }
69
68
 
70
69
  const UploadDocuments: React.FC<UploadDocumentsProps> = ({
@@ -79,7 +78,6 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
79
78
  onDocumentError,
80
79
  onValidateFile,
81
80
  onValidatePayloadSize,
82
- maxDocumentContentLength = 300000,
83
81
  }) => {
84
82
  const { t } = useTranslation();
85
83
  const { backend } = client || {
@@ -239,17 +237,20 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
239
237
 
240
238
  const processDocumentFile = async (
241
239
  file: File
242
- ): Promise<{ text: string | null }> => {
243
- 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
+ }
244
244
 
245
+ const ext = file.name.split('.').pop()?.toLowerCase() || '';
245
246
  try {
246
247
  let text: string | null = null;
247
248
 
248
- if (fileExt === 'pdf') {
249
+ if (ext === 'pdf') {
249
250
  text = await extractTextFromPDF(file);
250
- } else if (['txt', 'md', 'json', 'csv', 'html'].includes(fileExt)) {
251
+ } else if (['txt', 'md', 'json', 'csv', 'html'].includes(ext)) {
251
252
  text = await file.text();
252
- } else if (fileExt === 'xlsx') {
253
+ } else if (ext === 'xlsx') {
253
254
  text = await extractTextFromXLSX(file);
254
255
  }
255
256
 
@@ -299,7 +300,12 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
299
300
  throw new Error(response.resultMessage || 'Upload failed');
300
301
  }
301
302
 
302
- 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;
303
309
  };
304
310
 
305
311
  const handleDocumentUpload = async (
@@ -349,7 +355,6 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
349
355
  id: string;
350
356
  content: string;
351
357
  mimeType: string;
352
- sourceUrl?: string;
353
358
  textAssetUrl?: string;
354
359
  }[] = [];
355
360
 
@@ -365,32 +370,48 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
365
370
  const fileId = Math.random().toString(36).substr(2, 9);
366
371
 
367
372
  try {
368
- 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
+ }
369
390
 
370
- 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) {
371
405
  const baseName = file.name.replace(/\.[^/.]+$/, '') || file.name;
372
406
  const textFile = new File([text], `${baseName}.txt`, {
373
407
  type: 'text/plain',
374
408
  });
375
409
 
376
- const uploadResults = await Promise.allSettled([
377
- uploadAssetFile(file),
378
- uploadAssetFile(textFile),
379
- ]);
380
-
381
- const sourceUrl =
382
- uploadResults[0].status === 'fulfilled'
383
- ? uploadResults[0].value
384
- : undefined;
385
- const textAssetUrl =
386
- uploadResults[1].status === 'fulfilled'
387
- ? uploadResults[1].value
388
- : undefined;
389
-
390
- if (
391
- uploadResults[0].status === 'rejected' ||
392
- uploadResults[1].status === 'rejected'
393
- ) {
410
+ let textAssetUrl: string | undefined;
411
+ try {
412
+ textAssetUrl = await uploadAssetFile(textFile);
413
+ } catch (uploadError) {
414
+ console.error('Text asset upload failed:', uploadError);
394
415
  onDocumentError?.({
395
416
  message: t('upload.partialAssetUploadWarning', {
396
417
  fileName: file.name,
@@ -401,20 +422,11 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
401
422
  });
402
423
  }
403
424
 
404
- let contentForMessage = text;
405
- const perDocumentLimit = maxDocumentContentLength;
406
- if (text.length > perDocumentLimit) {
407
- contentForMessage =
408
- text.substring(0, perDocumentLimit) +
409
- '\n\n[Content truncated due to size limits]';
410
- }
411
-
412
425
  processedFiles.push({
413
426
  name: file.name,
414
427
  id: fileId,
415
- content: contentForMessage,
428
+ content: text,
416
429
  mimeType: file.type,
417
- sourceUrl,
418
430
  textAssetUrl,
419
431
  });
420
432
  } else {
@@ -456,7 +468,7 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
456
468
  <input
457
469
  ref={documentInputRef}
458
470
  type="file"
459
- accept=".pdf,.txt,.md,.json,.xlsx,.csv,.html"
471
+ accept={`.pdf,.txt,.md,.json,.xlsx,.csv,.html,${officeNativeExtensions.join(',')}`}
460
472
  multiple
461
473
  className="memori--upload-file-input"
462
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"
@@ -60,7 +60,7 @@ const Tooltip: FC<Props> = ({
60
60
  closeTimeoutRef.current = setTimeout(() => {
61
61
  setIsHovered(false);
62
62
  closeTimeoutRef.current = null;
63
- }, 100);
63
+ }, 300);
64
64
  }, []);
65
65
 
66
66
  const handleContentEnter = useCallback(() => {
@@ -72,7 +72,7 @@ const Tooltip: FC<Props> = ({
72
72
  closeTimeoutRef.current = setTimeout(() => {
73
73
  setIsHovered(false);
74
74
  closeTimeoutRef.current = null;
75
- }, 100);
75
+ }, 300);
76
76
  }, []);
77
77
 
78
78
  useEffect(() => {
@@ -124,6 +124,7 @@ const Tooltip: FC<Props> = ({
124
124
  >
125
125
  <div
126
126
  className="memori-tooltip--content memori-tooltip--content-portal"
127
+ style={{ pointerEvents: 'auto' }}
127
128
  onMouseEnter={handleContentEnter}
128
129
  onMouseLeave={handleContentLeave}
129
130
  >
@@ -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.4';
2
+ export const version = '8.38.6';
@@ -1,2 +0,0 @@
1
- export declare function sanitizeUserMessageInput(raw: string): string;
2
- export declare function formatUserBubbleHtml(cleanText: string): string;
@@ -1,23 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sanitizeUserMessageInput = sanitizeUserMessageInput;
4
- exports.formatUserBubbleHtml = formatUserBubbleHtml;
5
- const tslib_1 = require("tslib");
6
- const dompurify_1 = tslib_1.__importDefault(require("dompurify"));
7
- const utils_1 = require("./utils");
8
- const message_1 = require("./message");
9
- function sanitizeUserMessageInput(raw) {
10
- if (raw == null || typeof raw !== 'string') {
11
- return '';
12
- }
13
- const stripped = dompurify_1.default.sanitize(raw, {
14
- ALLOWED_TAGS: [],
15
- ALLOWED_ATTR: [],
16
- });
17
- return stripped.trim();
18
- }
19
- function formatUserBubbleHtml(cleanText) {
20
- const truncated = (0, message_1.truncateMessagePlain)(cleanText);
21
- return (0, utils_1.escapeHTML)(truncated).replace(/\n/g, '<br />');
22
- }
23
- //# sourceMappingURL=userMessage.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"userMessage.js","sourceRoot":"","sources":["../../src/helpers/userMessage.ts"],"names":[],"mappings":";;AAQA,4DASC;AAGD,oDAGC;;AAvBD,kEAAkC;AAClC,mCAAqC;AACrC,uCAAiD;AAMjD,SAAgB,wBAAwB,CAAC,GAAW;IAClD,IAAI,GAAG,IAAI,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC3C,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,QAAQ,GAAG,mBAAS,CAAC,QAAQ,CAAC,GAAG,EAAE;QACvC,YAAY,EAAE,EAAE;QAChB,YAAY,EAAE,EAAE;KACjB,CAAC,CAAC;IACH,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;AACzB,CAAC;AAGD,SAAgB,oBAAoB,CAAC,SAAiB;IACpD,MAAM,SAAS,GAAG,IAAA,8BAAoB,EAAC,SAAS,CAAC,CAAC;IAClD,OAAO,IAAA,kBAAU,EAAC,SAAS,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;AACxD,CAAC"}
@@ -1,2 +0,0 @@
1
- export declare function sanitizeUserMessageInput(raw: string): string;
2
- export declare function formatUserBubbleHtml(cleanText: string): string;
@@ -1,18 +0,0 @@
1
- import DOMPurify from 'dompurify';
2
- import { escapeHTML } from './utils';
3
- import { truncateMessagePlain } from './message';
4
- export function sanitizeUserMessageInput(raw) {
5
- if (raw == null || typeof raw !== 'string') {
6
- return '';
7
- }
8
- const stripped = DOMPurify.sanitize(raw, {
9
- ALLOWED_TAGS: [],
10
- ALLOWED_ATTR: [],
11
- });
12
- return stripped.trim();
13
- }
14
- export function formatUserBubbleHtml(cleanText) {
15
- const truncated = truncateMessagePlain(cleanText);
16
- return escapeHTML(truncated).replace(/\n/g, '<br />');
17
- }
18
- //# sourceMappingURL=userMessage.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"userMessage.js","sourceRoot":"","sources":["../../src/helpers/userMessage.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAMjD,MAAM,UAAU,wBAAwB,CAAC,GAAW;IAClD,IAAI,GAAG,IAAI,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC3C,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,QAAQ,GAAG,SAAS,CAAC,QAAQ,CAAC,GAAG,EAAE;QACvC,YAAY,EAAE,EAAE;QAChB,YAAY,EAAE,EAAE;KACjB,CAAC,CAAC;IACH,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;AACzB,CAAC;AAGD,MAAM,UAAU,oBAAoB,CAAC,SAAiB;IACpD,MAAM,SAAS,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAC;IAClD,OAAO,UAAU,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;AACxD,CAAC"}