@memori.ai/memori-react 8.38.5 → 8.38.7
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.
- package/CHANGELOG.md +19 -0
- package/dist/components/Chat/Chat.js +11 -23
- package/dist/components/Chat/Chat.js.map +1 -1
- package/dist/components/ChatHistoryDrawer/ChatResumeDrawer.css +1 -1
- package/dist/components/ChatHistoryDrawer/ChatResumeDrawer.d.ts +0 -1
- package/dist/components/ChatHistoryDrawer/ChatResumeDrawer.js +0 -1
- package/dist/components/ChatHistoryDrawer/ChatResumeDrawer.js.map +1 -1
- package/dist/components/FilePreview/FilePreview.js +20 -2
- package/dist/components/FilePreview/FilePreview.js.map +1 -1
- package/dist/components/MediaWidget/MediaItemWidget.js +13 -9
- package/dist/components/MediaWidget/MediaItemWidget.js.map +1 -1
- package/dist/components/MediaWidget/MediaItemWidget.utils.d.ts +1 -0
- package/dist/components/MediaWidget/MediaItemWidget.utils.js +27 -7
- package/dist/components/MediaWidget/MediaItemWidget.utils.js.map +1 -1
- package/dist/components/MobileSessionPanel/MobileSessionPanel.css +30 -4
- package/dist/components/MobileSessionPanel/MobileSessionPanel.d.ts +3 -2
- package/dist/components/MobileSessionPanel/MobileSessionPanel.js +16 -13
- package/dist/components/MobileSessionPanel/MobileSessionPanel.js.map +1 -1
- package/dist/components/UploadButton/UploadButton.js +21 -6
- package/dist/components/UploadButton/UploadButton.js.map +1 -1
- package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js +45 -8
- package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
- package/dist/components/layouts/WebsiteAssistant/WebsiteAssistant.js +5 -1
- package/dist/components/layouts/WebsiteAssistant/WebsiteAssistant.js.map +1 -1
- package/dist/components/layouts/WebsiteAssistant/website-assistant.css +1 -3
- package/dist/components/layouts/fullpage.css +55 -21
- package/dist/helpers/constants.d.ts +3 -0
- package/dist/helpers/constants.js +24 -1
- package/dist/helpers/constants.js.map +1 -1
- package/dist/helpers/usePressTooltip.d.ts +13 -0
- package/dist/helpers/usePressTooltip.js +23 -0
- package/dist/helpers/usePressTooltip.js.map +1 -0
- package/dist/helpers/utils.d.ts +21 -0
- package/dist/helpers/utils.js +66 -1
- package/dist/helpers/utils.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/esm/components/Chat/Chat.js +12 -24
- package/esm/components/Chat/Chat.js.map +1 -1
- package/esm/components/ChatHistoryDrawer/ChatResumeDrawer.css +1 -1
- package/esm/components/ChatHistoryDrawer/ChatResumeDrawer.d.ts +0 -1
- package/esm/components/ChatHistoryDrawer/ChatResumeDrawer.js +0 -1
- package/esm/components/ChatHistoryDrawer/ChatResumeDrawer.js.map +1 -1
- package/esm/components/FilePreview/FilePreview.js +22 -4
- package/esm/components/FilePreview/FilePreview.js.map +1 -1
- package/esm/components/MediaWidget/MediaItemWidget.js +15 -11
- package/esm/components/MediaWidget/MediaItemWidget.js.map +1 -1
- package/esm/components/MediaWidget/MediaItemWidget.utils.d.ts +1 -0
- package/esm/components/MediaWidget/MediaItemWidget.utils.js +25 -6
- package/esm/components/MediaWidget/MediaItemWidget.utils.js.map +1 -1
- package/esm/components/MobileSessionPanel/MobileSessionPanel.css +30 -4
- package/esm/components/MobileSessionPanel/MobileSessionPanel.d.ts +3 -2
- package/esm/components/MobileSessionPanel/MobileSessionPanel.js +17 -14
- package/esm/components/MobileSessionPanel/MobileSessionPanel.js.map +1 -1
- package/esm/components/UploadButton/UploadButton.js +21 -6
- package/esm/components/UploadButton/UploadButton.js.map +1 -1
- package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js +45 -8
- package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
- package/esm/components/layouts/WebsiteAssistant/WebsiteAssistant.js +5 -1
- package/esm/components/layouts/WebsiteAssistant/WebsiteAssistant.js.map +1 -1
- package/esm/components/layouts/WebsiteAssistant/website-assistant.css +1 -3
- package/esm/components/layouts/fullpage.css +55 -21
- package/esm/helpers/constants.d.ts +3 -0
- package/esm/helpers/constants.js +23 -0
- package/esm/helpers/constants.js.map +1 -1
- package/esm/helpers/usePressTooltip.d.ts +13 -0
- package/esm/helpers/usePressTooltip.js +20 -0
- package/esm/helpers/usePressTooltip.js.map +1 -0
- package/esm/helpers/utils.d.ts +21 -0
- package/esm/helpers/utils.js +59 -0
- package/esm/helpers/utils.js.map +1 -1
- package/esm/version.d.ts +1 -1
- package/esm/version.js +1 -1
- package/package.json +1 -1
- package/src/components/Chat/Chat.tsx +19 -44
- package/src/components/FilePreview/FilePreview.tsx +26 -4
- package/src/components/MediaWidget/MediaItemWidget.tsx +24 -10
- package/src/components/MediaWidget/MediaItemWidget.utils.test.ts +45 -2
- package/src/components/MediaWidget/MediaItemWidget.utils.ts +37 -6
- package/src/components/UploadButton/UploadButton.tsx +28 -12
- package/src/components/UploadButton/UploadDocuments/UploadDocuments.tsx +48 -9
- package/src/components/UploadButton/__snapshots__/UploadButton.test.tsx.snap +2 -2
- package/src/helpers/constants.ts +29 -1
- package/src/helpers/utils.test.ts +113 -0
- package/src/helpers/utils.ts +92 -0
- package/src/version.ts +1 -1
|
@@ -9,6 +9,7 @@ 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 { officeNativeExtensions } from '../../helpers/constants';
|
|
12
13
|
import Tooltip from '../ui/Tooltip';
|
|
13
14
|
// Props interface
|
|
14
15
|
interface UploadManagerProps {
|
|
@@ -108,6 +109,7 @@ const UploadButton: React.FC<UploadManagerProps> = ({
|
|
|
108
109
|
'.csv',
|
|
109
110
|
'.md',
|
|
110
111
|
'.html',
|
|
112
|
+
...officeNativeExtensions,
|
|
111
113
|
];
|
|
112
114
|
const fileExt = `.${file.name.split('.').pop()?.toLowerCase()}`;
|
|
113
115
|
return documentExtensions.includes(fileExt);
|
|
@@ -401,18 +403,29 @@ const UploadButton: React.FC<UploadManagerProps> = ({
|
|
|
401
403
|
const processedDocuments = files.map(file => {
|
|
402
404
|
const escapedFileName = escapeAttributeValue(file.name);
|
|
403
405
|
|
|
404
|
-
|
|
405
|
-
// <document_attachment> tag. The full text is still available via
|
|
406
|
-
// textAssetUrl as an uploaded asset.
|
|
407
|
-
const inlinedContent =
|
|
408
|
-
file.content.length > effectivePerDocumentLimit
|
|
409
|
-
? file.content.substring(0, effectivePerDocumentLimit) +
|
|
410
|
-
'\n\n[Content truncated due to size limits]'
|
|
411
|
-
: file.content;
|
|
406
|
+
let formattedContent: string;
|
|
412
407
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
408
|
+
if (!file.content) {
|
|
409
|
+
// Office native format: metadata-only attachment tag + asset link (no text body)
|
|
410
|
+
formattedContent = `<document_attachment filename="${escapedFileName}" type="${file.mimeType}">
|
|
411
|
+
</document_attachment>
|
|
412
|
+
|
|
413
|
+
<attachment_link>
|
|
414
|
+
${file.textAssetUrl || ''}
|
|
415
|
+
</attachment_link>`;
|
|
416
|
+
} else {
|
|
417
|
+
// Truncate only the content that gets inlined inside the message
|
|
418
|
+
// <document_attachment> tag. The full text is still available via
|
|
419
|
+
// textAssetUrl as an uploaded asset.
|
|
420
|
+
const inlinedContent =
|
|
421
|
+
file.content.length > effectivePerDocumentLimit
|
|
422
|
+
? file.content.substring(0, effectivePerDocumentLimit) +
|
|
423
|
+
'\n\n[Content truncated due to size limits]'
|
|
424
|
+
: file.content;
|
|
425
|
+
|
|
426
|
+
formattedContent = `<document_attachment filename="${escapedFileName}" type="${
|
|
427
|
+
file.mimeType
|
|
428
|
+
}">
|
|
416
429
|
|
|
417
430
|
${inlinedContent}
|
|
418
431
|
|
|
@@ -421,6 +434,7 @@ ${inlinedContent}
|
|
|
421
434
|
<attachment_link>
|
|
422
435
|
${file.textAssetUrl || ''}
|
|
423
436
|
</attachment_link>`;
|
|
437
|
+
}
|
|
424
438
|
|
|
425
439
|
return {
|
|
426
440
|
name: file.name,
|
|
@@ -428,6 +442,7 @@ ${file.textAssetUrl || ''}
|
|
|
428
442
|
content: formattedContent,
|
|
429
443
|
type: 'document',
|
|
430
444
|
mimeType: file.mimeType,
|
|
445
|
+
url: file.textAssetUrl,
|
|
431
446
|
};
|
|
432
447
|
});
|
|
433
448
|
|
|
@@ -446,6 +461,7 @@ ${file.textAssetUrl || ''}
|
|
|
446
461
|
'.csv',
|
|
447
462
|
'.md',
|
|
448
463
|
'.html',
|
|
464
|
+
...officeNativeExtensions,
|
|
449
465
|
];
|
|
450
466
|
const MAX_FILE_SIZE = 15 * 1024 * 1024; // 15MB
|
|
451
467
|
|
|
@@ -562,7 +578,7 @@ ${file.textAssetUrl || ''}
|
|
|
562
578
|
<input
|
|
563
579
|
ref={unifiedInputRef}
|
|
564
580
|
type="file"
|
|
565
|
-
accept=
|
|
581
|
+
accept={`.jpg,.jpeg,.png,.pdf,.txt,.json,.xlsx,.csv,.md,.html,${officeNativeExtensions.join(',')}`}
|
|
566
582
|
multiple
|
|
567
583
|
className="memori--upload-file-input"
|
|
568
584
|
onChange={handleFileInputChange}
|
|
@@ -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
|
-
|
|
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 (
|
|
249
|
+
if (ext === 'pdf') {
|
|
245
250
|
text = await extractTextFromPDF(file);
|
|
246
|
-
} else if (['txt', 'md', 'json', 'csv', 'html'].includes(
|
|
251
|
+
} else if (['txt', 'md', 'json', 'csv', 'html'].includes(ext)) {
|
|
247
252
|
text = await file.text();
|
|
248
|
-
} else if (
|
|
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
|
-
|
|
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
|
-
|
|
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=
|
|
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"
|
package/src/helpers/constants.ts
CHANGED
|
@@ -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,114 @@ 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
|
+
title: 'report.docx',
|
|
191
|
+
content: '',
|
|
192
|
+
url: 'https://assets.example.com/file.docx',
|
|
193
|
+
})
|
|
194
|
+
).toBe(true);
|
|
195
|
+
expect(
|
|
196
|
+
isAssetOnlyDocumentAttachment({
|
|
197
|
+
title: 'report.docx',
|
|
198
|
+
content: [
|
|
199
|
+
'<document_attachment filename="report.docx" type="application/vnd.openxmlformats-officedocument.wordprocessingml.document">',
|
|
200
|
+
'</document_attachment>',
|
|
201
|
+
'<attachment_link>https://assets.example.com/file.docx</attachment_link>',
|
|
202
|
+
].join('\n'),
|
|
203
|
+
url: '',
|
|
204
|
+
})
|
|
205
|
+
).toBe(true);
|
|
206
|
+
expect(
|
|
207
|
+
isAssetOnlyDocumentAttachment({
|
|
208
|
+
content: 'extracted text',
|
|
209
|
+
url: 'https://assets.example.com/file.txt',
|
|
210
|
+
})
|
|
211
|
+
).toBe(false);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('pairs each document attachment with its adjacent link', () => {
|
|
215
|
+
const input = [
|
|
216
|
+
'<attachment_link>https://assets.example.com/orphan.txt</attachment_link>',
|
|
217
|
+
'<document_attachment filename="a.docx" type="application/vnd.openxmlformats-officedocument.wordprocessingml.document">',
|
|
218
|
+
'</document_attachment>',
|
|
219
|
+
'<attachment_link>https://assets.example.com/a.docx</attachment_link>',
|
|
220
|
+
'<document_attachment filename="b.pdf" type="application/pdf">',
|
|
221
|
+
'pdf text',
|
|
222
|
+
'</document_attachment>',
|
|
223
|
+
'<attachment_link>https://assets.example.com/b.txt</attachment_link>',
|
|
224
|
+
].join('\n');
|
|
225
|
+
|
|
226
|
+
expect(parseDocumentAttachmentsFromMessage(input)).toEqual([
|
|
227
|
+
{
|
|
228
|
+
filename: 'a.docx',
|
|
229
|
+
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
230
|
+
content: '',
|
|
231
|
+
url: 'https://assets.example.com/a.docx',
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
filename: 'b.pdf',
|
|
235
|
+
type: 'application/pdf',
|
|
236
|
+
content: 'pdf text',
|
|
237
|
+
url: 'https://assets.example.com/b.txt',
|
|
238
|
+
},
|
|
239
|
+
]);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('returns an empty url when no adjacent attachment link exists', () => {
|
|
243
|
+
const input =
|
|
244
|
+
'<document_attachment filename="a.docx" type="application/vnd.openxmlformats-officedocument.wordprocessingml.document"></document_attachment>';
|
|
245
|
+
|
|
246
|
+
expect(parseDocumentAttachmentsFromMessage(input)).toEqual([
|
|
247
|
+
{
|
|
248
|
+
filename: 'a.docx',
|
|
249
|
+
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
250
|
+
content: '',
|
|
251
|
+
url: '',
|
|
252
|
+
},
|
|
253
|
+
]);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
144
257
|
describe('utils/parsing combined', () => {
|
|
145
258
|
it('should remove output tag from real message', () => {
|
|
146
259
|
const result = escapeHTML(
|
package/src/helpers/utils.ts
CHANGED
|
@@ -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,67 @@ 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*([\s\S]*?)\s*<\/attachment_link>/
|
|
297
|
+
);
|
|
298
|
+
const rawUrl = match?.[1]?.trim() || '';
|
|
299
|
+
return /^https?:\/\//.test(rawUrl) ? rawUrl : null;
|
|
300
|
+
};
|
|
301
|
+
|
|
240
302
|
export const stripDocumentAttachmentTags = (text: string): string => {
|
|
241
303
|
const documentAttachmentTagRegex = /<document_attachment filename="([^"]+)" type="([^"]+)">([\s\S]*?)<\/document_attachment>/g;
|
|
242
304
|
return text
|
|
@@ -245,6 +307,36 @@ export const stripDocumentAttachmentTags = (text: string): string => {
|
|
|
245
307
|
.replace(/<attachment_link>\s*[\s\S]*?\s*<\/attachment_link>/g, '');
|
|
246
308
|
};
|
|
247
309
|
|
|
310
|
+
export const getDocumentAttachmentAssetUrl = (attachment: {
|
|
311
|
+
content?: string | null;
|
|
312
|
+
url?: string | null;
|
|
313
|
+
}): string =>
|
|
314
|
+
attachment.url?.trim() ||
|
|
315
|
+
extractAttachmentLink(attachment.content || '') ||
|
|
316
|
+
'';
|
|
317
|
+
|
|
318
|
+
export const isAssetOnlyDocumentAttachment = (attachment: {
|
|
319
|
+
title?: string;
|
|
320
|
+
name?: string;
|
|
321
|
+
content?: string | null;
|
|
322
|
+
url?: string | null;
|
|
323
|
+
}): boolean => {
|
|
324
|
+
const filename = attachment.title || attachment.name || '';
|
|
325
|
+
const assetUrl = getDocumentAttachmentAssetUrl(attachment);
|
|
326
|
+
|
|
327
|
+
if (!assetUrl) return false;
|
|
328
|
+
|
|
329
|
+
if (isOfficeNativeFilename(filename)) {
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const strippedContent = attachment.content
|
|
334
|
+
? stripDocumentAttachmentTags(attachment.content).trim()
|
|
335
|
+
: '';
|
|
336
|
+
|
|
337
|
+
return !strippedContent;
|
|
338
|
+
};
|
|
339
|
+
|
|
248
340
|
export const stripOutputTags = (text: string): string => {
|
|
249
341
|
const outputTagRegex = /<output.*?<\/output>/gs;
|
|
250
342
|
|
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.
|
|
2
|
+
export const version = '8.38.7';
|