@memori.ai/memori-react 8.29.1 → 8.30.1
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/README.md +1 -1
- package/dist/components/Chat/Chat.css +110 -9
- package/dist/components/Chat/Chat.d.ts +1 -0
- package/dist/components/Chat/Chat.js +72 -5
- package/dist/components/Chat/Chat.js.map +1 -1
- package/dist/components/ChatBubble/ChatBubble.d.ts +1 -0
- package/dist/components/ChatBubble/ChatBubble.js +2 -2
- package/dist/components/ChatBubble/ChatBubble.js.map +1 -1
- package/dist/components/ChatInputs/ChatInputs.js +2 -11
- package/dist/components/ChatInputs/ChatInputs.js.map +1 -1
- package/dist/components/MemoriWidget/MemoriWidget.d.ts +2 -1
- package/dist/components/MemoriWidget/MemoriWidget.js +13 -3
- package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/dist/components/UploadButton/UploadButton.js +8 -14
- package/dist/components/UploadButton/UploadButton.js.map +1 -1
- package/dist/components/UploadButton/UploadDocuments/UploadDocuments.d.ts +0 -1
- package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js +1 -20
- package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
- package/dist/helpers/constants.d.ts +1 -2
- package/dist/helpers/constants.js +2 -3
- package/dist/helpers/constants.js.map +1 -1
- package/dist/helpers/llmUsage.d.ts +60 -0
- package/dist/helpers/llmUsage.js +223 -0
- package/dist/helpers/llmUsage.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/locales/de.json +22 -0
- package/dist/locales/en.json +22 -0
- package/dist/locales/es.json +22 -0
- package/dist/locales/fr.json +22 -0
- package/dist/locales/it.json +22 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/esm/components/Chat/Chat.css +110 -9
- package/esm/components/Chat/Chat.d.ts +1 -0
- package/esm/components/Chat/Chat.js +74 -7
- package/esm/components/Chat/Chat.js.map +1 -1
- package/esm/components/ChatBubble/ChatBubble.d.ts +1 -0
- package/esm/components/ChatBubble/ChatBubble.js +2 -2
- package/esm/components/ChatBubble/ChatBubble.js.map +1 -1
- package/esm/components/ChatInputs/ChatInputs.js +2 -11
- package/esm/components/ChatInputs/ChatInputs.js.map +1 -1
- package/esm/components/MemoriWidget/MemoriWidget.d.ts +2 -1
- package/esm/components/MemoriWidget/MemoriWidget.js +13 -3
- package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/esm/components/UploadButton/UploadButton.js +8 -14
- package/esm/components/UploadButton/UploadButton.js.map +1 -1
- package/esm/components/UploadButton/UploadDocuments/UploadDocuments.d.ts +0 -1
- package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js +1 -20
- package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
- package/esm/helpers/constants.d.ts +1 -2
- package/esm/helpers/constants.js +1 -2
- package/esm/helpers/constants.js.map +1 -1
- package/esm/helpers/llmUsage.d.ts +60 -0
- package/esm/helpers/llmUsage.js +212 -0
- package/esm/helpers/llmUsage.js.map +1 -0
- package/esm/index.d.ts +1 -0
- package/esm/index.js +3 -1
- package/esm/index.js.map +1 -1
- package/esm/locales/de.json +22 -0
- package/esm/locales/en.json +22 -0
- package/esm/locales/es.json +22 -0
- package/esm/locales/fr.json +22 -0
- package/esm/locales/it.json +22 -0
- package/esm/version.d.ts +1 -1
- package/esm/version.js +1 -1
- package/package.json +3 -4
- package/src/components/Chat/Chat.css +110 -9
- package/src/components/Chat/Chat.stories.tsx +42 -0
- package/src/components/Chat/Chat.test.tsx +47 -0
- package/src/components/Chat/Chat.tsx +238 -5
- package/src/components/Chat/__snapshots__/Chat.test.tsx.snap +745 -0
- package/src/components/ChatBubble/ChatBubble.tsx +9 -0
- package/src/components/ChatInputs/ChatInputs.tsx +4 -15
- package/src/components/MemoriWidget/MemoriWidget.tsx +20 -2
- package/src/components/UploadButton/UploadButton.stories.tsx +3 -3
- package/src/components/UploadButton/UploadButton.tsx +8 -23
- package/src/components/UploadButton/UploadDocuments/UploadDocuments.tsx +1 -27
- package/src/helpers/constants.ts +1 -2
- package/src/helpers/llmUsage.ts +328 -0
- package/src/index.stories.tsx +2 -3
- package/src/index.tsx +5 -1
- package/src/locales/de.json +22 -0
- package/src/locales/en.json +22 -0
- package/src/locales/es.json +22 -0
- package/src/locales/fr.json +22 -0
- package/src/locales/it.json +22 -0
- package/src/version.ts +1 -1
|
@@ -61,6 +61,7 @@ export interface Props {
|
|
|
61
61
|
experts?: ExpertReference[];
|
|
62
62
|
showFunctionCache?: boolean;
|
|
63
63
|
showReasoning?: boolean;
|
|
64
|
+
usageHtml?: string;
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
const ChatBubble: React.FC<Props> = ({
|
|
@@ -84,6 +85,7 @@ const ChatBubble: React.FC<Props> = ({
|
|
|
84
85
|
experts,
|
|
85
86
|
showFunctionCache = false,
|
|
86
87
|
showReasoning = false,
|
|
88
|
+
usageHtml = '',
|
|
87
89
|
}) => {
|
|
88
90
|
const { t, i18n } = useTranslation();
|
|
89
91
|
const lang = i18n.language || 'en';
|
|
@@ -455,6 +457,13 @@ const ChatBubble: React.FC<Props> = ({
|
|
|
455
457
|
/>
|
|
456
458
|
)}
|
|
457
459
|
|
|
460
|
+
{!!usageHtml && (
|
|
461
|
+
<div
|
|
462
|
+
className="memori-chat--usage-inside-bubble"
|
|
463
|
+
dangerouslySetInnerHTML={{ __html: usageHtml }}
|
|
464
|
+
/>
|
|
465
|
+
)}
|
|
466
|
+
|
|
458
467
|
{(shouldShowCopyButtons ||
|
|
459
468
|
(message.generatedByAI && showAIicon) ||
|
|
460
469
|
(message.generatedByAI && showFunctionCache) ||
|
|
@@ -269,8 +269,10 @@ const ChatInputs: React.FC<Props> = ({
|
|
|
269
269
|
return;
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
-
|
|
273
|
-
|
|
272
|
+
// Only enforce a per-document limit. `maxTotalMessagePayload` is kept for backward compatibility
|
|
273
|
+
// and now acts as the per-document content length override.
|
|
274
|
+
const perDocumentLimit =
|
|
275
|
+
maxTotalMessagePayload ?? maxDocumentContentLength ?? 300000;
|
|
274
276
|
|
|
275
277
|
if (text.length > perDocumentLimit) {
|
|
276
278
|
e.preventDefault();
|
|
@@ -281,19 +283,6 @@ const ChatInputs: React.FC<Props> = ({
|
|
|
281
283
|
return;
|
|
282
284
|
}
|
|
283
285
|
|
|
284
|
-
const currentTotal = documentPreviewFiles.reduce(
|
|
285
|
-
(sum, f) => sum + f.content.length,
|
|
286
|
-
0
|
|
287
|
-
);
|
|
288
|
-
if (currentTotal + text.length > totalPayloadLimit) {
|
|
289
|
-
e.preventDefault();
|
|
290
|
-
toast(t('upload.pasteContentExceedsLimit', {
|
|
291
|
-
defaultValue:
|
|
292
|
-
'Pasted content exceeds the size limit. Try shortening the text or splitting it into smaller parts.',
|
|
293
|
-
}), { icon: '⚠️' });
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
286
|
e.preventDefault();
|
|
298
287
|
const displayName = t('upload.pastedText') || 'pasted-text';
|
|
299
288
|
const wrappedContent = `<document_attachment filename="pasted-text.txt" type="text/plain">
|
|
@@ -400,6 +400,7 @@ export interface Props {
|
|
|
400
400
|
showInputs?: boolean;
|
|
401
401
|
showDates?: boolean;
|
|
402
402
|
showContextPerLine?: boolean;
|
|
403
|
+
showMessageConsumption?: boolean;
|
|
403
404
|
showSettings?: boolean;
|
|
404
405
|
showClear?: boolean;
|
|
405
406
|
showOnlyLastMessages?: boolean;
|
|
@@ -469,6 +470,7 @@ const MemoriWidget = ({
|
|
|
469
470
|
showInputs = true,
|
|
470
471
|
showDates = false,
|
|
471
472
|
showContextPerLine = false,
|
|
473
|
+
showMessageConsumption = false,
|
|
472
474
|
showSettings,
|
|
473
475
|
showTypingText = false,
|
|
474
476
|
showClear = false,
|
|
@@ -662,6 +664,8 @@ const MemoriWidget = ({
|
|
|
662
664
|
null
|
|
663
665
|
);
|
|
664
666
|
const [hideEmissions, setHideEmissions] = useState(false);
|
|
667
|
+
const [runtimeShowMessageConsumption, setRuntimeShowMessageConsumption] =
|
|
668
|
+
useState(false);
|
|
665
669
|
|
|
666
670
|
const speechSynthesizerRef = useRef<any | null>(null);
|
|
667
671
|
const [memoriSpeaking, setMemoriSpeaking] = useState(false);
|
|
@@ -700,6 +704,12 @@ const MemoriWidget = ({
|
|
|
700
704
|
);
|
|
701
705
|
setAvatarType(getLocalConfig('avatarType', 'avatar3d'));
|
|
702
706
|
setHideEmissions(getLocalConfig('hideEmissions', false));
|
|
707
|
+
setRuntimeShowMessageConsumption(
|
|
708
|
+
getLocalConfig(
|
|
709
|
+
'showMessageConsumption',
|
|
710
|
+
showMessageConsumption ?? integrationConfig?.showMessageConsumption ?? false
|
|
711
|
+
)
|
|
712
|
+
);
|
|
703
713
|
|
|
704
714
|
if (!additionalInfo?.loginToken && !authToken) {
|
|
705
715
|
setLoginToken(getLocalConfig<typeof loginToken>('loginToken', undefined));
|
|
@@ -999,6 +1009,7 @@ const MemoriWidget = ({
|
|
|
999
1009
|
text: emission,
|
|
1000
1010
|
emitter: currentState.emitter,
|
|
1001
1011
|
media: currentState.emittedMedia ?? currentState.media,
|
|
1012
|
+
llmUsage: (currentState as any).llmUsage,
|
|
1002
1013
|
fromUser: false,
|
|
1003
1014
|
questionAnswered: msg,
|
|
1004
1015
|
generatedByAI: !!currentState.completion,
|
|
@@ -1010,7 +1021,7 @@ const MemoriWidget = ({
|
|
|
1010
1021
|
placeUncertaintyKm: currentState.currentUncertaintyKm,
|
|
1011
1022
|
tag: currentState.currentTag,
|
|
1012
1023
|
memoryTags: currentState.memoryTags,
|
|
1013
|
-
});
|
|
1024
|
+
} as any);
|
|
1014
1025
|
if (emission && shouldPlayAudio(emission)) {
|
|
1015
1026
|
handleSpeak(emission);
|
|
1016
1027
|
}
|
|
@@ -1097,7 +1108,7 @@ const MemoriWidget = ({
|
|
|
1097
1108
|
const emission = state?.emission ?? currentDialogState?.emission;
|
|
1098
1109
|
|
|
1099
1110
|
let translatedState = { ...state };
|
|
1100
|
-
let translatedMsg = null;
|
|
1111
|
+
let translatedMsg: any = null;
|
|
1101
1112
|
|
|
1102
1113
|
// Skip translation if not needed
|
|
1103
1114
|
if (
|
|
@@ -1113,6 +1124,7 @@ const MemoriWidget = ({
|
|
|
1113
1124
|
text: emission,
|
|
1114
1125
|
emitter: state.emitter,
|
|
1115
1126
|
media: state.emittedMedia ?? state.media,
|
|
1127
|
+
llmUsage: (state as any).llmUsage,
|
|
1116
1128
|
fromUser: false,
|
|
1117
1129
|
questionAnswered: msg,
|
|
1118
1130
|
contextVars: state.contextVars,
|
|
@@ -1170,6 +1182,7 @@ const MemoriWidget = ({
|
|
|
1170
1182
|
translatedText: t.text,
|
|
1171
1183
|
emitter: state.emitter,
|
|
1172
1184
|
media: state.emittedMedia ?? state.media,
|
|
1185
|
+
llmUsage: (state as any).llmUsage,
|
|
1173
1186
|
fromUser: false,
|
|
1174
1187
|
questionAnswered: msg,
|
|
1175
1188
|
generatedByAI: !!state.completion,
|
|
@@ -1190,6 +1203,7 @@ const MemoriWidget = ({
|
|
|
1190
1203
|
text: emission,
|
|
1191
1204
|
emitter: state.emitter,
|
|
1192
1205
|
media: state.emittedMedia ?? state.media,
|
|
1206
|
+
llmUsage: (state as any).llmUsage,
|
|
1193
1207
|
fromUser: false,
|
|
1194
1208
|
questionAnswered: msg,
|
|
1195
1209
|
contextVars: state.contextVars,
|
|
@@ -2184,6 +2198,7 @@ const MemoriWidget = ({
|
|
|
2184
2198
|
const enableUpload = !!(showUpload ?? integrationConfig?.showUpload);
|
|
2185
2199
|
|
|
2186
2200
|
const enableReasoning = !!(showReasoning ?? integrationConfig?.showReasoning);
|
|
2201
|
+
const enableMessageConsumption = !!runtimeShowMessageConsumption;
|
|
2187
2202
|
|
|
2188
2203
|
const showWhyThisAnswer =
|
|
2189
2204
|
integrationConfig?.showWhyThisAnswer === undefined
|
|
@@ -2431,6 +2446,7 @@ const MemoriWidget = ({
|
|
|
2431
2446
|
...m,
|
|
2432
2447
|
})),
|
|
2433
2448
|
fromUser: l.inbound,
|
|
2449
|
+
llmUsage: (l as any).llmUsage,
|
|
2434
2450
|
timestamp: l.timestamp,
|
|
2435
2451
|
emitter: l.emitter,
|
|
2436
2452
|
initial: i === 0,
|
|
@@ -2615,6 +2631,7 @@ const MemoriWidget = ({
|
|
|
2615
2631
|
...m,
|
|
2616
2632
|
})),
|
|
2617
2633
|
fromUser: l.inbound,
|
|
2634
|
+
llmUsage: (l as any).llmUsage,
|
|
2618
2635
|
timestamp: l.timestamp,
|
|
2619
2636
|
emitter: l.emitter,
|
|
2620
2637
|
initial: i === 0,
|
|
@@ -2977,6 +2994,7 @@ const MemoriWidget = ({
|
|
|
2977
2994
|
simulateUserPrompt,
|
|
2978
2995
|
showDates,
|
|
2979
2996
|
showContextPerLine,
|
|
2997
|
+
showMessageConsumption: enableMessageConsumption,
|
|
2980
2998
|
showAIicon,
|
|
2981
2999
|
showUpload: enableUpload,
|
|
2982
3000
|
showReasoning: enableReasoning,
|
|
@@ -120,9 +120,9 @@ A unified upload button that supports both images and documents with the followi
|
|
|
120
120
|
|
|
121
121
|
## Limits
|
|
122
122
|
|
|
123
|
-
- **Images**: Maximum 5 images,
|
|
124
|
-
- **Documents**: Maximum 5 documents,
|
|
125
|
-
- **Content**: Document content is limited to
|
|
123
|
+
- **Images**: Maximum 5 images, 15MB per file, formats: .jpg, .jpeg, .png
|
|
124
|
+
- **Documents**: Maximum 5 documents, 15MB per file, formats: .pdf, .txt, .json, .xlsx, .csv, .md
|
|
125
|
+
- **Content**: Document content is limited to 300,000 characters per document (truncated if exceeded)
|
|
126
126
|
`,
|
|
127
127
|
},
|
|
128
128
|
},
|
|
@@ -25,7 +25,7 @@ interface UploadManagerProps {
|
|
|
25
25
|
type?: string;
|
|
26
26
|
}[];
|
|
27
27
|
memoriID?: string;
|
|
28
|
-
/** Override
|
|
28
|
+
/** Override per-document content length limit (character count). */
|
|
29
29
|
maxTotalMessagePayload?: number;
|
|
30
30
|
/** Max attachments (docs + images) per message. */
|
|
31
31
|
maxDocumentsPerMessage?: number;
|
|
@@ -43,8 +43,10 @@ const UploadButton: React.FC<UploadManagerProps> = ({
|
|
|
43
43
|
memoriID = '',
|
|
44
44
|
maxTotalMessagePayload,
|
|
45
45
|
maxDocumentsPerMessage = 10,
|
|
46
|
-
maxDocumentContentLength =
|
|
46
|
+
maxDocumentContentLength = 300000,
|
|
47
47
|
}) => {
|
|
48
|
+
const effectivePerDocumentLimit =
|
|
49
|
+
maxTotalMessagePayload ?? maxDocumentContentLength ?? 300000;
|
|
48
50
|
// State
|
|
49
51
|
const [isLoading, setIsLoading] = useState(false);
|
|
50
52
|
const [errors, setErrors] = useState<
|
|
@@ -411,7 +413,7 @@ ${file.content}
|
|
|
411
413
|
'.md',
|
|
412
414
|
'.html',
|
|
413
415
|
];
|
|
414
|
-
const MAX_FILE_SIZE =
|
|
416
|
+
const MAX_FILE_SIZE = 15 * 1024 * 1024; // 15MB
|
|
415
417
|
|
|
416
418
|
if (!ALLOWED_FILE_TYPES.includes(fileExt)) {
|
|
417
419
|
addError({
|
|
@@ -438,29 +440,13 @@ ${file.content}
|
|
|
438
440
|
|
|
439
441
|
// Validate total payload size. Returns result so caller can avoid showing this error when truncation was already shown.
|
|
440
442
|
const validatePayloadSize = (
|
|
441
|
-
|
|
443
|
+
_newDocuments: {
|
|
442
444
|
name: string;
|
|
443
445
|
id: string;
|
|
444
446
|
content: string;
|
|
445
447
|
mimeType: string;
|
|
446
448
|
}[]
|
|
447
449
|
): { valid: boolean; message?: string } => {
|
|
448
|
-
const limit = maxTotalMessagePayload ?? 200000;
|
|
449
|
-
|
|
450
|
-
const existingDocuments = documentPreviewFiles.filter(
|
|
451
|
-
(file: any) => file.type === 'document'
|
|
452
|
-
);
|
|
453
|
-
|
|
454
|
-
const allDocuments = [...existingDocuments, ...newDocuments];
|
|
455
|
-
const totalPayloadSize = allDocuments.reduce(
|
|
456
|
-
(total, doc) => total + doc.content.length,
|
|
457
|
-
0
|
|
458
|
-
);
|
|
459
|
-
|
|
460
|
-
if (totalPayloadSize > limit) {
|
|
461
|
-
return { valid: false, message: '' };
|
|
462
|
-
}
|
|
463
|
-
|
|
464
450
|
return { valid: true };
|
|
465
451
|
};
|
|
466
452
|
|
|
@@ -476,7 +462,7 @@ ${file.content}
|
|
|
476
462
|
const validateImageFile = (file: File): boolean => {
|
|
477
463
|
const fileExt = `.${file.name.split('.').pop()?.toLowerCase()}`;
|
|
478
464
|
const ALLOWED_FILE_TYPES = ['.jpg', '.jpeg', '.png'];
|
|
479
|
-
const MAX_FILE_SIZE =
|
|
465
|
+
const MAX_FILE_SIZE = 15 * 1024 * 1024; // 15MB
|
|
480
466
|
|
|
481
467
|
if (
|
|
482
468
|
!ALLOWED_FILE_TYPES.includes(fileExt) &&
|
|
@@ -580,8 +566,7 @@ ${file.content}
|
|
|
580
566
|
onDocumentError={handleDocumentError}
|
|
581
567
|
onValidateFile={validateDocumentFile}
|
|
582
568
|
onValidatePayloadSize={validatePayloadSize}
|
|
583
|
-
|
|
584
|
-
maxDocumentContentLength={maxDocumentContentLength}
|
|
569
|
+
maxDocumentContentLength={effectivePerDocumentLimit}
|
|
585
570
|
/>
|
|
586
571
|
</div>
|
|
587
572
|
|
|
@@ -51,8 +51,6 @@ interface UploadDocumentsProps {
|
|
|
51
51
|
mimeType: string;
|
|
52
52
|
}[]
|
|
53
53
|
) => boolean | { valid: boolean; message?: string };
|
|
54
|
-
/** Same as total payload: overrides per-document content limit (character count). */
|
|
55
|
-
maxTotalMessagePayload?: number;
|
|
56
54
|
/** Per-document content character limit. */
|
|
57
55
|
maxDocumentContentLength?: number;
|
|
58
56
|
}
|
|
@@ -65,8 +63,7 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
|
|
|
65
63
|
onDocumentError,
|
|
66
64
|
onValidateFile,
|
|
67
65
|
onValidatePayloadSize,
|
|
68
|
-
|
|
69
|
-
maxDocumentContentLength = 200000,
|
|
66
|
+
maxDocumentContentLength = 300000,
|
|
70
67
|
}) => {
|
|
71
68
|
const { t } = useTranslation();
|
|
72
69
|
|
|
@@ -307,11 +304,6 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
|
|
|
307
304
|
mimeType: string;
|
|
308
305
|
}[] = [];
|
|
309
306
|
let hadTruncation = false;
|
|
310
|
-
let skippedDueToPayload = 0;
|
|
311
|
-
const payloadLimit = maxTotalMessagePayload ?? 200000;
|
|
312
|
-
const existingTotal = documentPreviewFiles
|
|
313
|
-
.filter((f: any) => f.type === 'document')
|
|
314
|
-
.reduce((sum: number, f: any) => sum + (f.content?.length ?? 0), 0);
|
|
315
307
|
|
|
316
308
|
for (const file of filesToProcess) {
|
|
317
309
|
if (!validateDocumentFile(file)) {
|
|
@@ -325,14 +317,6 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
|
|
|
325
317
|
if (wasTruncated) hadTruncation = true;
|
|
326
318
|
|
|
327
319
|
if (text) {
|
|
328
|
-
const processedSum = processedFiles.reduce(
|
|
329
|
-
(s, d) => s + d.content.length,
|
|
330
|
-
0
|
|
331
|
-
);
|
|
332
|
-
if (existingTotal + processedSum + text.length > payloadLimit) {
|
|
333
|
-
skippedDueToPayload += 1;
|
|
334
|
-
continue;
|
|
335
|
-
}
|
|
336
320
|
processedFiles.push({
|
|
337
321
|
name: file.name,
|
|
338
322
|
id: fileId,
|
|
@@ -351,16 +335,6 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
|
|
|
351
335
|
}
|
|
352
336
|
}
|
|
353
337
|
|
|
354
|
-
if (skippedDueToPayload > 0) {
|
|
355
|
-
onDocumentError?.({
|
|
356
|
-
message: t('upload.documentsNotAddedContextSize', {
|
|
357
|
-
count: skippedDueToPayload,
|
|
358
|
-
defaultValue: `${skippedDueToPayload} document(s) not added (context size limit).`,
|
|
359
|
-
}),
|
|
360
|
-
severity: 'warning',
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
|
|
364
338
|
// Add new documents to existing ones (only those that fit within payload)
|
|
365
339
|
if (processedFiles.length > 0) {
|
|
366
340
|
const existingDocuments = documentPreviewFiles.filter(
|
package/src/helpers/constants.ts
CHANGED
|
@@ -207,7 +207,6 @@ export const MAX_MSG_CHARS = 4000;
|
|
|
207
207
|
export const MAX_MSG_WORDS = 300;
|
|
208
208
|
|
|
209
209
|
export const maxDocumentsPerMessage = 10;
|
|
210
|
-
export const
|
|
211
|
-
export const maxDocumentContentLength = 200000;
|
|
210
|
+
export const maxDocumentContentLength = 300000;
|
|
212
211
|
export const pasteAsCardLineThreshold = 100;
|
|
213
212
|
export const pasteAsCardCharThreshold = 4200;
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
export interface LlmUsageOnLine {
|
|
2
|
+
provider?: string;
|
|
3
|
+
model?: string;
|
|
4
|
+
totalInputTokens?: number;
|
|
5
|
+
inputCacheReadTokens?: number;
|
|
6
|
+
inputCacheWriteTokens?: number;
|
|
7
|
+
outputTokens?: number;
|
|
8
|
+
durationMs?: number;
|
|
9
|
+
energyImpact?: {
|
|
10
|
+
energy?: number | { source?: string; parsedValue?: number };
|
|
11
|
+
energyUnit?: string;
|
|
12
|
+
gwp?: number | { source?: string; parsedValue?: number };
|
|
13
|
+
gwpUnit?: string;
|
|
14
|
+
wcf?: number | { source?: string; parsedValue?: number };
|
|
15
|
+
wcfUnit?: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type UsageBadgeType = 'llm' | 'energy' | 'co2' | 'water';
|
|
20
|
+
type NumericMetric = number | { source?: string; parsedValue?: number };
|
|
21
|
+
type ImpactMetricType = 'energy' | 'co2' | 'water';
|
|
22
|
+
type TranslateFn = (key: string, options?: { [key: string]: unknown }) => string;
|
|
23
|
+
|
|
24
|
+
export interface LlmUsageLabels {
|
|
25
|
+
llm: string;
|
|
26
|
+
model: string;
|
|
27
|
+
provider: string;
|
|
28
|
+
tokens: string;
|
|
29
|
+
input: string;
|
|
30
|
+
output: string;
|
|
31
|
+
cacheRead: string;
|
|
32
|
+
cacheWrite: string;
|
|
33
|
+
duration: string;
|
|
34
|
+
energy: string;
|
|
35
|
+
co2: string;
|
|
36
|
+
water: string;
|
|
37
|
+
usageBadgesHint: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const BADGE_EMOJI: Record<UsageBadgeType, string> = {
|
|
41
|
+
llm: '🤖',
|
|
42
|
+
energy: '⚡',
|
|
43
|
+
co2: '🌍',
|
|
44
|
+
water: '💧',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const escapeHtml = (value: string): string =>
|
|
48
|
+
value
|
|
49
|
+
.replaceAll('&', '&')
|
|
50
|
+
.replaceAll('<', '<')
|
|
51
|
+
.replaceAll('>', '>')
|
|
52
|
+
.replaceAll('"', '"')
|
|
53
|
+
.replaceAll("'", ''');
|
|
54
|
+
|
|
55
|
+
export const getMetricValue = (metric?: NumericMetric): number | undefined => {
|
|
56
|
+
if (typeof metric === 'number' && Number.isFinite(metric)) return metric;
|
|
57
|
+
if (!metric || typeof metric !== 'object') return undefined;
|
|
58
|
+
if (
|
|
59
|
+
typeof metric.parsedValue === 'number' &&
|
|
60
|
+
Number.isFinite(metric.parsedValue)
|
|
61
|
+
) {
|
|
62
|
+
return metric.parsedValue;
|
|
63
|
+
}
|
|
64
|
+
if (typeof metric.source === 'string') {
|
|
65
|
+
const parsed = Number(metric.source);
|
|
66
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const formatMetricValue = (value: number, locale = 'it-IT'): string => {
|
|
72
|
+
if (!Number.isFinite(value)) return '—';
|
|
73
|
+
if (value === 0) return '0';
|
|
74
|
+
|
|
75
|
+
const absValue = Math.abs(value);
|
|
76
|
+
return new Intl.NumberFormat(locale, {
|
|
77
|
+
minimumFractionDigits: 0,
|
|
78
|
+
maximumFractionDigits: absValue >= 1 ? 3 : 4,
|
|
79
|
+
}).format(value);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const formatIntegerValue = (value: number, locale = 'it-IT'): string => {
|
|
83
|
+
if (!Number.isFinite(value)) return '0';
|
|
84
|
+
return new Intl.NumberFormat(locale, { maximumFractionDigits: 0 }).format(
|
|
85
|
+
value,
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const formatDuration = (durationMs?: number, locale = 'it-IT'): string => {
|
|
90
|
+
if (typeof durationMs !== 'number' || !Number.isFinite(durationMs)) {
|
|
91
|
+
return '—';
|
|
92
|
+
}
|
|
93
|
+
if (durationMs < 1000) return `${formatIntegerValue(durationMs, locale)} ms`;
|
|
94
|
+
return `${formatMetricValue(durationMs / 1000, locale)} s`;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const floorToSingleDecimal = (value: number): number =>
|
|
98
|
+
Math.floor(value * 10) / 10;
|
|
99
|
+
|
|
100
|
+
const formatComparisonNumber = (value: number, locale = 'it-IT'): string =>
|
|
101
|
+
new Intl.NumberFormat(locale, {
|
|
102
|
+
minimumFractionDigits: 1,
|
|
103
|
+
maximumFractionDigits: 1,
|
|
104
|
+
}).format(floorToSingleDecimal(value));
|
|
105
|
+
|
|
106
|
+
const formatReadableDuration = (seconds: number, locale = 'it-IT'): string => {
|
|
107
|
+
if (!Number.isFinite(seconds) || seconds <= 0) return '0 s';
|
|
108
|
+
if (seconds < 60) return `${formatComparisonNumber(seconds, locale)} s`;
|
|
109
|
+
|
|
110
|
+
const minutes = seconds / 60;
|
|
111
|
+
if (minutes < 60) return `${formatComparisonNumber(minutes, locale)} min`;
|
|
112
|
+
|
|
113
|
+
return `${formatComparisonNumber(minutes / 60, locale)} h`;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const formatReadableDistance = (meters: number, locale = 'it-IT'): string => {
|
|
117
|
+
if (!Number.isFinite(meters) || meters <= 0) return '0 m';
|
|
118
|
+
if (meters < 1000) return `${formatComparisonNumber(meters, locale)} m`;
|
|
119
|
+
return `${formatComparisonNumber(meters / 1000, locale)} km`;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const getApiUnitToBaseFactor = (
|
|
123
|
+
unitFromApi: string | undefined,
|
|
124
|
+
metricType: ImpactMetricType,
|
|
125
|
+
): number => {
|
|
126
|
+
const u = (unitFromApi ?? '').trim().toLowerCase();
|
|
127
|
+
if (metricType === 'energy') {
|
|
128
|
+
if (u === 'kwh') return 1;
|
|
129
|
+
if (u === 'wh') return 0.001;
|
|
130
|
+
if (u === 'mwh') return 0.000001;
|
|
131
|
+
return 1;
|
|
132
|
+
}
|
|
133
|
+
if (metricType === 'co2') {
|
|
134
|
+
if (u === 'kg' || u === 'kgco2eq') return 1;
|
|
135
|
+
if (u === 'g') return 0.001;
|
|
136
|
+
if (u === 'mg') return 0.000001;
|
|
137
|
+
return 1;
|
|
138
|
+
}
|
|
139
|
+
if (u === 'l') return 1;
|
|
140
|
+
if (u === 'ml') return 0.001;
|
|
141
|
+
if (u === 'μl' || u === 'ul') return 0.000001;
|
|
142
|
+
return 1;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export const formatImpactInReadableUnit = (
|
|
146
|
+
value: number,
|
|
147
|
+
metricType: ImpactMetricType,
|
|
148
|
+
locale = 'it-IT',
|
|
149
|
+
): string => {
|
|
150
|
+
const absValue = Math.abs(value);
|
|
151
|
+
|
|
152
|
+
if (metricType === 'energy') {
|
|
153
|
+
if (absValue >= 1) return `${formatMetricValue(value, locale)} kWh`;
|
|
154
|
+
const wattHours = value * 1000;
|
|
155
|
+
if (Math.abs(wattHours) >= 1) return `${formatMetricValue(wattHours, locale)} Wh`;
|
|
156
|
+
return `${formatMetricValue(wattHours * 1000, locale)} mWh`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (metricType === 'co2') {
|
|
160
|
+
if (absValue >= 1) return `${formatMetricValue(value, locale)} kg`;
|
|
161
|
+
const grams = value * 1000;
|
|
162
|
+
if (Math.abs(grams) >= 1) return `${formatMetricValue(grams, locale)} g`;
|
|
163
|
+
return `${formatMetricValue(grams * 1000, locale)} mg`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (absValue >= 1) return `${formatMetricValue(value, locale)} L`;
|
|
167
|
+
const milliliters = value * 1000;
|
|
168
|
+
if (Math.abs(milliliters) >= 1) return `${formatMetricValue(milliliters, locale)} mL`;
|
|
169
|
+
return `${formatMetricValue(milliliters * 1000, locale)} μL`;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export const formatImpactWithApiUnit = (
|
|
173
|
+
value: number,
|
|
174
|
+
unitFromApi: string | undefined,
|
|
175
|
+
fallbackUnit: string,
|
|
176
|
+
metricType: ImpactMetricType,
|
|
177
|
+
locale = 'it-IT',
|
|
178
|
+
): string => {
|
|
179
|
+
const factor = getApiUnitToBaseFactor(
|
|
180
|
+
unitFromApi ?? fallbackUnit,
|
|
181
|
+
metricType,
|
|
182
|
+
);
|
|
183
|
+
const baseValue = value * factor;
|
|
184
|
+
return formatImpactInReadableUnit(baseValue, metricType, locale);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
export const getImpactComparison = (
|
|
188
|
+
value: number,
|
|
189
|
+
metricType: ImpactMetricType,
|
|
190
|
+
locale = 'it-IT',
|
|
191
|
+
t: TranslateFn,
|
|
192
|
+
): string => {
|
|
193
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
194
|
+
return t('chatLogs.impactComparisonUnavailable');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (metricType === 'energy') {
|
|
198
|
+
const ledSeconds = (value * 1000 * 3600) / 10;
|
|
199
|
+
return t('chatLogs.impactComparisonEnergy', {
|
|
200
|
+
duration: formatReadableDuration(ledSeconds, locale),
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (metricType === 'co2') {
|
|
205
|
+
const averageCarMeters = (value / 0.12) * 1000;
|
|
206
|
+
return t('chatLogs.impactComparisonCo2', {
|
|
207
|
+
distance: formatReadableDistance(averageCarMeters, locale),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const drops = (value * 1000) / 0.05;
|
|
212
|
+
return t('chatLogs.impactComparisonWater', {
|
|
213
|
+
count: formatComparisonNumber(drops, locale),
|
|
214
|
+
});
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const buildUsageBadgeHtml = ({
|
|
218
|
+
badgeType,
|
|
219
|
+
badgeClassName,
|
|
220
|
+
label,
|
|
221
|
+
value,
|
|
222
|
+
lineIndex,
|
|
223
|
+
emoji,
|
|
224
|
+
}: {
|
|
225
|
+
badgeType: UsageBadgeType;
|
|
226
|
+
badgeClassName: string;
|
|
227
|
+
label: string;
|
|
228
|
+
value?: string;
|
|
229
|
+
lineIndex: number;
|
|
230
|
+
emoji: string;
|
|
231
|
+
}): string => {
|
|
232
|
+
const escapedLabel = escapeHtml(label);
|
|
233
|
+
const valueHtml = value
|
|
234
|
+
? `<span class="memori-chat--usage-badge-value">${value}</span>`
|
|
235
|
+
: '';
|
|
236
|
+
const content =
|
|
237
|
+
valueHtml ||
|
|
238
|
+
`<span class="memori-chat--usage-badge-label">${escapedLabel}</span>`;
|
|
239
|
+
|
|
240
|
+
return `<button type="button" class="memori-chat--usage-badge ${badgeClassName}" data-llm-badge-type="${badgeType}" data-line-index="${lineIndex}" aria-label="${escapedLabel}">${emoji} ${content}</button>`;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
export const buildLlmUsageHtml = (
|
|
244
|
+
usage: LlmUsageOnLine,
|
|
245
|
+
labels: LlmUsageLabels,
|
|
246
|
+
lineIndex: number,
|
|
247
|
+
locale = 'it-IT',
|
|
248
|
+
): string => {
|
|
249
|
+
const badges = [
|
|
250
|
+
buildUsageBadgeHtml({
|
|
251
|
+
badgeType: 'llm',
|
|
252
|
+
badgeClassName: 'memori-chat--usage-badge-llm',
|
|
253
|
+
label: labels.llm,
|
|
254
|
+
lineIndex,
|
|
255
|
+
emoji: BADGE_EMOJI.llm,
|
|
256
|
+
}),
|
|
257
|
+
];
|
|
258
|
+
|
|
259
|
+
const energy = getMetricValue(usage.energyImpact?.energy);
|
|
260
|
+
const gwp = getMetricValue(usage.energyImpact?.gwp);
|
|
261
|
+
const wcf = getMetricValue(usage.energyImpact?.wcf);
|
|
262
|
+
|
|
263
|
+
if (typeof energy === 'number') {
|
|
264
|
+
const energyFormatted = formatImpactWithApiUnit(
|
|
265
|
+
energy,
|
|
266
|
+
usage.energyImpact?.energyUnit,
|
|
267
|
+
'kWh',
|
|
268
|
+
'energy',
|
|
269
|
+
locale,
|
|
270
|
+
);
|
|
271
|
+
badges.push(
|
|
272
|
+
buildUsageBadgeHtml({
|
|
273
|
+
badgeType: 'energy',
|
|
274
|
+
badgeClassName: 'memori-chat--usage-badge-energy',
|
|
275
|
+
label: `${labels.energy} ${energyFormatted}`,
|
|
276
|
+
value: escapeHtml(energyFormatted),
|
|
277
|
+
lineIndex,
|
|
278
|
+
emoji: BADGE_EMOJI.energy,
|
|
279
|
+
}),
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (typeof gwp === 'number') {
|
|
284
|
+
const co2Formatted = formatImpactWithApiUnit(
|
|
285
|
+
gwp,
|
|
286
|
+
usage.energyImpact?.gwpUnit,
|
|
287
|
+
'kgCO2eq',
|
|
288
|
+
'co2',
|
|
289
|
+
locale,
|
|
290
|
+
);
|
|
291
|
+
badges.push(
|
|
292
|
+
buildUsageBadgeHtml({
|
|
293
|
+
badgeType: 'co2',
|
|
294
|
+
badgeClassName: 'memori-chat--usage-badge-co2',
|
|
295
|
+
label: `${labels.co2} ${co2Formatted}`,
|
|
296
|
+
value: escapeHtml(co2Formatted),
|
|
297
|
+
lineIndex,
|
|
298
|
+
emoji: BADGE_EMOJI.co2,
|
|
299
|
+
}),
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (typeof wcf === 'number') {
|
|
304
|
+
const waterFormatted = formatImpactWithApiUnit(
|
|
305
|
+
wcf,
|
|
306
|
+
usage.energyImpact?.wcfUnit,
|
|
307
|
+
'L',
|
|
308
|
+
'water',
|
|
309
|
+
locale,
|
|
310
|
+
);
|
|
311
|
+
badges.push(
|
|
312
|
+
buildUsageBadgeHtml({
|
|
313
|
+
badgeType: 'water',
|
|
314
|
+
badgeClassName: 'memori-chat--usage-badge-water',
|
|
315
|
+
label: `${labels.water} ${waterFormatted}`,
|
|
316
|
+
value: escapeHtml(waterFormatted),
|
|
317
|
+
lineIndex,
|
|
318
|
+
emoji: BADGE_EMOJI.water,
|
|
319
|
+
}),
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return `<div class="memori-chat--llm-usage" data-llm-usage><hr class="memori-chat--llm-usage-hr" /><p class="memori-chat--llm-usage-hint">${escapeHtml(
|
|
324
|
+
labels.usageBadgesHint,
|
|
325
|
+
)}</p><div class="memori-chat--llm-usage-badges">${badges.join(
|
|
326
|
+
'',
|
|
327
|
+
)}</div></div>`;
|
|
328
|
+
};
|
package/src/index.stories.tsx
CHANGED
|
@@ -86,8 +86,8 @@ WithChatHistory.args = {
|
|
|
86
86
|
showChatHistory: true,
|
|
87
87
|
};
|
|
88
88
|
|
|
89
|
-
export const
|
|
90
|
-
|
|
89
|
+
export const WithUploadNoTotalPayloadLimit = Template.bind({});
|
|
90
|
+
WithUploadNoTotalPayloadLimit.args = {
|
|
91
91
|
ownerUserName: 'nzambello',
|
|
92
92
|
memoriName: 'Nicola',
|
|
93
93
|
tenantID: 'www.aisuru.com',
|
|
@@ -98,7 +98,6 @@ WithUploadWithMaxTotalMessagePayload.args = {
|
|
|
98
98
|
spokenLang: 'IT',
|
|
99
99
|
enableAudio: true,
|
|
100
100
|
showUpload: true,
|
|
101
|
-
maxTotalMessagePayload: 300000,
|
|
102
101
|
};
|
|
103
102
|
|
|
104
103
|
export const WithMaxTextareaCharacters = Template.bind({});
|