@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
|
@@ -12,6 +12,7 @@ import { getResourceUrl } from '../../helpers/media';
|
|
|
12
12
|
import {
|
|
13
13
|
withLinksOpenInNewTab,
|
|
14
14
|
stripDocumentAttachmentTags,
|
|
15
|
+
isAssetOnlyDocumentAttachment,
|
|
15
16
|
} from '../../helpers/utils';
|
|
16
17
|
import { getTranslation } from '../../helpers/translations';
|
|
17
18
|
import { prismSyntaxLangs } from '../../helpers/constants';
|
|
@@ -36,7 +37,7 @@ import type {
|
|
|
36
37
|
import {
|
|
37
38
|
formatBytes,
|
|
38
39
|
getFileExtensionFromUrl,
|
|
39
|
-
|
|
40
|
+
getDocumentBadgeLabel,
|
|
40
41
|
countLines,
|
|
41
42
|
shouldUseDarkFileCard,
|
|
42
43
|
fetchLinkPreview,
|
|
@@ -272,7 +273,7 @@ export const RenderMediaItem = memo(function RenderMediaItem({
|
|
|
272
273
|
) : (
|
|
273
274
|
<DocumentCard
|
|
274
275
|
title={medium.title || 'File'}
|
|
275
|
-
badge={
|
|
276
|
+
badge={getDocumentBadgeLabel(medium.mimeType, medium.title)}
|
|
276
277
|
meta={(() => {
|
|
277
278
|
const size = getContentSize(medium);
|
|
278
279
|
return size != null && size > 0 ? formatBytes(size) : null;
|
|
@@ -288,7 +289,7 @@ export const RenderMediaItem = memo(function RenderMediaItem({
|
|
|
288
289
|
return (
|
|
289
290
|
<DocumentCard
|
|
290
291
|
title={medium.title || 'File'}
|
|
291
|
-
badge={
|
|
292
|
+
badge={getDocumentBadgeLabel(medium.mimeType, medium.title)}
|
|
292
293
|
meta={(() => {
|
|
293
294
|
const size = getContentSize(medium);
|
|
294
295
|
return size != null && size > 0 ? formatBytes(size) : null;
|
|
@@ -302,7 +303,7 @@ export const RenderMediaItem = memo(function RenderMediaItem({
|
|
|
302
303
|
return (
|
|
303
304
|
<DocumentCard
|
|
304
305
|
title={medium.title || 'File'}
|
|
305
|
-
badge={
|
|
306
|
+
badge={getDocumentBadgeLabel(medium.mimeType, medium.title)}
|
|
306
307
|
meta={(() => {
|
|
307
308
|
const size = getContentSize(medium);
|
|
308
309
|
return size != null && size > 0 ? formatBytes(size) : null;
|
|
@@ -328,8 +329,11 @@ export const RenderMediaItem = memo(function RenderMediaItem({
|
|
|
328
329
|
|
|
329
330
|
// Extension and file detection helpers
|
|
330
331
|
const fileExtensionFromUrl = getFileExtensionFromUrl(normURL || item.url);
|
|
331
|
-
const
|
|
332
|
-
|
|
332
|
+
const fileExtension = getDocumentBadgeLabel(
|
|
333
|
+
item.mimeType,
|
|
334
|
+
item.title,
|
|
335
|
+
normURL || item.url
|
|
336
|
+
);
|
|
333
337
|
const isFile = shouldUseDarkFileCard(
|
|
334
338
|
item,
|
|
335
339
|
fileExtensionFromUrl,
|
|
@@ -359,8 +363,16 @@ export const RenderMediaItem = memo(function RenderMediaItem({
|
|
|
359
363
|
const metaParts = [lineText, sizeText].filter(Boolean);
|
|
360
364
|
const metaLine = metaParts.length > 0 ? metaParts.join(' · ') : null;
|
|
361
365
|
|
|
366
|
+
// Asset-only attachments (e.g. Office native files) open via URL, not modal
|
|
367
|
+
const isAssetOnlyAttachment = isAssetOnlyDocumentAttachment(item);
|
|
368
|
+
|
|
362
369
|
// Document attachments and attached files should open in modal, not as links
|
|
363
|
-
if (
|
|
370
|
+
if (
|
|
371
|
+
(isDocumentAttachment || isAttachedFile) &&
|
|
372
|
+
item.mediumID &&
|
|
373
|
+
_onClick &&
|
|
374
|
+
!isAssetOnlyAttachment
|
|
375
|
+
) {
|
|
364
376
|
return (
|
|
365
377
|
<div
|
|
366
378
|
onClick={() => _onClick(item)}
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
formatBytes,
|
|
3
3
|
getFileExtensionFromUrl,
|
|
4
4
|
getFileExtensionFromMime,
|
|
5
|
+
getDocumentBadgeLabel,
|
|
5
6
|
countLines,
|
|
6
7
|
shouldUseDarkFileCard,
|
|
7
8
|
fetchLinkPreview,
|
|
@@ -57,8 +58,25 @@ describe('MediaItemWidget.utils', () => {
|
|
|
57
58
|
expect(getFileExtensionFromMime('text/html')).toBe('HTML');
|
|
58
59
|
expect(getFileExtensionFromMime('text/plain')).toBe('TXT');
|
|
59
60
|
expect(getFileExtensionFromMime('application/json')).toBe('JSON');
|
|
60
|
-
expect(getFileExtensionFromMime('application/vnd.ms-excel')).toBe('
|
|
61
|
-
expect(
|
|
61
|
+
expect(getFileExtensionFromMime('application/vnd.ms-excel')).toBe('Excel');
|
|
62
|
+
expect(
|
|
63
|
+
getFileExtensionFromMime(
|
|
64
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
65
|
+
)
|
|
66
|
+
).toBe('Excel');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('maps office mime types with parameters to short labels', () => {
|
|
70
|
+
expect(
|
|
71
|
+
getFileExtensionFromMime(
|
|
72
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document; charset=binary'
|
|
73
|
+
)
|
|
74
|
+
).toBe('Word');
|
|
75
|
+
expect(
|
|
76
|
+
getFileExtensionFromMime(
|
|
77
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.template'
|
|
78
|
+
)
|
|
79
|
+
).toBe('Excel');
|
|
62
80
|
});
|
|
63
81
|
|
|
64
82
|
it('uses subtype when not in MIME_TO_EXT map', () => {
|
|
@@ -71,6 +89,31 @@ describe('MediaItemWidget.utils', () => {
|
|
|
71
89
|
});
|
|
72
90
|
});
|
|
73
91
|
|
|
92
|
+
describe('getDocumentBadgeLabel', () => {
|
|
93
|
+
it('prefers URL extension, then filename, then mime type', () => {
|
|
94
|
+
expect(
|
|
95
|
+
getDocumentBadgeLabel(
|
|
96
|
+
'application/pdf',
|
|
97
|
+
'report.docx',
|
|
98
|
+
'https://example.com/file.pdf'
|
|
99
|
+
)
|
|
100
|
+
).toBe('PDF');
|
|
101
|
+
expect(
|
|
102
|
+
getDocumentBadgeLabel(
|
|
103
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
104
|
+
'report.docx',
|
|
105
|
+
'https://example.com/asset/123'
|
|
106
|
+
)
|
|
107
|
+
).toBe('Word');
|
|
108
|
+
expect(
|
|
109
|
+
getDocumentBadgeLabel(
|
|
110
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
111
|
+
'budget.xlsx'
|
|
112
|
+
)
|
|
113
|
+
).toBe('Excel');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
74
117
|
describe('countLines', () => {
|
|
75
118
|
it('returns 0 for undefined or empty', () => {
|
|
76
119
|
expect(countLines(undefined)).toBe(0);
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { Medium } from '@memori.ai/memori-api-client/dist/types';
|
|
2
|
+
import {
|
|
3
|
+
officeExtensionShortLabels,
|
|
4
|
+
officeMimeShortLabels,
|
|
5
|
+
} from '../../helpers/constants';
|
|
2
6
|
import type { LinkPreviewInfo } from './MediaItemWidget.types';
|
|
3
7
|
|
|
4
8
|
export const FILE_EXTENSIONS_DARK_CARD = [
|
|
@@ -54,11 +58,6 @@ export const IMAGE_MIME_TYPES = [
|
|
|
54
58
|
|
|
55
59
|
const MIME_TO_EXT: Record<string, string> = {
|
|
56
60
|
'application/pdf': 'PDF',
|
|
57
|
-
'application/msword': 'DOC',
|
|
58
|
-
'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
|
|
59
|
-
'DOCX',
|
|
60
|
-
'application/vnd.ms-excel': 'XLS',
|
|
61
|
-
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'XLSX',
|
|
62
61
|
'text/html': 'HTML',
|
|
63
62
|
'text/plain': 'TXT',
|
|
64
63
|
'text/css': 'CSS',
|
|
@@ -87,14 +86,46 @@ export function getFileExtensionFromUrl(
|
|
|
87
86
|
return match ? match[1].toUpperCase() : null;
|
|
88
87
|
}
|
|
89
88
|
|
|
89
|
+
function normalizeMimeType(mimeType: string): string {
|
|
90
|
+
return mimeType.split(';')[0].trim();
|
|
91
|
+
}
|
|
92
|
+
|
|
90
93
|
export function getFileExtensionFromMime(mimeType: string): string {
|
|
94
|
+
const normalized = normalizeMimeType(mimeType);
|
|
95
|
+
const normalizedLower = normalized.toLowerCase();
|
|
96
|
+
|
|
97
|
+
const officeLabel =
|
|
98
|
+
officeMimeShortLabels[normalized] ||
|
|
99
|
+
officeMimeShortLabels[normalizedLower];
|
|
100
|
+
if (officeLabel) return officeLabel;
|
|
101
|
+
|
|
91
102
|
return (
|
|
103
|
+
MIME_TO_EXT[normalized] ||
|
|
104
|
+
MIME_TO_EXT[normalizedLower] ||
|
|
92
105
|
MIME_TO_EXT[mimeType] ||
|
|
93
|
-
|
|
106
|
+
normalized.split('/')[1]?.toUpperCase() ||
|
|
94
107
|
'FILE'
|
|
95
108
|
);
|
|
96
109
|
}
|
|
97
110
|
|
|
111
|
+
export function getDocumentBadgeLabel(
|
|
112
|
+
mimeType: string,
|
|
113
|
+
filename?: string | null,
|
|
114
|
+
url?: string | null
|
|
115
|
+
): string {
|
|
116
|
+
const fromUrl = getFileExtensionFromUrl(url || undefined);
|
|
117
|
+
if (fromUrl) {
|
|
118
|
+
return officeExtensionShortLabels[fromUrl] || fromUrl;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const fromFilename = getFileExtensionFromUrl(filename || undefined);
|
|
122
|
+
if (fromFilename) {
|
|
123
|
+
return officeExtensionShortLabels[fromFilename] || fromFilename;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return getFileExtensionFromMime(mimeType);
|
|
127
|
+
}
|
|
128
|
+
|
|
98
129
|
export function countLines(content: string | undefined): number {
|
|
99
130
|
if (!content) return 0;
|
|
100
131
|
return content.split(/\r\n|\r|\n/).length;
|
|
@@ -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 {
|
|
@@ -48,6 +49,8 @@ const UploadButton: React.FC<UploadManagerProps> = ({
|
|
|
48
49
|
maxDocumentContentLength = 300000,
|
|
49
50
|
onUploadLoadingChange,
|
|
50
51
|
}) => {
|
|
52
|
+
// Per-document character limit for the inlined `<document_attachment>`
|
|
53
|
+
// content. Does NOT affect the full text uploaded as an asset.
|
|
51
54
|
const effectivePerDocumentLimit =
|
|
52
55
|
maxTotalMessagePayload ?? maxDocumentContentLength ?? 300000;
|
|
53
56
|
// State
|
|
@@ -98,7 +101,16 @@ const UploadButton: React.FC<UploadManagerProps> = ({
|
|
|
98
101
|
|
|
99
102
|
// Check if file is a document
|
|
100
103
|
const isDocumentFile = (file: File): boolean => {
|
|
101
|
-
const documentExtensions = [
|
|
104
|
+
const documentExtensions = [
|
|
105
|
+
'.pdf',
|
|
106
|
+
'.txt',
|
|
107
|
+
'.json',
|
|
108
|
+
'.xlsx',
|
|
109
|
+
'.csv',
|
|
110
|
+
'.md',
|
|
111
|
+
'.html',
|
|
112
|
+
...officeNativeExtensions,
|
|
113
|
+
];
|
|
102
114
|
const fileExt = `.${file.name.split('.').pop()?.toLowerCase()}`;
|
|
103
115
|
return documentExtensions.includes(fileExt);
|
|
104
116
|
};
|
|
@@ -115,115 +127,124 @@ const UploadButton: React.FC<UploadManagerProps> = ({
|
|
|
115
127
|
}, [isMediaAccepted, currentMediaCount, addError]);
|
|
116
128
|
|
|
117
129
|
// Handle unified file selection
|
|
118
|
-
const handleUnifiedFileSelection = useCallback(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
const handleUnifiedFileSelection = useCallback(
|
|
131
|
+
(files: FileList | File[]) => {
|
|
132
|
+
const fileArray = Array.from(files);
|
|
133
|
+
if (fileArray.length === 0) return;
|
|
134
|
+
|
|
135
|
+
const supportedFiles: File[] = [];
|
|
136
|
+
fileArray.forEach(file => {
|
|
137
|
+
if (isImageFile(file)) {
|
|
138
|
+
supportedFiles.push(file);
|
|
139
|
+
} else if (isDocumentFile(file)) {
|
|
140
|
+
supportedFiles.push(file);
|
|
141
|
+
} else {
|
|
142
|
+
addErrorRef.current({
|
|
143
|
+
message: `File "${file.name}" is not a supported image or document type`,
|
|
144
|
+
severity: 'warning',
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const totalSupported = supportedFiles.length;
|
|
150
|
+
if (totalSupported === 0) return;
|
|
151
|
+
|
|
152
|
+
const remainingSlots =
|
|
153
|
+
maxDocumentsPerMessage - currentMediaCountRef.current;
|
|
154
|
+
if (remainingSlots <= 0) {
|
|
129
155
|
addErrorRef.current({
|
|
130
|
-
message: `
|
|
156
|
+
message: `Maximum ${maxDocumentsPerMessage} media files allowed.`,
|
|
131
157
|
severity: 'warning',
|
|
132
158
|
});
|
|
159
|
+
return;
|
|
133
160
|
}
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
const totalSupported = supportedFiles.length;
|
|
137
|
-
if (totalSupported === 0) return;
|
|
138
161
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
message: `Maximum ${maxDocumentsPerMessage} media files allowed.`,
|
|
143
|
-
severity: 'warning',
|
|
144
|
-
});
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
162
|
+
const toProcess = supportedFiles.slice(0, remainingSlots);
|
|
163
|
+
const imageFiles = toProcess.filter(f => isImageFile(f));
|
|
164
|
+
const documentFiles = toProcess.filter(f => isDocumentFile(f));
|
|
147
165
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const documentFiles = toProcess.filter(f => isDocumentFile(f));
|
|
151
|
-
|
|
152
|
-
if (totalSupported > remainingSlots) {
|
|
153
|
-
const skipped = totalSupported - remainingSlots;
|
|
154
|
-
addErrorRef.current({
|
|
155
|
-
message:
|
|
156
|
-
t('upload.filesNotAddedMaxAllowed', {
|
|
157
|
-
count: skipped,
|
|
158
|
-
max: maxDocumentsPerMessage,
|
|
159
|
-
defaultValue: `${skipped} file(s) not added (maximum ${maxDocumentsPerMessage} files allowed).`,
|
|
160
|
-
}) ?? `${skipped} file(s) not added (maximum ${maxDocumentsPerMessage} files allowed).`,
|
|
161
|
-
severity: 'warning',
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Process images
|
|
166
|
-
if (imageFiles.length > 0) {
|
|
167
|
-
if (!isMediaAcceptedRef.current) {
|
|
166
|
+
if (totalSupported > remainingSlots) {
|
|
167
|
+
const skipped = totalSupported - remainingSlots;
|
|
168
168
|
addErrorRef.current({
|
|
169
169
|
message:
|
|
170
|
-
t('upload.
|
|
170
|
+
t('upload.filesNotAddedMaxAllowed', {
|
|
171
|
+
count: skipped,
|
|
172
|
+
max: maxDocumentsPerMessage,
|
|
173
|
+
defaultValue: `${skipped} file(s) not added (maximum ${maxDocumentsPerMessage} files allowed).`,
|
|
174
|
+
}) ??
|
|
175
|
+
`${skipped} file(s) not added (maximum ${maxDocumentsPerMessage} files allowed).`,
|
|
171
176
|
severity: 'warning',
|
|
172
177
|
});
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Process images
|
|
181
|
+
if (imageFiles.length > 0) {
|
|
182
|
+
if (!isMediaAcceptedRef.current) {
|
|
183
|
+
addErrorRef.current({
|
|
184
|
+
message:
|
|
185
|
+
t('upload.mediaNotAccepted') ?? 'Media uploads are not accepted',
|
|
186
|
+
severity: 'warning',
|
|
187
|
+
});
|
|
188
|
+
} else {
|
|
189
|
+
// Trigger image upload by creating a synthetic event
|
|
190
|
+
const imageInput = imageRef.current?.querySelector(
|
|
191
|
+
'input[type="file"]'
|
|
192
|
+
) as HTMLInputElement;
|
|
193
|
+
if (imageInput) {
|
|
194
|
+
const dataTransfer = new DataTransfer();
|
|
195
|
+
imageFiles.forEach(file => {
|
|
196
|
+
try {
|
|
197
|
+
dataTransfer.items.add(file);
|
|
198
|
+
} catch (err) {
|
|
199
|
+
console.warn('Failed to add image file to DataTransfer:', err);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Only proceed if we successfully added files
|
|
204
|
+
if (dataTransfer.files.length > 0) {
|
|
205
|
+
try {
|
|
206
|
+
imageInput.files = dataTransfer.files;
|
|
207
|
+
} catch {
|
|
208
|
+
// JSDOM and some environments do not allow assigning to input.files
|
|
209
|
+
}
|
|
210
|
+
const changeEvent = new Event('change', { bubbles: true });
|
|
211
|
+
imageInput.dispatchEvent(changeEvent);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Process documents – set loading early so skeleton shows for all entry points
|
|
218
|
+
if (documentFiles.length > 0) {
|
|
219
|
+
setIsDocumentLoading(true);
|
|
220
|
+
const documentInput = documentRef.current?.querySelector(
|
|
221
|
+
'input[type="file"]'
|
|
222
|
+
) as HTMLInputElement;
|
|
223
|
+
if (documentInput) {
|
|
177
224
|
const dataTransfer = new DataTransfer();
|
|
178
|
-
|
|
225
|
+
documentFiles.forEach(file => {
|
|
179
226
|
try {
|
|
180
227
|
dataTransfer.items.add(file);
|
|
181
228
|
} catch (err) {
|
|
182
|
-
console.warn('Failed to add
|
|
229
|
+
console.warn('Failed to add document file to DataTransfer:', err);
|
|
183
230
|
}
|
|
184
231
|
});
|
|
185
|
-
|
|
232
|
+
|
|
186
233
|
// Only proceed if we successfully added files
|
|
187
234
|
if (dataTransfer.files.length > 0) {
|
|
188
235
|
try {
|
|
189
|
-
|
|
236
|
+
documentInput.files = dataTransfer.files;
|
|
190
237
|
} catch {
|
|
191
238
|
// JSDOM and some environments do not allow assigning to input.files
|
|
192
239
|
}
|
|
193
240
|
const changeEvent = new Event('change', { bubbles: true });
|
|
194
|
-
|
|
241
|
+
documentInput.dispatchEvent(changeEvent);
|
|
195
242
|
}
|
|
196
243
|
}
|
|
197
244
|
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (documentFiles.length > 0) {
|
|
202
|
-
setIsDocumentLoading(true);
|
|
203
|
-
const documentInput = documentRef.current?.querySelector('input[type="file"]') as HTMLInputElement;
|
|
204
|
-
if (documentInput) {
|
|
205
|
-
const dataTransfer = new DataTransfer();
|
|
206
|
-
documentFiles.forEach(file => {
|
|
207
|
-
try {
|
|
208
|
-
dataTransfer.items.add(file);
|
|
209
|
-
} catch (err) {
|
|
210
|
-
console.warn('Failed to add document file to DataTransfer:', err);
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// Only proceed if we successfully added files
|
|
215
|
-
if (dataTransfer.files.length > 0) {
|
|
216
|
-
try {
|
|
217
|
-
documentInput.files = dataTransfer.files;
|
|
218
|
-
} catch {
|
|
219
|
-
// JSDOM and some environments do not allow assigning to input.files
|
|
220
|
-
}
|
|
221
|
-
const changeEvent = new Event('change', { bubbles: true });
|
|
222
|
-
documentInput.dispatchEvent(changeEvent);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}, [t]);
|
|
245
|
+
},
|
|
246
|
+
[t]
|
|
247
|
+
);
|
|
227
248
|
|
|
228
249
|
// Handle button click - open file chooser directly
|
|
229
250
|
const handleButtonClick = () => {
|
|
@@ -253,16 +274,17 @@ const UploadButton: React.FC<UploadManagerProps> = ({
|
|
|
253
274
|
}
|
|
254
275
|
|
|
255
276
|
const files: File[] = [];
|
|
256
|
-
|
|
277
|
+
|
|
257
278
|
// Helper to check if a file is already in the array
|
|
258
279
|
const isDuplicate = (file: File) => {
|
|
259
|
-
return files.some(
|
|
260
|
-
f
|
|
261
|
-
|
|
262
|
-
|
|
280
|
+
return files.some(
|
|
281
|
+
f =>
|
|
282
|
+
f.name === file.name &&
|
|
283
|
+
f.size === file.size &&
|
|
284
|
+
f.lastModified === file.lastModified
|
|
263
285
|
);
|
|
264
286
|
};
|
|
265
|
-
|
|
287
|
+
|
|
266
288
|
// Prefer clipboardData.files if available (most reliable and prevents duplicates)
|
|
267
289
|
// Only fall back to items if files is empty (some browsers only populate items)
|
|
268
290
|
if (clipboardData.files && clipboardData.files.length > 0) {
|
|
@@ -362,7 +384,6 @@ const UploadButton: React.FC<UploadManagerProps> = ({
|
|
|
362
384
|
id: string;
|
|
363
385
|
content: string;
|
|
364
386
|
mimeType: string;
|
|
365
|
-
sourceUrl?: string;
|
|
366
387
|
textAssetUrl?: string;
|
|
367
388
|
}[]
|
|
368
389
|
) => {
|
|
@@ -381,19 +402,39 @@ const UploadButton: React.FC<UploadManagerProps> = ({
|
|
|
381
402
|
// Process each document file
|
|
382
403
|
const processedDocuments = files.map(file => {
|
|
383
404
|
const escapedFileName = escapeAttributeValue(file.name);
|
|
384
|
-
const formattedContent = `<document_attachment filename="${escapedFileName}" type="${file.mimeType}">
|
|
385
405
|
|
|
386
|
-
|
|
406
|
+
let formattedContent: string;
|
|
387
407
|
|
|
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}">
|
|
388
411
|
</document_attachment>
|
|
389
412
|
|
|
390
|
-
<
|
|
391
|
-
${file.
|
|
392
|
-
</
|
|
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
|
+
}">
|
|
429
|
+
|
|
430
|
+
${inlinedContent}
|
|
431
|
+
|
|
432
|
+
</document_attachment>
|
|
393
433
|
|
|
394
434
|
<attachment_link>
|
|
395
435
|
${file.textAssetUrl || ''}
|
|
396
436
|
</attachment_link>`;
|
|
437
|
+
}
|
|
397
438
|
|
|
398
439
|
return {
|
|
399
440
|
name: file.name,
|
|
@@ -419,6 +460,7 @@ ${file.textAssetUrl || ''}
|
|
|
419
460
|
'.csv',
|
|
420
461
|
'.md',
|
|
421
462
|
'.html',
|
|
463
|
+
...officeNativeExtensions,
|
|
422
464
|
];
|
|
423
465
|
const MAX_FILE_SIZE = 15 * 1024 * 1024; // 15MB
|
|
424
466
|
|
|
@@ -505,18 +547,17 @@ ${file.textAssetUrl || ''}
|
|
|
505
547
|
addError(error);
|
|
506
548
|
};
|
|
507
549
|
|
|
508
|
-
|
|
509
550
|
const handleDocumentLoadingChange = useCallback(
|
|
510
551
|
(loading: boolean, fileCount?: number) => {
|
|
511
552
|
setIsDocumentLoading(loading);
|
|
512
|
-
setDocUploadingCount(loading ?
|
|
553
|
+
setDocUploadingCount(loading ? fileCount ?? 1 : 0);
|
|
513
554
|
},
|
|
514
555
|
[]
|
|
515
556
|
);
|
|
516
557
|
const handleImageLoadingChange = useCallback(
|
|
517
558
|
(loading: boolean, fileCount?: number) => {
|
|
518
559
|
setIsImageLoading(loading);
|
|
519
|
-
setImgUploadingCount(loading ?
|
|
560
|
+
setImgUploadingCount(loading ? fileCount ?? 1 : 0);
|
|
520
561
|
},
|
|
521
562
|
[]
|
|
522
563
|
);
|
|
@@ -526,7 +567,7 @@ ${file.textAssetUrl || ''}
|
|
|
526
567
|
}, [isLoading, uploadingFileCount, onUploadLoadingChange]);
|
|
527
568
|
|
|
528
569
|
return (
|
|
529
|
-
<div
|
|
570
|
+
<div
|
|
530
571
|
className={cx('memori--unified-upload-wrapper', {
|
|
531
572
|
'memori--dragging': isDragging,
|
|
532
573
|
})}
|
|
@@ -536,7 +577,7 @@ ${file.textAssetUrl || ''}
|
|
|
536
577
|
<input
|
|
537
578
|
ref={unifiedInputRef}
|
|
538
579
|
type="file"
|
|
539
|
-
accept=
|
|
580
|
+
accept={`.jpg,.jpeg,.png,.pdf,.txt,.json,.xlsx,.csv,.md,.html,${officeNativeExtensions.join(',')}`}
|
|
540
581
|
multiple
|
|
541
582
|
className="memori--upload-file-input"
|
|
542
583
|
onChange={handleFileInputChange}
|
|
@@ -557,7 +598,15 @@ ${file.textAssetUrl || ''}
|
|
|
557
598
|
)}
|
|
558
599
|
onClick={handleButtonClick}
|
|
559
600
|
disabled={isLoading || hasReachedMediaLimit}
|
|
560
|
-
title={
|
|
601
|
+
title={
|
|
602
|
+
t('upload.uploadFiles', {
|
|
603
|
+
shortcut:
|
|
604
|
+
/Mac|iPhone|iPod|iPad/i.test(navigator.platform) ||
|
|
605
|
+
navigator.userAgent.includes('Mac')
|
|
606
|
+
? 'Cmd'
|
|
607
|
+
: 'Ctrl',
|
|
608
|
+
}) ?? 'Upload files (drag & drop)'
|
|
609
|
+
}
|
|
561
610
|
>
|
|
562
611
|
{isLoading ? (
|
|
563
612
|
<Spin spinning className="memori--upload-icon" />
|
|
@@ -591,7 +640,6 @@ ${file.textAssetUrl || ''}
|
|
|
591
640
|
onDocumentError={handleDocumentError}
|
|
592
641
|
onValidateFile={validateDocumentFile}
|
|
593
642
|
onValidatePayloadSize={validatePayloadSize}
|
|
594
|
-
maxDocumentContentLength={effectivePerDocumentLimit}
|
|
595
643
|
/>
|
|
596
644
|
</div>
|
|
597
645
|
|
|
@@ -619,7 +667,9 @@ ${file.textAssetUrl || ''}
|
|
|
619
667
|
key={`${error.message}-${index}`}
|
|
620
668
|
open={true}
|
|
621
669
|
type={error.severity}
|
|
622
|
-
title={t('upload.uploadNotification', {
|
|
670
|
+
title={t('upload.uploadNotification', {
|
|
671
|
+
defaultValue: 'Upload notification',
|
|
672
|
+
})}
|
|
623
673
|
description={error.message}
|
|
624
674
|
onClose={() => removeError(error.message)}
|
|
625
675
|
width="350px"
|