@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.
- package/CHANGELOG.md +24 -0
- package/dist/components/Chat/Chat.js +11 -23
- package/dist/components/Chat/Chat.js.map +1 -1
- package/dist/components/ChatHistoryDrawer/ChatResumeDrawer.css +12 -3
- package/dist/components/ChatHistoryDrawer/ChatResumeDrawer.d.ts +0 -1
- package/dist/components/ChatHistoryDrawer/ChatResumeDrawer.js +18 -8
- package/dist/components/ChatHistoryDrawer/ChatResumeDrawer.js.map +1 -1
- package/dist/components/DrawerFooter/DrawerFooter.js +1 -1
- package/dist/components/DrawerFooter/DrawerFooter.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 +9 -6
- 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 +48 -4
- package/dist/components/MobileSessionPanel/MobileSessionPanel.d.ts +7 -2
- package/dist/components/MobileSessionPanel/MobileSessionPanel.js +178 -58
- package/dist/components/MobileSessionPanel/MobileSessionPanel.js.map +1 -1
- package/dist/components/PositionPopover/PositionPopover.js +2 -1
- package/dist/components/PositionPopover/PositionPopover.js.map +1 -1
- package/dist/components/UploadButton/UploadButton.js +40 -11
- package/dist/components/UploadButton/UploadButton.js.map +1 -1
- package/dist/components/UploadButton/UploadDocuments/UploadDocuments.d.ts +0 -2
- package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js +53 -30
- package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
- package/dist/components/layouts/WebsiteAssistant/WebsiteAssistant.js +6 -2
- 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 +79 -28
- package/dist/components/ui/Tooltip.js +3 -3
- package/dist/components/ui/Tooltip.js.map +1 -1
- 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 +15 -0
- package/dist/helpers/utils.js +45 -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 +12 -3
- package/esm/components/ChatHistoryDrawer/ChatResumeDrawer.d.ts +0 -1
- package/esm/components/ChatHistoryDrawer/ChatResumeDrawer.js +18 -8
- package/esm/components/ChatHistoryDrawer/ChatResumeDrawer.js.map +1 -1
- package/esm/components/DrawerFooter/DrawerFooter.js +1 -1
- package/esm/components/DrawerFooter/DrawerFooter.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 +11 -8
- 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 +48 -4
- package/esm/components/MobileSessionPanel/MobileSessionPanel.d.ts +7 -2
- package/esm/components/MobileSessionPanel/MobileSessionPanel.js +179 -60
- package/esm/components/MobileSessionPanel/MobileSessionPanel.js.map +1 -1
- package/esm/components/PositionPopover/PositionPopover.js +2 -1
- package/esm/components/PositionPopover/PositionPopover.js.map +1 -1
- package/esm/components/UploadButton/UploadButton.js +40 -11
- package/esm/components/UploadButton/UploadButton.js.map +1 -1
- package/esm/components/UploadButton/UploadDocuments/UploadDocuments.d.ts +0 -2
- package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js +53 -30
- package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
- package/esm/components/layouts/WebsiteAssistant/WebsiteAssistant.js +6 -2
- 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 +79 -28
- package/esm/components/ui/Tooltip.js +3 -3
- package/esm/components/ui/Tooltip.js.map +1 -1
- 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 +15 -0
- package/esm/helpers/utils.js +39 -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 +19 -7
- 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 +154 -104
- package/src/components/UploadButton/UploadDocuments/UploadDocuments.tsx +54 -42
- package/src/components/UploadButton/__snapshots__/UploadButton.test.tsx.snap +2 -2
- package/src/components/ui/Tooltip.tsx +3 -2
- package/src/helpers/constants.ts +29 -1
- package/src/helpers/utils.test.ts +101 -0
- package/src/helpers/utils.ts +66 -0
- package/src/version.ts +1 -1
- package/dist/helpers/userMessage.d.ts +0 -2
- package/dist/helpers/userMessage.js +0 -23
- package/dist/helpers/userMessage.js.map +0 -1
- package/esm/helpers/userMessage.d.ts +0 -2
- package/esm/helpers/userMessage.js +0 -18
- 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
|
-
|
|
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 (
|
|
249
|
+
if (ext === 'pdf') {
|
|
249
250
|
text = await extractTextFromPDF(file);
|
|
250
|
-
} else if (['txt', 'md', 'json', 'csv', 'html'].includes(
|
|
251
|
+
} else if (['txt', 'md', 'json', 'csv', 'html'].includes(ext)) {
|
|
251
252
|
text = await file.text();
|
|
252
|
-
} else if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
377
|
-
|
|
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:
|
|
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=
|
|
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
|
-
},
|
|
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
|
-
},
|
|
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
|
>
|
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,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(
|
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,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.
|
|
2
|
+
export const version = '8.38.6';
|
|
@@ -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,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"}
|