@memori.ai/memori-react 8.18.1 → 8.19.0
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 +15 -0
- package/README.md +2 -0
- package/dist/components/Chat/Chat.d.ts +2 -0
- package/dist/components/Chat/Chat.js +2 -2
- package/dist/components/Chat/Chat.js.map +1 -1
- package/dist/components/ChatInputs/ChatInputs.d.ts +2 -0
- package/dist/components/ChatInputs/ChatInputs.js +12 -25
- package/dist/components/ChatInputs/ChatInputs.js.map +1 -1
- package/dist/components/ChatTextArea/ChatTextArea.css +19 -1
- package/dist/components/ChatTextArea/ChatTextArea.d.ts +1 -0
- package/dist/components/ChatTextArea/ChatTextArea.js +46 -27
- package/dist/components/ChatTextArea/ChatTextArea.js.map +1 -1
- package/dist/components/FilePreview/FilePreview.css +0 -1
- package/dist/components/MemoriWidget/MemoriWidget.d.ts +3 -1
- package/dist/components/MemoriWidget/MemoriWidget.js +3 -1
- package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/dist/components/UploadButton/UploadButton.js +10 -16
- package/dist/components/UploadButton/UploadButton.js.map +1 -1
- package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js +3 -26
- package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
- package/dist/components/UploadButton/UploadImages/UploadImages.js +8 -14
- package/dist/components/UploadButton/UploadImages/UploadImages.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/locales/de.json +1 -5
- package/dist/locales/en.json +2 -5
- package/dist/locales/es.json +1 -5
- package/dist/locales/fr.json +1 -5
- package/dist/locales/it.json +2 -6
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/esm/components/Chat/Chat.d.ts +2 -0
- package/esm/components/Chat/Chat.js +2 -2
- package/esm/components/Chat/Chat.js.map +1 -1
- package/esm/components/ChatInputs/ChatInputs.d.ts +2 -0
- package/esm/components/ChatInputs/ChatInputs.js +12 -25
- package/esm/components/ChatInputs/ChatInputs.js.map +1 -1
- package/esm/components/ChatTextArea/ChatTextArea.css +19 -1
- package/esm/components/ChatTextArea/ChatTextArea.d.ts +1 -0
- package/esm/components/ChatTextArea/ChatTextArea.js +47 -28
- package/esm/components/ChatTextArea/ChatTextArea.js.map +1 -1
- package/esm/components/FilePreview/FilePreview.css +0 -1
- package/esm/components/MemoriWidget/MemoriWidget.d.ts +3 -1
- package/esm/components/MemoriWidget/MemoriWidget.js +3 -1
- package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/esm/components/UploadButton/UploadButton.js +10 -16
- package/esm/components/UploadButton/UploadButton.js.map +1 -1
- package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js +3 -26
- package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
- package/esm/components/UploadButton/UploadImages/UploadImages.js +8 -14
- package/esm/components/UploadButton/UploadImages/UploadImages.js.map +1 -1
- package/esm/index.d.ts +2 -0
- package/esm/index.js +4 -2
- package/esm/index.js.map +1 -1
- package/esm/locales/de.json +1 -5
- package/esm/locales/en.json +2 -5
- package/esm/locales/es.json +1 -5
- package/esm/locales/fr.json +1 -5
- package/esm/locales/it.json +2 -6
- package/esm/version.d.ts +1 -1
- package/esm/version.js +1 -1
- package/package.json +1 -1
- package/src/components/Chat/Chat.tsx +8 -0
- package/src/components/ChatInputs/ChatInputs.test.tsx +4 -4
- package/src/components/ChatInputs/ChatInputs.tsx +19 -41
- package/src/components/ChatTextArea/ChatTextArea.css +19 -1
- package/src/components/ChatTextArea/ChatTextArea.tsx +38 -7
- package/src/components/FilePreview/FilePreview.css +0 -1
- package/src/components/MemoriWidget/MemoriWidget.tsx +8 -0
- package/src/components/UploadButton/UploadButton.tsx +10 -17
- package/src/components/UploadButton/UploadDocuments/UploadDocuments.tsx +3 -28
- package/src/components/UploadButton/UploadImages/UploadImages.tsx +7 -15
- package/src/components/layouts/layouts.stories.tsx +1 -0
- package/src/index.stories.tsx +15 -0
- package/src/index.tsx +11 -1
- package/src/locales/de.json +1 -5
- package/src/locales/en.json +2 -5
- package/src/locales/es.json +1 -5
- package/src/locales/fr.json +1 -5
- package/src/locales/it.json +2 -6
- package/src/version.ts +1 -1
package/esm/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const version = '8.
|
|
1
|
+
export const version = '8.19.0';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
|
@@ -76,6 +76,10 @@ export interface Props {
|
|
|
76
76
|
showFunctionCache?: boolean;
|
|
77
77
|
/** Override total document payload and per-document content limit (character count). */
|
|
78
78
|
maxTotalMessagePayload?: number;
|
|
79
|
+
/** When true, pasted text is not added as a document attachment. Default false. */
|
|
80
|
+
disablePastedText?: boolean;
|
|
81
|
+
/** Max characters in chat textarea; shows counter when set. */
|
|
82
|
+
maxTextareaCharacters?: number;
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
const Chat: React.FC<Props> = ({
|
|
@@ -128,6 +132,8 @@ const Chat: React.FC<Props> = ({
|
|
|
128
132
|
isChatlogPanel = false,
|
|
129
133
|
showFunctionCache = false,
|
|
130
134
|
maxTotalMessagePayload,
|
|
135
|
+
disablePastedText = false,
|
|
136
|
+
maxTextareaCharacters,
|
|
131
137
|
}) => {
|
|
132
138
|
const [isTextareaExpanded, setIsTextareaExpanded] = useState(false);
|
|
133
139
|
const [isDragging, setIsDragging] = useState(false);
|
|
@@ -546,6 +552,8 @@ const Chat: React.FC<Props> = ({
|
|
|
546
552
|
showMicrophone={showMicrophone}
|
|
547
553
|
memoriID={memori?.memoriID}
|
|
548
554
|
maxTotalMessagePayload={maxTotalMessagePayload}
|
|
555
|
+
disablePastedText={disablePastedText}
|
|
556
|
+
maxTextareaCharacters={maxTextareaCharacters}
|
|
549
557
|
/>
|
|
550
558
|
)}
|
|
551
559
|
</div>
|
|
@@ -9,10 +9,10 @@ import {
|
|
|
9
9
|
|
|
10
10
|
jest.mock('react-hot-toast', () => ({
|
|
11
11
|
__esModule: true,
|
|
12
|
-
default: {
|
|
12
|
+
default: Object.assign(jest.fn(), {
|
|
13
13
|
success: jest.fn(),
|
|
14
14
|
error: jest.fn(),
|
|
15
|
-
},
|
|
15
|
+
}),
|
|
16
16
|
}));
|
|
17
17
|
|
|
18
18
|
// jsdom does not define DataTransfer; UploadButton's paste handler uses it when clipboard has files
|
|
@@ -312,7 +312,7 @@ describe('paste as card (long text becomes attachment)', () => {
|
|
|
312
312
|
);
|
|
313
313
|
});
|
|
314
314
|
|
|
315
|
-
it('
|
|
315
|
+
it('shows warning toast and prevents paste when content exceeds size limit', async () => {
|
|
316
316
|
const { MAX_DOCUMENT_CONTENT_LENGTH } = require('../../helpers/constants');
|
|
317
317
|
const tooLongText = 'x'.repeat(MAX_DOCUMENT_CONTENT_LENGTH + 1);
|
|
318
318
|
render(
|
|
@@ -328,7 +328,7 @@ describe('paste as card (long text becomes attachment)', () => {
|
|
|
328
328
|
|
|
329
329
|
await waitFor(() => {
|
|
330
330
|
const toast = require('react-hot-toast').default;
|
|
331
|
-
expect(toast
|
|
331
|
+
expect(toast).toHaveBeenCalled();
|
|
332
332
|
});
|
|
333
333
|
expect(screen.queryByText('upload.pastedText')).toBeNull();
|
|
334
334
|
});
|
|
@@ -45,6 +45,10 @@ export interface Props {
|
|
|
45
45
|
onTextareaExpanded?: (expanded: boolean) => void;
|
|
46
46
|
/** Override total document payload limit (character count). */
|
|
47
47
|
maxTotalMessagePayload?: number;
|
|
48
|
+
/** When true, pasted text is not added as a document attachment (normal paste only). Default false. */
|
|
49
|
+
disablePastedText?: boolean;
|
|
50
|
+
/** Max characters in textarea; shows counter (e.g. "0 / 500") when set. */
|
|
51
|
+
maxTextareaCharacters?: number;
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
const ChatInputs: React.FC<Props> = ({
|
|
@@ -69,6 +73,8 @@ const ChatInputs: React.FC<Props> = ({
|
|
|
69
73
|
client,
|
|
70
74
|
onTextareaExpanded,
|
|
71
75
|
maxTotalMessagePayload,
|
|
76
|
+
disablePastedText = false,
|
|
77
|
+
maxTextareaCharacters,
|
|
72
78
|
}) => {
|
|
73
79
|
const { t } = useTranslation();
|
|
74
80
|
|
|
@@ -93,8 +99,6 @@ const ChatInputs: React.FC<Props> = ({
|
|
|
93
99
|
dialog: { postMediumDeselectedEvent: null },
|
|
94
100
|
};
|
|
95
101
|
|
|
96
|
-
const totalPayloadLimit = maxTotalMessagePayload ?? MAX_TOTAL_MESSAGE_PAYLOAD;
|
|
97
|
-
|
|
98
102
|
/**
|
|
99
103
|
* Handles sending a message, including any attached files
|
|
100
104
|
*/
|
|
@@ -111,17 +115,6 @@ const ChatInputs: React.FC<Props> = ({
|
|
|
111
115
|
) => {
|
|
112
116
|
if (isTyping) return;
|
|
113
117
|
|
|
114
|
-
const totalContentLength = files.reduce((sum, f) => sum + f.content.length, 0);
|
|
115
|
-
if (totalContentLength > totalPayloadLimit) {
|
|
116
|
-
toast.error(
|
|
117
|
-
t('upload.contextSizeExceedsLimit', {
|
|
118
|
-
defaultValue:
|
|
119
|
-
'Context size exceeds the limit. Try reducing the number of files or content in the conversation.',
|
|
120
|
-
})
|
|
121
|
-
);
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
118
|
const mediaWithIds = files.map((file, index) => {
|
|
126
119
|
const generatedMediumID =
|
|
127
120
|
file.mediumID ||
|
|
@@ -164,19 +157,6 @@ const ChatInputs: React.FC<Props> = ({
|
|
|
164
157
|
|
|
165
158
|
if (sendOnEnter === 'keypress' && userMessage?.length > 0) {
|
|
166
159
|
stopListening();
|
|
167
|
-
const totalContentLength = documentPreviewFiles.reduce(
|
|
168
|
-
(sum, f) => sum + f.content.length,
|
|
169
|
-
0
|
|
170
|
-
);
|
|
171
|
-
if (totalContentLength > totalPayloadLimit) {
|
|
172
|
-
toast.error(
|
|
173
|
-
t('upload.contextSizeExceedsLimit', {
|
|
174
|
-
defaultValue:
|
|
175
|
-
'Context size exceeds the limit. Try reducing the number of files or content in the conversation.',
|
|
176
|
-
})
|
|
177
|
-
);
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
160
|
const mediaWithIds = documentPreviewFiles.map((file, index) => {
|
|
181
161
|
const generatedMediumID =
|
|
182
162
|
file.mediumID ||
|
|
@@ -240,6 +220,7 @@ const ChatInputs: React.FC<Props> = ({
|
|
|
240
220
|
*/
|
|
241
221
|
const handleTextareaPaste = useCallback(
|
|
242
222
|
(e: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
|
223
|
+
if (disablePastedText) return;
|
|
243
224
|
if (e.clipboardData.files?.length) return;
|
|
244
225
|
const text = e.clipboardData.getData('text/plain');
|
|
245
226
|
if (!text?.trim()) return;
|
|
@@ -256,32 +237,28 @@ const ChatInputs: React.FC<Props> = ({
|
|
|
256
237
|
return;
|
|
257
238
|
}
|
|
258
239
|
|
|
259
|
-
|
|
240
|
+
const totalPayloadLimit = maxTotalMessagePayload ?? MAX_TOTAL_MESSAGE_PAYLOAD;
|
|
260
241
|
const perDocumentLimit = maxTotalMessagePayload ?? MAX_DOCUMENT_CONTENT_LENGTH;
|
|
242
|
+
|
|
261
243
|
if (text.length > perDocumentLimit) {
|
|
262
244
|
e.preventDefault();
|
|
263
|
-
toast.
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
})
|
|
268
|
-
);
|
|
245
|
+
toast(t('upload.pasteContentExceedsLimit', {
|
|
246
|
+
defaultValue:
|
|
247
|
+
'Pasted content exceeds the size limit. Try shortening the text or splitting it into smaller parts.',
|
|
248
|
+
}), { icon: '⚠️' });
|
|
269
249
|
return;
|
|
270
250
|
}
|
|
271
251
|
|
|
272
|
-
const totalPayloadLimit = maxTotalMessagePayload ?? MAX_TOTAL_MESSAGE_PAYLOAD;
|
|
273
252
|
const currentTotal = documentPreviewFiles.reduce(
|
|
274
253
|
(sum, f) => sum + f.content.length,
|
|
275
254
|
0
|
|
276
255
|
);
|
|
277
256
|
if (currentTotal + text.length > totalPayloadLimit) {
|
|
278
257
|
e.preventDefault();
|
|
279
|
-
toast.
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
})
|
|
284
|
-
);
|
|
258
|
+
toast(t('upload.pasteContentExceedsLimit', {
|
|
259
|
+
defaultValue:
|
|
260
|
+
'Pasted content exceeds the size limit. Try shortening the text or splitting it into smaller parts.',
|
|
261
|
+
}), { icon: '⚠️' });
|
|
285
262
|
return;
|
|
286
263
|
}
|
|
287
264
|
|
|
@@ -314,7 +291,7 @@ ${text}
|
|
|
314
291
|
) => [...prev, newFile]
|
|
315
292
|
);
|
|
316
293
|
},
|
|
317
|
-
[documentPreviewFiles, maxTotalMessagePayload, t]
|
|
294
|
+
[documentPreviewFiles, disablePastedText, maxTotalMessagePayload, t]
|
|
318
295
|
);
|
|
319
296
|
|
|
320
297
|
const isDisabled =
|
|
@@ -371,6 +348,7 @@ ${text}
|
|
|
371
348
|
onBlur={onTextareaBlur}
|
|
372
349
|
onExpandedChange={handleTextareaExpanded}
|
|
373
350
|
disabled={textareaDisabled}
|
|
351
|
+
maxTextareaCharacters={maxTextareaCharacters}
|
|
374
352
|
/>
|
|
375
353
|
</div>
|
|
376
354
|
|
|
@@ -5,10 +5,23 @@
|
|
|
5
5
|
min-height: 36px;
|
|
6
6
|
box-sizing: border-box;
|
|
7
7
|
flex: 1;
|
|
8
|
-
|
|
8
|
+
flex-direction: column;
|
|
9
9
|
transition: height 0.2s ease-in-out;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
.memori-chat-textarea--with-counter {
|
|
13
|
+
align-items: stretch;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.memori-chat-textarea--counter {
|
|
17
|
+
min-height: 18px;
|
|
18
|
+
flex-shrink: 0;
|
|
19
|
+
padding: 2px 0 4px 0;
|
|
20
|
+
color: var(--memori-text-color, #666);
|
|
21
|
+
font-size: 12px;
|
|
22
|
+
line-height: 1.2;
|
|
23
|
+
}
|
|
24
|
+
|
|
12
25
|
.memori-chat-textarea--expanded {
|
|
13
26
|
min-height: 36px;
|
|
14
27
|
}
|
|
@@ -40,6 +53,11 @@
|
|
|
40
53
|
transition: height 0.2s ease-in-out;
|
|
41
54
|
}
|
|
42
55
|
|
|
56
|
+
.memori-chat-textarea--with-counter .memori-chat-textarea--inner {
|
|
57
|
+
min-height: 36px;
|
|
58
|
+
flex: 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
43
61
|
.memori-chat-textarea--expanded .memori-chat-textarea--inner {
|
|
44
62
|
max-height: 208px;
|
|
45
63
|
}
|
|
@@ -15,6 +15,8 @@ export interface Props {
|
|
|
15
15
|
onFocus?: (e: React.FocusEvent) => void;
|
|
16
16
|
onBlur?: (e: React.FocusEvent) => void;
|
|
17
17
|
onExpandedChange?: (expanded: boolean) => void;
|
|
18
|
+
/** When set, shows a character counter (e.g. "0 / 500") above the textarea and enforces the max length. */
|
|
19
|
+
maxTextareaCharacters?: number;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
const ChatTextArea: React.FC<Props> = ({
|
|
@@ -26,6 +28,7 @@ const ChatTextArea: React.FC<Props> = ({
|
|
|
26
28
|
onFocus,
|
|
27
29
|
onBlur,
|
|
28
30
|
onExpandedChange,
|
|
31
|
+
maxTextareaCharacters,
|
|
29
32
|
}) => {
|
|
30
33
|
const { t } = useTranslation();
|
|
31
34
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
@@ -78,22 +81,48 @@ const ChatTextArea: React.FC<Props> = ({
|
|
|
78
81
|
}
|
|
79
82
|
}, [value]);
|
|
80
83
|
|
|
84
|
+
const displayValue =
|
|
85
|
+
maxTextareaCharacters != null
|
|
86
|
+
? value.slice(0, maxTextareaCharacters)
|
|
87
|
+
: value;
|
|
88
|
+
|
|
89
|
+
// Keep parent state in sync when value exceeds limit (e.g. paste or prop change)
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (
|
|
92
|
+
maxTextareaCharacters != null &&
|
|
93
|
+
value.length > maxTextareaCharacters
|
|
94
|
+
) {
|
|
95
|
+
onChange(value.slice(0, maxTextareaCharacters));
|
|
96
|
+
}
|
|
97
|
+
}, [maxTextareaCharacters, value, onChange]);
|
|
98
|
+
|
|
81
99
|
return (
|
|
82
100
|
<div
|
|
83
101
|
data-testid="chat-textarea"
|
|
84
102
|
className={cx('memori-chat-textarea', {
|
|
85
103
|
'memori-chat-textarea--disabled': disabled,
|
|
104
|
+
'memori-chat-textarea--with-counter': maxTextareaCharacters != null,
|
|
86
105
|
})}
|
|
87
106
|
>
|
|
107
|
+
{maxTextareaCharacters != null && (
|
|
108
|
+
<div className="memori-chat-textarea--counter" aria-live="polite">
|
|
109
|
+
{displayValue.length} / {maxTextareaCharacters}
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
88
112
|
<div ref={innerRef} className="memori-chat-textarea--inner">
|
|
89
113
|
<textarea
|
|
90
114
|
ref={textareaRef}
|
|
91
115
|
className="memori-chat-textarea--input"
|
|
92
116
|
disabled={disabled}
|
|
93
|
-
value={
|
|
117
|
+
value={displayValue}
|
|
94
118
|
placeholder={t('placeholder', 'Ask a question') || 'Ask a question'}
|
|
95
119
|
onChange={e => {
|
|
96
|
-
|
|
120
|
+
const next = e.target.value;
|
|
121
|
+
if (maxTextareaCharacters != null) {
|
|
122
|
+
onChange(next.slice(0, maxTextareaCharacters));
|
|
123
|
+
} else {
|
|
124
|
+
onChange(next);
|
|
125
|
+
}
|
|
97
126
|
}}
|
|
98
127
|
onKeyDownCapture={e => {
|
|
99
128
|
// On mobile/tablet only: Enter creates a new line instead of sending.
|
|
@@ -106,10 +135,12 @@ const ChatTextArea: React.FC<Props> = ({
|
|
|
106
135
|
e.preventDefault();
|
|
107
136
|
|
|
108
137
|
const el = textareaRef.current;
|
|
109
|
-
const start = el?.selectionStart ??
|
|
110
|
-
const end = el?.selectionEnd ??
|
|
111
|
-
|
|
112
|
-
|
|
138
|
+
const start = el?.selectionStart ?? displayValue.length;
|
|
139
|
+
const end = el?.selectionEnd ?? displayValue.length;
|
|
140
|
+
let nextValue = `${displayValue.slice(0, start)}\n${displayValue.slice(end)}`;
|
|
141
|
+
if (maxTextareaCharacters != null) {
|
|
142
|
+
nextValue = nextValue.slice(0, maxTextareaCharacters);
|
|
143
|
+
}
|
|
113
144
|
onChange(nextValue);
|
|
114
145
|
|
|
115
146
|
// Restore caret right after the inserted newline
|
|
@@ -130,7 +161,7 @@ const ChatTextArea: React.FC<Props> = ({
|
|
|
130
161
|
onPaste={onPaste}
|
|
131
162
|
onFocus={onFocus}
|
|
132
163
|
onBlur={onBlur}
|
|
133
|
-
maxLength={100000}
|
|
164
|
+
maxLength={maxTextareaCharacters ?? 100000}
|
|
134
165
|
/>
|
|
135
166
|
</div>
|
|
136
167
|
</div>
|
|
@@ -435,6 +435,10 @@ export interface Props {
|
|
|
435
435
|
__WEBCOMPONENT__?: boolean;
|
|
436
436
|
/** Override total document payload and per-document content limit (character count). Default from constants. */
|
|
437
437
|
maxTotalMessagePayload?: number;
|
|
438
|
+
/** When true, pasted text is not added as a document attachment (normal paste only). Default false. */
|
|
439
|
+
disablePastedText?: boolean;
|
|
440
|
+
/** Max characters in chat textarea; shows counter and disables paste-as-attachment when set. */
|
|
441
|
+
maxTextareaCharacters?: number;
|
|
438
442
|
}
|
|
439
443
|
|
|
440
444
|
const MemoriWidget = ({
|
|
@@ -491,6 +495,8 @@ const MemoriWidget = ({
|
|
|
491
495
|
applyVarsToRoot = false,
|
|
492
496
|
showFunctionCache = false,
|
|
493
497
|
maxTotalMessagePayload,
|
|
498
|
+
disablePastedText = false,
|
|
499
|
+
maxTextareaCharacters,
|
|
494
500
|
}: Props) => {
|
|
495
501
|
const { t, i18n } = useTranslation();
|
|
496
502
|
|
|
@@ -3025,6 +3031,8 @@ const MemoriWidget = ({
|
|
|
3025
3031
|
experts,
|
|
3026
3032
|
useMathFormatting: applyMathFormatting,
|
|
3027
3033
|
maxTotalMessagePayload,
|
|
3034
|
+
disablePastedText,
|
|
3035
|
+
maxTextareaCharacters,
|
|
3028
3036
|
};
|
|
3029
3037
|
|
|
3030
3038
|
const integrationBackground =
|
|
@@ -114,7 +114,7 @@ const UploadButton: React.FC<UploadManagerProps> = ({
|
|
|
114
114
|
} else {
|
|
115
115
|
addErrorRef.current({
|
|
116
116
|
message: `File "${file.name}" is not a supported image or document type`,
|
|
117
|
-
severity: '
|
|
117
|
+
severity: 'warning',
|
|
118
118
|
});
|
|
119
119
|
}
|
|
120
120
|
});
|
|
@@ -126,7 +126,7 @@ const UploadButton: React.FC<UploadManagerProps> = ({
|
|
|
126
126
|
if (remainingSlots <= 0) {
|
|
127
127
|
addErrorRef.current({
|
|
128
128
|
message: `Maximum ${MAX_DOCUMENTS_PER_MESSAGE} media files allowed.`,
|
|
129
|
-
severity: '
|
|
129
|
+
severity: 'warning',
|
|
130
130
|
});
|
|
131
131
|
return;
|
|
132
132
|
}
|
|
@@ -144,7 +144,7 @@ const UploadButton: React.FC<UploadManagerProps> = ({
|
|
|
144
144
|
max: MAX_DOCUMENTS_PER_MESSAGE,
|
|
145
145
|
defaultValue: `${skipped} file(s) not added (maximum ${MAX_DOCUMENTS_PER_MESSAGE} files allowed).`,
|
|
146
146
|
}) ?? `${skipped} file(s) not added (maximum ${MAX_DOCUMENTS_PER_MESSAGE} files allowed).`,
|
|
147
|
-
severity: '
|
|
147
|
+
severity: 'warning',
|
|
148
148
|
});
|
|
149
149
|
}
|
|
150
150
|
|
|
@@ -153,11 +153,8 @@ const UploadButton: React.FC<UploadManagerProps> = ({
|
|
|
153
153
|
if (!isMediaAcceptedRef.current) {
|
|
154
154
|
addErrorRef.current({
|
|
155
155
|
message:
|
|
156
|
-
t('upload.
|
|
157
|
-
|
|
158
|
-
defaultValue: `${imageFiles.length} image(s) not added (media uploads not accepted).`,
|
|
159
|
-
}) ?? `${imageFiles.length} image(s) not added (media uploads not accepted).`,
|
|
160
|
-
severity: 'info',
|
|
156
|
+
t('upload.mediaNotAccepted') ?? 'Media uploads are not accepted',
|
|
157
|
+
severity: 'warning',
|
|
161
158
|
});
|
|
162
159
|
} else {
|
|
163
160
|
// Trigger image upload by creating a synthetic event
|
|
@@ -417,7 +414,7 @@ ${file.content}
|
|
|
417
414
|
message: `File type "${fileExt}" is not supported. Please use: ${ALLOWED_FILE_TYPES.join(
|
|
418
415
|
', '
|
|
419
416
|
)}`,
|
|
420
|
-
severity: '
|
|
417
|
+
severity: 'warning',
|
|
421
418
|
});
|
|
422
419
|
return false;
|
|
423
420
|
}
|
|
@@ -427,7 +424,7 @@ ${file.content}
|
|
|
427
424
|
message: `File "${file.name}" exceeds ${
|
|
428
425
|
MAX_FILE_SIZE / 1024 / 1024
|
|
429
426
|
}MB limit`,
|
|
430
|
-
severity: '
|
|
427
|
+
severity: 'warning',
|
|
431
428
|
});
|
|
432
429
|
return false;
|
|
433
430
|
}
|
|
@@ -458,11 +455,7 @@ ${file.content}
|
|
|
458
455
|
);
|
|
459
456
|
|
|
460
457
|
if (totalPayloadSize > limit) {
|
|
461
|
-
|
|
462
|
-
defaultValue:
|
|
463
|
-
'Context size exceeds the limit. Try reducing the number of files or content in the conversation.',
|
|
464
|
-
});
|
|
465
|
-
return { valid: false, message: typeof msg === 'string' ? msg : String(msg) };
|
|
458
|
+
return { valid: false, message: '' };
|
|
466
459
|
}
|
|
467
460
|
|
|
468
461
|
return { valid: true };
|
|
@@ -490,7 +483,7 @@ ${file.content}
|
|
|
490
483
|
message: `File type "${fileExt}" is not supported. Please use: ${ALLOWED_FILE_TYPES.join(
|
|
491
484
|
', '
|
|
492
485
|
)}`,
|
|
493
|
-
severity: '
|
|
486
|
+
severity: 'warning',
|
|
494
487
|
});
|
|
495
488
|
return false;
|
|
496
489
|
}
|
|
@@ -500,7 +493,7 @@ ${file.content}
|
|
|
500
493
|
message: `File "${file.name}" exceeds ${
|
|
501
494
|
MAX_FILE_SIZE / 1024 / 1024
|
|
502
495
|
}MB limit`,
|
|
503
|
-
severity: '
|
|
496
|
+
severity: 'warning',
|
|
504
497
|
});
|
|
505
498
|
return false;
|
|
506
499
|
}
|
|
@@ -250,13 +250,6 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
|
|
|
250
250
|
'>',
|
|
251
251
|
perDocumentLimit
|
|
252
252
|
);
|
|
253
|
-
onDocumentError?.({
|
|
254
|
-
message: t('upload.contextSizeExceedsLimit', {
|
|
255
|
-
defaultValue:
|
|
256
|
-
'Context size exceeds the limit. Try reducing the number of files or content in the conversation.',
|
|
257
|
-
}),
|
|
258
|
-
severity: 'error',
|
|
259
|
-
});
|
|
260
253
|
wasTruncated = true;
|
|
261
254
|
text =
|
|
262
255
|
text.substring(0, perDocumentLimit) +
|
|
@@ -295,7 +288,7 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
|
|
|
295
288
|
max: maxDocuments ?? 10,
|
|
296
289
|
defaultValue: `${skipped} document(s) not added (maximum ${maxDocuments ?? 10} files allowed).`,
|
|
297
290
|
}) ?? `${skipped} document(s) not added (maximum ${maxDocuments ?? 10} files allowed).`,
|
|
298
|
-
severity: '
|
|
291
|
+
severity: 'warning',
|
|
299
292
|
});
|
|
300
293
|
}
|
|
301
294
|
|
|
@@ -349,14 +342,6 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
|
|
|
349
342
|
content: text,
|
|
350
343
|
mimeType: file.type,
|
|
351
344
|
});
|
|
352
|
-
} else {
|
|
353
|
-
onDocumentError?.({
|
|
354
|
-
message: t('upload.documentNotReadable', {
|
|
355
|
-
name: file.name,
|
|
356
|
-
defaultValue: `Document "${file.name}" could not be read or was empty.`,
|
|
357
|
-
}),
|
|
358
|
-
severity: 'info',
|
|
359
|
-
});
|
|
360
345
|
}
|
|
361
346
|
} catch (error) {
|
|
362
347
|
console.error('File processing error:', error);
|
|
@@ -364,7 +349,7 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
|
|
|
364
349
|
message: `${
|
|
365
350
|
error instanceof Error ? error.message : 'Unknown error'
|
|
366
351
|
}`,
|
|
367
|
-
severity: '
|
|
352
|
+
severity: 'warning',
|
|
368
353
|
});
|
|
369
354
|
}
|
|
370
355
|
}
|
|
@@ -375,17 +360,7 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
|
|
|
375
360
|
count: skippedDueToPayload,
|
|
376
361
|
defaultValue: `${skippedDueToPayload} document(s) not added (context size limit).`,
|
|
377
362
|
}),
|
|
378
|
-
severity: '
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
if (processedFiles.length === 0 && filesToProcess.length > 0) {
|
|
383
|
-
onDocumentError?.({
|
|
384
|
-
message: t('upload.noDocumentsAdded', {
|
|
385
|
-
defaultValue:
|
|
386
|
-
'No documents could be added. Check file type, size (max 10MB), and that the file is readable.',
|
|
387
|
-
}),
|
|
388
|
-
severity: 'info',
|
|
363
|
+
severity: 'warning',
|
|
389
364
|
});
|
|
390
365
|
}
|
|
391
366
|
|
|
@@ -102,7 +102,7 @@ const UploadImages: React.FC<UploadImagesProps> = ({
|
|
|
102
102
|
max: maxImages,
|
|
103
103
|
defaultValue: `${skipped} image(s) not added (maximum ${maxImages} files allowed).`,
|
|
104
104
|
}) ?? `${skipped} image(s) not added (maximum ${maxImages} files allowed).`,
|
|
105
|
-
severity: '
|
|
105
|
+
severity: 'warning',
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
108
|
|
|
@@ -122,14 +122,6 @@ const UploadImages: React.FC<UploadImagesProps> = ({
|
|
|
122
122
|
});
|
|
123
123
|
|
|
124
124
|
if (validFiles.length === 0) {
|
|
125
|
-
onImageError?.({
|
|
126
|
-
message:
|
|
127
|
-
t('upload.noImagesAdded', {
|
|
128
|
-
defaultValue:
|
|
129
|
-
'No images could be added. Check file type (.jpg, .jpeg, .png) and size (max 10MB).',
|
|
130
|
-
}) ?? 'No images could be added. Check file type (.jpg, .jpeg, .png) and size (max 10MB).',
|
|
131
|
-
severity: 'info',
|
|
132
|
-
});
|
|
133
125
|
if (imageInputRef.current) {
|
|
134
126
|
imageInputRef.current.value = '';
|
|
135
127
|
}
|
|
@@ -251,7 +243,7 @@ const UploadImages: React.FC<UploadImagesProps> = ({
|
|
|
251
243
|
} catch (error) {
|
|
252
244
|
onImageError?.({
|
|
253
245
|
message: t('upload.uploadFailed') ?? 'Upload failed',
|
|
254
|
-
severity: '
|
|
246
|
+
severity: 'warning',
|
|
255
247
|
});
|
|
256
248
|
resolve(null);
|
|
257
249
|
}
|
|
@@ -269,7 +261,7 @@ const UploadImages: React.FC<UploadImagesProps> = ({
|
|
|
269
261
|
reader.onerror = () => {
|
|
270
262
|
onImageError?.({
|
|
271
263
|
message: t('upload.fileReadingFailed') ?? 'File reading failed',
|
|
272
|
-
severity: '
|
|
264
|
+
severity: 'warning',
|
|
273
265
|
});
|
|
274
266
|
resolve(null);
|
|
275
267
|
};
|
|
@@ -290,7 +282,7 @@ const UploadImages: React.FC<UploadImagesProps> = ({
|
|
|
290
282
|
} catch (error) {
|
|
291
283
|
onImageError?.({
|
|
292
284
|
message: t('upload.uploadFailed') ?? 'Upload failed',
|
|
293
|
-
severity: '
|
|
285
|
+
severity: 'warning',
|
|
294
286
|
});
|
|
295
287
|
} finally {
|
|
296
288
|
setIsLoading(false);
|
|
@@ -398,7 +390,7 @@ const UploadImages: React.FC<UploadImagesProps> = ({
|
|
|
398
390
|
} catch (error) {
|
|
399
391
|
onImageError?.({
|
|
400
392
|
message: t('upload.uploadFailed') ?? 'Upload failed',
|
|
401
|
-
severity: '
|
|
393
|
+
severity: 'warning',
|
|
402
394
|
});
|
|
403
395
|
}
|
|
404
396
|
} else {
|
|
@@ -416,7 +408,7 @@ const UploadImages: React.FC<UploadImagesProps> = ({
|
|
|
416
408
|
reader.onerror = () => {
|
|
417
409
|
onImageError?.({
|
|
418
410
|
message: t('upload.fileReadingFailed') ?? 'File reading failed',
|
|
419
|
-
severity: '
|
|
411
|
+
severity: 'warning',
|
|
420
412
|
});
|
|
421
413
|
setIsLoading(false);
|
|
422
414
|
};
|
|
@@ -425,7 +417,7 @@ const UploadImages: React.FC<UploadImagesProps> = ({
|
|
|
425
417
|
} catch (error) {
|
|
426
418
|
onImageError?.({
|
|
427
419
|
message: t('upload.uploadFailed') ?? 'Upload failed',
|
|
428
|
-
severity: '
|
|
420
|
+
severity: 'warning',
|
|
429
421
|
});
|
|
430
422
|
setIsLoading(false);
|
|
431
423
|
}
|
package/src/index.stories.tsx
CHANGED
|
@@ -101,6 +101,21 @@ WithUploadWithMaxTotalMessagePayload.args = {
|
|
|
101
101
|
maxTotalMessagePayload: 300000,
|
|
102
102
|
};
|
|
103
103
|
|
|
104
|
+
export const WithMaxTextareaCharacters = Template.bind({});
|
|
105
|
+
WithMaxTextareaCharacters.args = {
|
|
106
|
+
ownerUserName: 'nzambello',
|
|
107
|
+
memoriName: 'Nicola',
|
|
108
|
+
tenantID: 'www.aisuru.com',
|
|
109
|
+
engineURL: 'https://engine.memori.ai',
|
|
110
|
+
apiURL: 'https://backend.memori.ai',
|
|
111
|
+
baseURL: 'https://www.aisuru.com',
|
|
112
|
+
uiLang: 'IT',
|
|
113
|
+
spokenLang: 'IT',
|
|
114
|
+
enableAudio: true,
|
|
115
|
+
showUpload: true,
|
|
116
|
+
maxTextareaCharacters: 500,
|
|
117
|
+
};
|
|
118
|
+
|
|
104
119
|
export const WithPrivateAgent = Template.bind({});
|
|
105
120
|
WithPrivateAgent.args = {
|
|
106
121
|
memoriName: 'Test Private',
|
package/src/index.tsx
CHANGED
|
@@ -48,7 +48,7 @@ export interface Props {
|
|
|
48
48
|
__WEBCOMPONENT__?: boolean;
|
|
49
49
|
showClear?: boolean;
|
|
50
50
|
showOnlyLastMessages?: boolean;
|
|
51
|
-
showTypingText?: boolean;
|
|
51
|
+
showTypingText?: boolean;
|
|
52
52
|
showLogin?: boolean;
|
|
53
53
|
showUpload?: boolean;
|
|
54
54
|
showReasoning?: boolean;
|
|
@@ -77,6 +77,10 @@ export interface Props {
|
|
|
77
77
|
applyVarsToRoot?: boolean;
|
|
78
78
|
/** Override total document payload and per-document content limit (character count). Default from constants (200000). */
|
|
79
79
|
maxTotalMessagePayload?: number;
|
|
80
|
+
/** When true, pasted text is not added as a document attachment (normal paste only). Default false. */
|
|
81
|
+
disablePastedText?: boolean;
|
|
82
|
+
/** Max characters allowed in the chat textarea. When set, shows a counter (e.g. "0 / 500") and disables paste-as-attachment by default. */
|
|
83
|
+
maxTextareaCharacters?: number;
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
const getPreferredLanguages = () => {
|
|
@@ -156,6 +160,8 @@ const Memori: React.FC<Props> = ({
|
|
|
156
160
|
applyVarsToRoot = false,
|
|
157
161
|
__WEBCOMPONENT__ = false,
|
|
158
162
|
maxTotalMessagePayload,
|
|
163
|
+
disablePastedText = false,
|
|
164
|
+
maxTextareaCharacters,
|
|
159
165
|
}) => {
|
|
160
166
|
const [memori, setMemori] = useState<IMemori>();
|
|
161
167
|
const [tenant, setTenant] = useState<Tenant>();
|
|
@@ -469,6 +475,8 @@ const Memori: React.FC<Props> = ({
|
|
|
469
475
|
userAvatar={userAvatar}
|
|
470
476
|
applyVarsToRoot={applyVarsToRoot}
|
|
471
477
|
maxTotalMessagePayload={maxTotalMessagePayload}
|
|
478
|
+
disablePastedText={disablePastedText ?? (maxTextareaCharacters != null)}
|
|
479
|
+
maxTextareaCharacters={maxTextareaCharacters}
|
|
472
480
|
disableTextEnteredEvents={disableTextEnteredEvents}
|
|
473
481
|
// From layout, from client if allowed
|
|
474
482
|
{...clientAttributes}
|
|
@@ -570,5 +578,7 @@ Memori.propTypes = {
|
|
|
570
578
|
autoStart: PropTypes.bool,
|
|
571
579
|
applyVarsToRoot: PropTypes.bool,
|
|
572
580
|
maxTotalMessagePayload: PropTypes.number,
|
|
581
|
+
disablePastedText: PropTypes.bool,
|
|
582
|
+
maxTextareaCharacters: PropTypes.number,
|
|
573
583
|
};
|
|
574
584
|
export default Memori;
|