@memori.ai/memori-react 9.0.0-alpha.5 → 9.0.0-alpha.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.
Files changed (186) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/dist/components/AgeVerificationModal/AgeVerificationModal.js +5 -2
  3. package/dist/components/AgeVerificationModal/AgeVerificationModal.js.map +1 -1
  4. package/dist/components/Avatar/Avatar.js +5 -10
  5. package/dist/components/Avatar/Avatar.js.map +1 -1
  6. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +8 -1
  7. package/dist/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
  8. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js +1 -1
  9. package/dist/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js.map +1 -1
  10. package/dist/components/Avatar/AvatarView/AvatarComponent/constants.d.ts +2 -0
  11. package/dist/components/Avatar/AvatarView/AvatarComponent/constants.js +14 -5
  12. package/dist/components/Avatar/AvatarView/AvatarComponent/constants.js.map +1 -1
  13. package/dist/components/ChatBubble/ChatBubble.js +19 -15
  14. package/dist/components/ChatBubble/ChatBubble.js.map +1 -1
  15. package/dist/components/CompletionProviderStatus/CompletionProviderStatus.js +6 -3
  16. package/dist/components/CompletionProviderStatus/CompletionProviderStatus.js.map +1 -1
  17. package/dist/components/DateSelector/DateSelector.d.ts +2 -1
  18. package/dist/components/DateSelector/DateSelector.js +17 -7
  19. package/dist/components/DateSelector/DateSelector.js.map +1 -1
  20. package/dist/components/FilePreview/FilePreview.js +22 -5
  21. package/dist/components/FilePreview/FilePreview.js.map +1 -1
  22. package/dist/components/MediaWidget/MediaItemWidget.js +14 -12
  23. package/dist/components/MediaWidget/MediaItemWidget.js.map +1 -1
  24. package/dist/components/MediaWidget/MediaItemWidget.utils.d.ts +3 -2
  25. package/dist/components/MediaWidget/MediaItemWidget.utils.js +28 -6
  26. package/dist/components/MediaWidget/MediaItemWidget.utils.js.map +1 -1
  27. package/dist/components/MediaWidget/MediaPreviewModal.js +6 -7
  28. package/dist/components/MediaWidget/MediaPreviewModal.js.map +1 -1
  29. package/dist/components/MemoriWidget/MemoriWidget.js +632 -280
  30. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  31. package/dist/components/UploadButton/UploadButton.js +21 -5
  32. package/dist/components/UploadButton/UploadButton.js.map +1 -1
  33. package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js +123 -59
  34. package/dist/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
  35. package/dist/components/layouts/WebsiteAssistant/website-assistant.css +2 -1
  36. package/dist/helpers/constants.d.ts +3 -0
  37. package/dist/helpers/constants.js +27 -1
  38. package/dist/helpers/constants.js.map +1 -1
  39. package/dist/helpers/credits.d.ts +5 -2
  40. package/dist/helpers/credits.js +5 -1
  41. package/dist/helpers/credits.js.map +1 -1
  42. package/dist/helpers/message.d.ts +1 -1
  43. package/dist/helpers/message.js +4 -4
  44. package/dist/helpers/message.js.map +1 -1
  45. package/dist/helpers/nats/getNatsConfig.d.ts +9 -0
  46. package/dist/helpers/nats/getNatsConfig.js +65 -0
  47. package/dist/helpers/nats/getNatsConfig.js.map +1 -0
  48. package/dist/helpers/nats/isSessionExpiredError.d.ts +3 -0
  49. package/dist/helpers/nats/isSessionExpiredError.js +36 -0
  50. package/dist/helpers/nats/isSessionExpiredError.js.map +1 -0
  51. package/dist/helpers/nats/useNats.d.ts +12 -0
  52. package/dist/helpers/nats/useNats.js +75 -0
  53. package/dist/helpers/nats/useNats.js.map +1 -0
  54. package/dist/helpers/nats/useNatsSession.d.ts +28 -0
  55. package/dist/helpers/nats/useNatsSession.js +212 -0
  56. package/dist/helpers/nats/useNatsSession.js.map +1 -0
  57. package/dist/helpers/utils.d.ts +21 -0
  58. package/dist/helpers/utils.js +75 -4
  59. package/dist/helpers/utils.js.map +1 -1
  60. package/dist/locales/de.json +1 -0
  61. package/dist/locales/en.json +1 -0
  62. package/dist/locales/es.json +1 -0
  63. package/dist/locales/fr.json +1 -0
  64. package/dist/locales/it.json +1 -0
  65. package/dist/styles.css +1 -1
  66. package/dist/types/integration.d.ts +3 -0
  67. package/dist/types/integration.js +16 -0
  68. package/dist/types/integration.js.map +1 -0
  69. package/dist/version.d.ts +1 -1
  70. package/dist/version.js +1 -1
  71. package/esm/components/AgeVerificationModal/AgeVerificationModal.js +5 -2
  72. package/esm/components/AgeVerificationModal/AgeVerificationModal.js.map +1 -1
  73. package/esm/components/Avatar/Avatar.js +5 -10
  74. package/esm/components/Avatar/Avatar.js.map +1 -1
  75. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js +9 -2
  76. package/esm/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.js.map +1 -1
  77. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js +2 -2
  78. package/esm/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.js.map +1 -1
  79. package/esm/components/Avatar/AvatarView/AvatarComponent/constants.d.ts +2 -0
  80. package/esm/components/Avatar/AvatarView/AvatarComponent/constants.js +13 -4
  81. package/esm/components/Avatar/AvatarView/AvatarComponent/constants.js.map +1 -1
  82. package/esm/components/ChatBubble/ChatBubble.js +19 -15
  83. package/esm/components/ChatBubble/ChatBubble.js.map +1 -1
  84. package/esm/components/CompletionProviderStatus/CompletionProviderStatus.js +6 -3
  85. package/esm/components/CompletionProviderStatus/CompletionProviderStatus.js.map +1 -1
  86. package/esm/components/DateSelector/DateSelector.d.ts +2 -1
  87. package/esm/components/DateSelector/DateSelector.js +17 -7
  88. package/esm/components/DateSelector/DateSelector.js.map +1 -1
  89. package/esm/components/FilePreview/FilePreview.js +24 -7
  90. package/esm/components/FilePreview/FilePreview.js.map +1 -1
  91. package/esm/components/MediaWidget/MediaItemWidget.js +16 -14
  92. package/esm/components/MediaWidget/MediaItemWidget.js.map +1 -1
  93. package/esm/components/MediaWidget/MediaItemWidget.utils.d.ts +3 -2
  94. package/esm/components/MediaWidget/MediaItemWidget.utils.js +27 -6
  95. package/esm/components/MediaWidget/MediaItemWidget.utils.js.map +1 -1
  96. package/esm/components/MediaWidget/MediaPreviewModal.js +6 -7
  97. package/esm/components/MediaWidget/MediaPreviewModal.js.map +1 -1
  98. package/esm/components/MemoriWidget/MemoriWidget.js +632 -280
  99. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  100. package/esm/components/UploadButton/UploadButton.js +21 -5
  101. package/esm/components/UploadButton/UploadButton.js.map +1 -1
  102. package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js +123 -59
  103. package/esm/components/UploadButton/UploadDocuments/UploadDocuments.js.map +1 -1
  104. package/esm/components/layouts/WebsiteAssistant/website-assistant.css +2 -1
  105. package/esm/helpers/constants.d.ts +3 -0
  106. package/esm/helpers/constants.js +26 -0
  107. package/esm/helpers/constants.js.map +1 -1
  108. package/esm/helpers/credits.d.ts +5 -2
  109. package/esm/helpers/credits.js +5 -1
  110. package/esm/helpers/credits.js.map +1 -1
  111. package/esm/helpers/message.d.ts +1 -1
  112. package/esm/helpers/message.js +2 -2
  113. package/esm/helpers/message.js.map +1 -1
  114. package/esm/helpers/nats/getNatsConfig.d.ts +9 -0
  115. package/esm/helpers/nats/getNatsConfig.js +61 -0
  116. package/esm/helpers/nats/getNatsConfig.js.map +1 -0
  117. package/esm/helpers/nats/isSessionExpiredError.d.ts +3 -0
  118. package/esm/helpers/nats/isSessionExpiredError.js +32 -0
  119. package/esm/helpers/nats/isSessionExpiredError.js.map +1 -0
  120. package/esm/helpers/nats/useNats.d.ts +12 -0
  121. package/esm/helpers/nats/useNats.js +72 -0
  122. package/esm/helpers/nats/useNats.js.map +1 -0
  123. package/esm/helpers/nats/useNatsSession.d.ts +28 -0
  124. package/esm/helpers/nats/useNatsSession.js +208 -0
  125. package/esm/helpers/nats/useNatsSession.js.map +1 -0
  126. package/esm/helpers/utils.d.ts +21 -0
  127. package/esm/helpers/utils.js +68 -3
  128. package/esm/helpers/utils.js.map +1 -1
  129. package/esm/locales/de.json +1 -0
  130. package/esm/locales/en.json +1 -0
  131. package/esm/locales/es.json +1 -0
  132. package/esm/locales/fr.json +1 -0
  133. package/esm/locales/it.json +1 -0
  134. package/esm/styles.css +1 -1
  135. package/esm/types/integration.d.ts +3 -0
  136. package/esm/types/integration.js +11 -0
  137. package/esm/types/integration.js.map +1 -0
  138. package/esm/version.d.ts +1 -1
  139. package/esm/version.js +1 -1
  140. package/package.json +5 -3
  141. package/src/components/AgeVerificationModal/AgeVerificationModal.tsx +6 -1
  142. package/src/components/Avatar/Avatar.stories.tsx +45 -28
  143. package/src/components/Avatar/Avatar.test.tsx +54 -0
  144. package/src/components/Avatar/Avatar.tsx +8 -12
  145. package/src/components/Avatar/AvatarView/AvatarComponent/components/FullbodyAvatar/fullbodyAvatar.tsx +12 -5
  146. package/src/components/Avatar/AvatarView/AvatarComponent/components/controllers/MorphTargetController.ts +2 -1
  147. package/src/components/Avatar/AvatarView/AvatarComponent/constants.ts +16 -4
  148. package/src/components/Avatar/__snapshots__/Avatar.test.tsx.snap +164 -0
  149. package/src/components/ChatBubble/ChatBubble.tsx +37 -12
  150. package/src/components/CompletionProviderStatus/CompletionProviderStatus.tsx +8 -4
  151. package/src/components/DateSelector/DateSelector.stories.tsx +11 -2
  152. package/src/components/DateSelector/DateSelector.tsx +28 -9
  153. package/src/components/FilePreview/FilePreview.tsx +28 -7
  154. package/src/components/MediaWidget/MediaItemWidget.tsx +26 -13
  155. package/src/components/MediaWidget/MediaItemWidget.utils.test.ts +45 -2
  156. package/src/components/MediaWidget/MediaItemWidget.utils.ts +39 -6
  157. package/src/components/MediaWidget/MediaPreviewModal.tsx +9 -7
  158. package/src/components/MemoriWidget/MemoriWidget.tsx +785 -331
  159. package/src/components/StartPanel/StartPanel.stories.tsx +22 -0
  160. package/src/components/StartPanel/StartPanel.test.tsx +64 -1
  161. package/src/components/StartPanel/__snapshots__/StartPanel.test.tsx.snap +261 -0
  162. package/src/components/UploadButton/UploadButton.tsx +30 -11
  163. package/src/components/UploadButton/UploadDocuments/UploadDocuments.tsx +148 -61
  164. package/src/components/UploadButton/__snapshots__/UploadButton.test.tsx.snap +2 -2
  165. package/src/components/layouts/WebsiteAssistant/website-assistant.css +2 -1
  166. package/src/components/layouts/layouts.stories.tsx +131 -12
  167. package/src/helpers/constants.ts +32 -1
  168. package/src/helpers/credits.ts +16 -1
  169. package/src/helpers/message.test.ts +28 -1
  170. package/src/helpers/message.ts +4 -4
  171. package/src/helpers/nats/getNatsConfig.ts +145 -0
  172. package/src/helpers/nats/isSessionExpiredError.test.ts +38 -0
  173. package/src/helpers/nats/isSessionExpiredError.ts +51 -0
  174. package/src/helpers/nats/useNats.ts +124 -0
  175. package/src/helpers/nats/useNatsSession.ts +394 -0
  176. package/src/helpers/utils.test.ts +162 -0
  177. package/src/helpers/utils.ts +108 -3
  178. package/src/index.stories.tsx +32 -0
  179. package/src/locales/de.json +1 -0
  180. package/src/locales/en.json +1 -0
  181. package/src/locales/es.json +1 -0
  182. package/src/locales/fr.json +1 -0
  183. package/src/locales/it.json +1 -0
  184. package/src/types/integration.test.ts +32 -0
  185. package/src/types/integration.ts +31 -0
  186. package/src/version.ts +1 -1
@@ -5,6 +5,8 @@ import { FileText as DocumentIcon } from 'lucide-react';
5
5
  import { Modal } from '@memori.ai/ui';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import memoriApiClient from '@memori.ai/memori-api-client';
8
+ import { officeNativeExtensions } from '../../../helpers/constants';
9
+ import { isOfficeNativeFilename } from '../../../helpers/utils';
8
10
  // Types
9
11
  type PreviewFile = {
10
12
  name: string;
@@ -165,64 +167,101 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
165
167
  }
166
168
  };
167
169
 
168
- const extractTextFromXLSX = async (file: File): Promise<string> => {
169
- try {
170
- if (!window.XLSX) {
171
- await new Promise((resolve, reject) => {
172
- const script = document.createElement('script');
173
- script.src = XLSX_URL;
174
- script.onload = resolve;
175
- script.onerror = reject;
176
- document.head.appendChild(script);
177
- });
178
- }
170
+ const loadXLSXLibrary = async (): Promise<void> => {
171
+ if (window.XLSX) return;
179
172
 
180
- const arrayBuffer = await file.arrayBuffer();
181
- const workbook = window.XLSX.read(arrayBuffer, {
182
- type: 'array',
183
- cellFormula: true,
184
- cellNF: true,
185
- cellText: true,
186
- cellDates: true,
173
+ await new Promise((resolve, reject) => {
174
+ const script = document.createElement('script');
175
+ script.src = XLSX_URL;
176
+ script.onload = resolve;
177
+ script.onerror = reject;
178
+ document.head.appendChild(script);
179
+ });
180
+ };
181
+
182
+ const readXLSXWorkbook = async (file: File) => {
183
+ await loadXLSXLibrary();
184
+
185
+ const arrayBuffer = await file.arrayBuffer();
186
+ return window.XLSX.read(arrayBuffer, {
187
+ type: 'array',
188
+ cellFormula: true,
189
+ cellNF: true,
190
+ cellText: true,
191
+ cellDates: true,
192
+ });
193
+ };
194
+
195
+ const formatXLSXWorkbookForDisplay = (workbook: {
196
+ SheetNames: string[];
197
+ Sheets: Record<string, unknown>;
198
+ }): string => {
199
+ let text = '';
200
+
201
+ for (const sheetName of workbook.SheetNames) {
202
+ const worksheet = workbook.Sheets[sheetName];
203
+ const data = window.XLSX.utils.sheet_to_json(worksheet, {
204
+ header: 1,
205
+ raw: false,
187
206
  });
188
207
 
189
- let text = '';
190
- for (const sheetName of workbook.SheetNames) {
191
- const worksheet = workbook.Sheets[sheetName];
192
- const data = window.XLSX.utils.sheet_to_json(worksheet, {
193
- header: 1,
194
- raw: false,
208
+ const colWidths = data.reduce((widths: number[], row: any[]) => {
209
+ row.forEach((cell, i) => {
210
+ const cellWidth = (cell || '').toString().length;
211
+ widths[i] = Math.max(widths[i] || 0, cellWidth);
195
212
  });
213
+ return widths;
214
+ }, []);
215
+
216
+ const formattedText = data.map((row: any[]) => {
217
+ return row
218
+ .map((cell, i) => {
219
+ const cellStr = (cell || '').toString();
220
+ return cellStr.padEnd(colWidths[i] + 2);
221
+ })
222
+ .join('|')
223
+ .trim();
224
+ });
196
225
 
197
- const colWidths = data.reduce((widths: number[], row: any[]) => {
198
- row.forEach((cell, i) => {
199
- const cellWidth = (cell || '').toString().length;
200
- widths[i] = Math.max(widths[i] || 0, cellWidth);
201
- });
202
- return widths;
203
- }, []);
204
-
205
- const formattedText = data.map((row: any[]) => {
206
- return row
207
- .map((cell, i) => {
208
- const cellStr = (cell || '').toString();
209
- return cellStr.padEnd(colWidths[i] + 2);
210
- })
211
- .join('|')
212
- .trim();
213
- });
226
+ if (formattedText.length > 0) {
227
+ const separator = colWidths
228
+ .map((w: number) => '-'.repeat(w + 2))
229
+ .join('+');
230
+ formattedText.splice(1, 0, separator);
231
+ }
214
232
 
215
- if (formattedText.length > 0) {
216
- const separator = colWidths
217
- .map((w: number) => '-'.repeat(w + 2))
218
- .join('+');
219
- formattedText.splice(1, 0, separator);
220
- }
233
+ text += `Sheet: ${sheetName}\n${formattedText.join('\n')}\n\n`;
234
+ }
235
+
236
+ return text;
237
+ };
221
238
 
222
- text += `Sheet: ${sheetName}\n${formattedText.join('\n')}\n\n`;
239
+ const formatXLSXWorkbookAsCsv = (workbook: {
240
+ SheetNames: string[];
241
+ Sheets: Record<string, unknown>;
242
+ }): string => {
243
+ let text = '';
244
+
245
+ for (const sheetName of workbook.SheetNames) {
246
+ const worksheet = workbook.Sheets[sheetName];
247
+ const csv = window.XLSX.utils.sheet_to_csv(worksheet, { raw: false });
248
+ if (csv.trim()) {
249
+ text += `Sheet: ${sheetName}\n${csv}\n\n`;
223
250
  }
251
+ }
224
252
 
225
- return text;
253
+ return text;
254
+ };
255
+
256
+ const extractTextFromXLSX = async (
257
+ file: File
258
+ ): Promise<{ display: string; asset: string }> => {
259
+ try {
260
+ const workbook = await readXLSXWorkbook(file);
261
+ return {
262
+ display: formatXLSXWorkbookForDisplay(workbook),
263
+ asset: formatXLSXWorkbookAsCsv(workbook),
264
+ };
226
265
  } catch (error) {
227
266
  console.error('XLSX extraction failed:', error);
228
267
  throw new Error(
@@ -235,21 +274,31 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
235
274
 
236
275
  const processDocumentFile = async (
237
276
  file: File
238
- ): Promise<{ text: string | null }> => {
239
- const fileExt = file.name.split('.').pop()?.toLowerCase() || '';
277
+ ): Promise<{
278
+ text: string | null;
279
+ textForAsset?: string;
280
+ uploadAsOriginal?: boolean;
281
+ }> => {
282
+ if (isOfficeNativeFilename(file.name)) {
283
+ return { text: null, uploadAsOriginal: true };
284
+ }
240
285
 
286
+ const ext = file.name.split('.').pop()?.toLowerCase() || '';
241
287
  try {
242
288
  let text: string | null = null;
289
+ let textForAsset: string | undefined;
243
290
 
244
- if (fileExt === 'pdf') {
291
+ if (ext === 'pdf') {
245
292
  text = await extractTextFromPDF(file);
246
- } else if (['txt', 'md', 'json', 'csv', 'html'].includes(fileExt)) {
293
+ } else if (['txt', 'md', 'json', 'csv', 'html'].includes(ext)) {
247
294
  text = await file.text();
248
- } else if (fileExt === 'xlsx') {
249
- text = await extractTextFromXLSX(file);
295
+ } else if (ext === 'xlsx') {
296
+ const extracted = await extractTextFromXLSX(file);
297
+ text = extracted.display;
298
+ textForAsset = extracted.asset;
250
299
  }
251
300
 
252
- return { text };
301
+ return { text, textForAsset };
253
302
  } catch (error) {
254
303
  console.error('Document processing failed:', error);
255
304
  throw new Error(
@@ -295,7 +344,12 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
295
344
  throw new Error(response.resultMessage || 'Upload failed');
296
345
  }
297
346
 
298
- return response.asset?.assetURL;
347
+ const assetURL = response.asset?.assetURL;
348
+ if (!assetURL) {
349
+ throw new Error('Upload failed: missing asset URL');
350
+ }
351
+
352
+ return assetURL;
299
353
  };
300
354
 
301
355
  const handleDocumentUpload = async (
@@ -360,11 +414,42 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
360
414
  const fileId = Math.random().toString(36).substr(2, 9);
361
415
 
362
416
  try {
363
- const { text } = await processDocumentFile(file);
417
+ const { text, textForAsset, uploadAsOriginal } =
418
+ await processDocumentFile(file);
419
+
420
+ if (uploadAsOriginal) {
421
+ // Office native format: upload the original file as asset, no text extraction
422
+ let assetUrl: string | undefined;
423
+ try {
424
+ assetUrl = await uploadAssetFile(file);
425
+ } catch (uploadError) {
426
+ console.error('Office asset upload failed:', uploadError);
427
+ onDocumentError?.({
428
+ message: t('upload.officeAssetUploadFailed', {
429
+ fileName: file.name,
430
+ defaultValue: `"${file.name}" could not be uploaded and was not added.`,
431
+ }),
432
+ severity: 'error',
433
+ });
434
+ }
435
+
436
+ if (!assetUrl) {
437
+ activeCount--;
438
+ onLoadingChange?.(true, activeCount);
439
+ continue;
440
+ }
364
441
 
365
- if (text) {
442
+ processedFiles.push({
443
+ name: file.name,
444
+ id: fileId,
445
+ content: '',
446
+ mimeType: file.type,
447
+ textAssetUrl: assetUrl,
448
+ });
449
+ } else if (text) {
366
450
  const baseName = file.name.replace(/\.[^/.]+$/, '') || file.name;
367
- const textFile = new File([text], `${baseName}.txt`, {
451
+ const assetText = textForAsset ?? text;
452
+ const textFile = new File([assetText], `${baseName}.txt`, {
368
453
  type: 'text/plain',
369
454
  });
370
455
 
@@ -429,7 +514,9 @@ const UploadDocuments: React.FC<UploadDocumentsProps> = ({
429
514
  <input
430
515
  ref={documentInputRef}
431
516
  type="file"
432
- accept=".pdf,.txt,.md,.json,.xlsx,.csv,.html"
517
+ accept={`.pdf,.txt,.md,.json,.xlsx,.csv,.html,${officeNativeExtensions.join(
518
+ ','
519
+ )}`}
433
520
  multiple
434
521
  className="memori--upload-file-input"
435
522
  onChange={handleDocumentUpload}
@@ -6,7 +6,7 @@ exports[`renders UploadButton unchanged 1`] = `
6
6
  class="memori--unified-upload-wrapper"
7
7
  >
8
8
  <input
9
- accept=".jpg,.jpeg,.png,.pdf,.txt,.json,.xlsx,.csv,.md,.html"
9
+ accept=".jpg,.jpeg,.png,.pdf,.txt,.json,.xlsx,.csv,.md,.html,.doc,.docx,.dotx,.xltx,.potx"
10
10
  class="memori--upload-file-input"
11
11
  multiple=""
12
12
  style="display: none;"
@@ -60,7 +60,7 @@ exports[`renders UploadButton unchanged 1`] = `
60
60
  class="memori--document-upload-wrapper"
61
61
  >
62
62
  <input
63
- accept=".pdf,.txt,.md,.json,.xlsx,.csv,.html"
63
+ accept=".pdf,.txt,.md,.json,.xlsx,.csv,.html,.doc,.docx,.dotx,.xltx,.potx"
64
64
  class="memori--upload-file-input"
65
65
  multiple=""
66
66
  type="file"
@@ -418,7 +418,8 @@
418
418
  transform: none !important;
419
419
  }
420
420
 
421
- .memori-layout-website_assistant.memori--avatar-readyplayerme-full .memori--avatar-wrapper > div {
421
+ .memori-layout-website_assistant.memori--avatar-readyplayerme-full .memori--avatar-wrapper > div,
422
+ .memori-layout-website_assistant.memori--avatar-avatar-configurator .memori--avatar-wrapper > div {
422
423
  transform: scale(1.7) translate(0px, 10vh);
423
424
  }
424
425
 
@@ -1,7 +1,9 @@
1
1
  import React from 'react';
2
2
  import { Meta, Story } from '@storybook/react';
3
+ import { Spin } from '@memori.ai/ui';
3
4
  import { memori, tenant, integration } from '../../mocks/data';
4
5
  import Memori, { Props } from '../../index';
6
+ import { LayoutProps } from '../MemoriWidget/MemoriWidget';
5
7
  import I18nWrapper from '../../I18nWrapper';
6
8
  import { VisemeProvider } from '../../context/visemeContext';
7
9
  import { ArtifactProvider } from '../MemoriArtifactSystem/context/ArtifactContext';
@@ -30,25 +32,18 @@ const Template: Story<Props> = args => (
30
32
 
31
33
  const DefaultLayout = Template.bind({});
32
34
  DefaultLayout.args = {
33
- memoriName: 'Layout Storybook',
34
- ownerUserName: 'andrea.patini',
35
- memoriID: 'ae20fc5a-cc15-4db9-b7dd-2cd4a621b85e',
35
+ memoriName: 'prova-sharepoint',
36
+ ownerUserName: 'Andrea-Patini',
37
+ memoriID: '019f129c-a5a3-74d3-b0c8-b7794437bb23',
36
38
  ownerUserID: '91dbc9ba-b684-4fbe-9828-b5980af6cda9',
37
39
  tenantID: 'aisuru-staging.aclambda.online',
38
40
  engineURL: 'https://engine-staging.memori.ai/memori/v2',
39
41
  apiURL: 'https://backend-staging.memori.ai/api/v2',
42
+ baseURL: 'https://aisuru-staging.aclambda.online',
40
43
  layout: 'FULLPAGE',
41
44
  uiLang: 'IT',
42
45
  spokenLang: 'IT',
43
- integrationID: '0b1256c1-530c-4e67-aef8-36667c8887bb',
44
- autoStart: false,
45
- sessionID: '' as string | undefined,
46
- showUpload: true,
47
- showReasoning: false,
48
- showLogin: true,
49
- multilingual: true,
50
- avatar3dHidden: true,
51
- showMessageConsumption: true,
46
+ integrationID: '019f1896-730d-7e19-9a88-6413f485247b',
52
47
  };
53
48
 
54
49
  export const Default = Template.bind({});
@@ -82,6 +77,130 @@ ChatOnly.args = {
82
77
  layout: 'CHAT',
83
78
  };
84
79
 
80
+ const CustomLayout: React.FC<LayoutProps> = ({
81
+ Header,
82
+ headerProps,
83
+ Avatar,
84
+ avatarProps,
85
+ Chat,
86
+ chatProps,
87
+ StartPanel,
88
+ startPanelProps,
89
+ integrationStyle,
90
+ integrationBackground,
91
+ sessionId,
92
+ hasUserActivatedSpeak,
93
+ loading = false,
94
+ poweredBy,
95
+ }) => (
96
+ <>
97
+ {integrationStyle}
98
+ {integrationBackground}
99
+
100
+ <Spin spinning={loading} className="memori-mycustom-layout">
101
+ {poweredBy}
102
+
103
+ <div className="memori-mycustom-layout--controls">
104
+ {sessionId && hasUserActivatedSpeak && Chat && chatProps ? (
105
+ <Chat {...chatProps} />
106
+ ) : startPanelProps ? (
107
+ <StartPanel {...startPanelProps} />
108
+ ) : null}
109
+ </div>
110
+ </Spin>
111
+ </>
112
+ );
113
+
114
+ export const Custom = Template.bind({});
115
+ Custom.args = {
116
+ ...DefaultLayout.args,
117
+ uiLang: 'it',
118
+ showShare: true,
119
+ showSettings: true,
120
+ memori: memori,
121
+ tenant,
122
+ layout: 'FULLPAGE',
123
+ customLayout: CustomLayout,
124
+ };
125
+
126
+ export const WebsiteAssistant2 = Template.bind({});
127
+ WebsiteAssistant2.args = {
128
+ memoriName: 'Marconi Express',
129
+ ownerUserName: 'exmita',
130
+ memoriID: '1755979f-28f1-45d1-8f41-bb34e1f7756b',
131
+ ownerUserID: '3194fcd3-040b-4d5e-a925-691911fb1177',
132
+ tenantID: 'exmachina.aclambda.online',
133
+ engineURL: 'https://engine.memori.ai',
134
+ apiURL: 'https://backend.memori.ai',
135
+ baseURL: 'https://exmachina.aclambda.online',
136
+ uiLang: 'IT',
137
+ avatar3dHidden: true,
138
+ spokenLang: 'IT',
139
+ layout: 'WEBSITE_ASSISTANT',
140
+ };
141
+
142
+ export const WebsiteAssistant3 = Template.bind({});
143
+ WebsiteAssistant3.args = {
144
+ memoriName: 'Layout Storybook',
145
+ ownerUserName: 'Andrea-Patini',
146
+ memoriID: 'ae20fc5a-cc15-4db9-b7dd-2cd4a621b85e',
147
+ ownerUserID: '91dbc9ba-b684-4fbe-9828-b5980af6cda9',
148
+ tenantID: 'aisuru-staging.aclambda.online',
149
+ engineURL: 'https://engine-staging.memori.ai/memori/v2',
150
+ apiURL: 'https://backend-staging.memori.ai/api/v2',
151
+ baseURL: 'http://localhost:3000',
152
+ layout: 'WEBSITE_ASSISTANT',
153
+ avatar3dHidden: true,
154
+ uiLang: 'IT',
155
+ spokenLang: 'IT',
156
+ showOnlyLastMessages: true,
157
+ integrationID: '716f4728-919c-4015-aae1-88998a081c6f',
158
+ };
159
+
160
+ export const WebsiteAssistant = Template.bind({});
161
+ WebsiteAssistant.args = {
162
+ uiLang: 'EN',
163
+ showShare: false,
164
+ showSettings: true,
165
+ memori: memori,
166
+ tenant,
167
+ layout: 'WEBSITE_ASSISTANT',
168
+ memoriName: 'Memori',
169
+ memoriID: '25ced51c-3520-41af-8bbe-222d861b8e32',
170
+ ownerUserName: 'memoridev',
171
+ tenantID: 'www.aisuru.com',
172
+ apiURL: 'https://backend.memori.ai',
173
+ baseURL: 'https://www.aisuru.com',
174
+ spokenLang: 'EN',
175
+ context: {
176
+ SOURCE: 'WEBSITE',
177
+ PATH: '/en/',
178
+ },
179
+ multilingual: true,
180
+ integrationID: 'cb3c4776-7f0b-4f97-a773-c32a5d7a3bf1',
181
+ integration: {
182
+ ...integration,
183
+ customData: JSON.stringify({
184
+ textColor: '#2a2a2a',
185
+ buttonBgColor: '#653165',
186
+ buttonTextColor: '#ffffff',
187
+ blurBackground: true,
188
+ innerBgColor: 'light',
189
+ innerBgAlpha: 0.8,
190
+ multilanguage: true,
191
+ avatar: 'readyplayerme',
192
+ avatarURL:
193
+ 'https://assets.memori.ai/api/v2/asset/b791f77c-1a94-4272-829e-eca82fcc62b7.glb',
194
+ }),
195
+ },
196
+ };
197
+
198
+ export const WebsiteAssistantWithout3DAvatar = Template.bind({});
199
+ WebsiteAssistantWithout3DAvatar.args = {
200
+ ...WebsiteAssistant.args,
201
+ avatar3dHidden: true,
202
+ };
203
+
85
204
  export const HiddenChat = Template.bind({});
86
205
  HiddenChat.args = {
87
206
  ...DefaultLayout.args,
@@ -36,7 +36,7 @@ export const getGroupedChatLanguages = () => {
36
36
  popularLanguageCodes.includes(lang.value)
37
37
  );
38
38
  const all = chatLanguages.filter(lang => !popularLanguageCodes.includes(lang.value));
39
- return {
39
+ return {
40
40
  popular,
41
41
  all,
42
42
  };
@@ -44,6 +44,15 @@ export const getGroupedChatLanguages = () => {
44
44
 
45
45
  export const uiLanguages = ['en', 'it', 'fr', 'es', 'de'];
46
46
 
47
+ /** Extensions uploaded as original Office binaries (no text extraction) */
48
+ export const officeNativeExtensions = [
49
+ '.doc',
50
+ '.docx',
51
+ '.dotx',
52
+ '.xltx',
53
+ '.potx',
54
+ ] as const;
55
+
47
56
  export const allowedMediaTypes = [
48
57
  'image/jpeg',
49
58
  'image/png',
@@ -51,6 +60,7 @@ export const allowedMediaTypes = [
51
60
  'image/gif',
52
61
  'application/msword',
53
62
  'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
63
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
54
64
  'application/vnd.ms-excel',
55
65
  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
56
66
  'application/pdf',
@@ -63,6 +73,27 @@ export const allowedMediaTypes = [
63
73
  'model/gltf-binary',
64
74
  ];
65
75
 
76
+ /** Short badge labels for Office document cards */
77
+ export const officeMimeShortLabels: Record<string, string> = {
78
+ 'application/msword': 'Word',
79
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'Word',
80
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.template': 'Word',
81
+ 'application/vnd.ms-excel': 'Excel',
82
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'Excel',
83
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.template': 'Excel',
84
+ 'application/vnd.openxmlformats-officedocument.presentationml.template': 'PPT',
85
+ };
86
+
87
+ export const officeExtensionShortLabels: Record<string, string> = {
88
+ DOC: 'Word',
89
+ DOCX: 'Word',
90
+ DOTX: 'Word',
91
+ XLS: 'Excel',
92
+ XLSX: 'Excel',
93
+ XLTX: 'Excel',
94
+ POTX: 'PPT',
95
+ };
96
+
66
97
  export const anonTag = '👤';
67
98
 
68
99
  export const prismSyntaxLangs = [
@@ -1,23 +1,37 @@
1
1
  // POST http://localhost:3000/api/verify-tokens operation=session_creation userID=585ec0ff-e805-495e-b8fc-5b0b8dd288ff tenant=aisuru-staging-tokenized.aclambda.online
2
+ export type CreditsOperation =
3
+ | 'twin_creation'
4
+ | 'session_creation'
5
+ | 'import_document'
6
+ // accepted by the API and normalized to session_creation
7
+ | 'dt_session_creation';
8
+
2
9
  export const getCredits = async ({
3
10
  operation = 'session_creation',
4
11
  baseUrl,
5
12
  userID,
6
13
  userName,
7
14
  tenant,
15
+ characters,
8
16
  }: {
9
- operation?: string;
17
+ operation?: CreditsOperation;
10
18
  baseUrl: string;
11
19
  userID?: string | null;
12
20
  userName?: string | null;
13
21
  tenant: string;
22
+ characters?: number;
14
23
  }): Promise<{
15
24
  enough: boolean;
16
25
  required: number;
26
+ tokens?: number;
17
27
  }> => {
18
28
  if (!userID && !userName) {
19
29
  throw new Error('Either userID or userName must be provided');
20
30
  }
31
+ if (operation === 'import_document' && characters == null) {
32
+ throw new Error('characters must be provided for import_document');
33
+ }
34
+
21
35
  const resp = await fetch(`${baseUrl}/api/verify-tokens`, {
22
36
  method: 'POST',
23
37
  headers: {
@@ -28,6 +42,7 @@ export const getCredits = async ({
28
42
  userID,
29
43
  userName,
30
44
  tenant,
45
+ ...(operation === 'import_document' ? { characters } : {}),
31
46
  }),
32
47
  });
33
48
 
@@ -1,4 +1,24 @@
1
- import { renderMsg, stripAttachmentTags, stripAllInternalTags } from './message';
1
+ import {
2
+ renderMsg,
3
+ sanitizeMsg,
4
+ stripAttachmentTags,
5
+ stripAllInternalTags,
6
+ } from './message';
7
+
8
+ describe('sanitizeMsg', () => {
9
+ it('strips onerror from partner XSS img payload', () => {
10
+ const payload =
11
+ '<img src="immagine-inesistente.jpg" onerror="alert(\'XSS Eseguito!\')">';
12
+ const result = sanitizeMsg(payload);
13
+ expect(result).toBe('<img src="immagine-inesistente.jpg">');
14
+ expect(result).not.toMatch(/onerror/i);
15
+ });
16
+
17
+ it('strips svg onload handlers', () => {
18
+ const result = sanitizeMsg('<svg onload="alert(1)"></svg>');
19
+ expect(result).not.toMatch(/onload/i);
20
+ });
21
+ });
2
22
 
3
23
  describe('renderMsg', () => {
4
24
  it('should render message with reasoning and output tag adjacent correctly', () => {
@@ -11,6 +31,13 @@ describe('renderMsg', () => {
11
31
  '<details class="memori-think"><summary>Reasoning...</summary>L\'utente vuole che io elenchi i parametri dell\'ALBERO ESISTENTE.</details>\n\n<output class="rails-query" style="display:none">\nproject = Project.find("68d99b05783713e16737d32b")\n</output>\n\n<p>Recupero tutti i 7 parametri dell\'ALBERO ESISTENTE...</p>'
12
32
  );
13
33
  });
34
+
35
+ it('strips onerror from XSS img payload in rendered output', () => {
36
+ const payload =
37
+ '<img src="immagine-inesistente.jpg" onerror="alert(\'XSS Eseguito!\')">';
38
+ const result = renderMsg(payload, false, 'Reasoning...', false);
39
+ expect(result.text).not.toMatch(/onerror/i);
40
+ });
14
41
  });
15
42
 
16
43
  describe('stripAttachmentTags', () => {
@@ -85,6 +85,9 @@ export const truncateMessage = (message: string) => {
85
85
  return truncatedMessage;
86
86
  };
87
87
 
88
+ export const sanitizeMsg = (msg: string) =>
89
+ DOMPurify.sanitize(msg, { ADD_ATTR: ['target'] });
90
+
88
91
  export const renderMsg = (
89
92
  text: string,
90
93
  useMathFormatting = false,
@@ -196,9 +199,6 @@ export const renderMsg = (
196
199
  return { text: finalText };
197
200
  } catch (e) {
198
201
  console.error('Error rendering message:', e);
199
- return { text };
202
+ return { text: sanitizeMsg(text) };
200
203
  }
201
204
  };
202
-
203
- export const sanitizeMsg = (msg: string) =>
204
- DOMPurify.sanitize(msg, { ADD_ATTR: ['target'] });