@memori.ai/memori-react 7.10.0 → 7.11.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.
Files changed (67) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +1 -0
  3. package/dist/components/AgeVerificationModal/AgeVerificationModal.css +1 -1
  4. package/dist/components/Chat/Chat.d.ts +1 -0
  5. package/dist/components/Chat/Chat.js +2 -2
  6. package/dist/components/Chat/Chat.js.map +1 -1
  7. package/dist/components/ChatBubble/ChatBubble.d.ts +1 -0
  8. package/dist/components/ChatBubble/ChatBubble.js +65 -57
  9. package/dist/components/ChatBubble/ChatBubble.js.map +1 -1
  10. package/dist/components/MemoriWidget/MemoriWidget.d.ts +2 -1
  11. package/dist/components/MemoriWidget/MemoriWidget.js +62 -12
  12. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  13. package/dist/components/StartPanel/StartPanel.d.ts +1 -0
  14. package/dist/components/StartPanel/StartPanel.js +6 -2
  15. package/dist/components/StartPanel/StartPanel.js.map +1 -1
  16. package/dist/components/layouts/chat.css +0 -1
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.js +3 -2
  19. package/dist/index.js.map +1 -1
  20. package/dist/locales/de.json +3 -0
  21. package/dist/locales/en.json +4 -0
  22. package/dist/locales/es.json +3 -0
  23. package/dist/locales/fr.json +3 -0
  24. package/dist/locales/it.json +4 -0
  25. package/esm/components/AgeVerificationModal/AgeVerificationModal.css +1 -1
  26. package/esm/components/Chat/Chat.d.ts +1 -0
  27. package/esm/components/Chat/Chat.js +2 -2
  28. package/esm/components/Chat/Chat.js.map +1 -1
  29. package/esm/components/ChatBubble/ChatBubble.d.ts +1 -0
  30. package/esm/components/ChatBubble/ChatBubble.js +65 -57
  31. package/esm/components/ChatBubble/ChatBubble.js.map +1 -1
  32. package/esm/components/MemoriWidget/MemoriWidget.d.ts +2 -1
  33. package/esm/components/MemoriWidget/MemoriWidget.js +63 -13
  34. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  35. package/esm/components/StartPanel/StartPanel.d.ts +1 -0
  36. package/esm/components/StartPanel/StartPanel.js +6 -2
  37. package/esm/components/StartPanel/StartPanel.js.map +1 -1
  38. package/esm/components/layouts/chat.css +0 -1
  39. package/esm/index.d.ts +1 -0
  40. package/esm/index.js +3 -2
  41. package/esm/index.js.map +1 -1
  42. package/esm/locales/de.json +3 -0
  43. package/esm/locales/en.json +4 -0
  44. package/esm/locales/es.json +3 -0
  45. package/esm/locales/fr.json +3 -0
  46. package/esm/locales/it.json +4 -0
  47. package/package.json +1 -1
  48. package/src/components/AgeVerificationModal/AgeVerificationModal.css +1 -1
  49. package/src/components/Chat/Chat.tsx +4 -1
  50. package/src/components/ChatBubble/ChatBubble.stories.tsx +33 -0
  51. package/src/components/ChatBubble/ChatBubble.test.tsx +22 -0
  52. package/src/components/ChatBubble/ChatBubble.tsx +110 -94
  53. package/src/components/ChatBubble/__snapshots__/ChatBubble.test.tsx.snap +229 -0
  54. package/src/components/MemoriWidget/MemoriWidget.stories.tsx +4 -1
  55. package/src/components/MemoriWidget/MemoriWidget.tsx +138 -22
  56. package/src/components/StartPanel/StartPanel.stories.tsx +18 -0
  57. package/src/components/StartPanel/StartPanel.test.tsx +20 -0
  58. package/src/components/StartPanel/StartPanel.tsx +9 -1
  59. package/src/components/StartPanel/__snapshots__/StartPanel.test.tsx.snap +101 -0
  60. package/src/components/layouts/chat.css +0 -1
  61. package/src/index.stories.tsx +6 -0
  62. package/src/index.tsx +4 -0
  63. package/src/locales/de.json +3 -0
  64. package/src/locales/en.json +4 -0
  65. package/src/locales/es.json +3 -0
  66. package/src/locales/fr.json +3 -0
  67. package/src/locales/it.json +4 -0
@@ -55,6 +55,9 @@
55
55
  "birthDateHelper": "Nous demandons votre date de naissance uniquement pour activer ou désactiver les fonctionnalités qui ont des restrictions d'âge",
56
56
  "underage": "Vous devez être au moins {{age}} ans pour s'inscrire.",
57
57
  "underageTwinSession": "Vous devez être au moins {{age}} ans pour interagir avec ce Twin.",
58
+ "errorFetchingSession": "Erreur lors du chargement de la session",
59
+ "errorGettingReferralURL": "Erreur lors du chargement du référent",
60
+ "errorReopeningSession": "Erreur lors de la re-ouverture de la session",
58
61
  "ageVerification": "Vérification de l'âge",
59
62
  "ageVerificationText": "Pour interagir avec ce Twin, vous devez être au minimum {{minAge}} ans.",
60
63
  "nsfw": "NSFW : Ce jumeau contient du contenu pour adultes",
@@ -55,6 +55,9 @@
55
55
  "birthDateHelper": "Ti chiediamo la data di nascita esclusivamente per abilitare o disabilitare le funzionalità che hanno restrizioni in base all'età",
56
56
  "underage": "Devi avere almeno {{age}} anni per registrarti.",
57
57
  "underageTwinSession": "Devi avere almeno {{age}} anni per interagire con questo Twin.",
58
+ "errorFetchingSession": "Errore durante il caricamento della sessione",
59
+ "errorGettingReferralURL": "Errore durante il caricamento del riferimento",
60
+ "errorReopeningSession": "Errore durante il riapertura della sessione",
58
61
  "ageVerification": "Verifica dell'età",
59
62
  "ageVerificationText": "Per interagire con questo Twin, devi aver almeno {{minAge}} anni.",
60
63
  "nsfw": "NSFW: Questo Twin contiene contenuti per adulti",
@@ -108,6 +111,7 @@
108
111
  "pageTryMeExplanation": "Per parlare con me clicca sul pulsante INIZIAMO per cominciare",
109
112
  "instructButton": "ISTRUISCIMI",
110
113
  "tryMeButton": "INIZIAMO",
114
+ "resumeButton": "RIPRENDI",
111
115
  "chatHistory": "Cronologia chat",
112
116
  "exportChatHistory": "Esporta la cronologia chat",
113
117
  "exportChatHistoryMessage": "Vuoi esportare la cronologia chat?",
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "7.10.0",
2
+ "version": "7.11.0",
3
3
  "name": "@memori.ai/memori-react",
4
4
  "author": "Memori Srl",
5
5
  "main": "dist/index.js",
@@ -1,5 +1,5 @@
1
1
  .age-verification-modal {
2
- z-index: 1001;
2
+ z-index: 9999;
3
3
  }
4
4
 
5
5
  .age-verification-modal,
@@ -69,6 +69,7 @@ export interface Props {
69
69
  userAvatar?: MemoriProps['userAvatar'];
70
70
  user?: User;
71
71
  experts?: ExpertReference[];
72
+ useMathFormatting?: boolean;
72
73
  }
73
74
 
74
75
  const Chat: React.FC<Props> = ({
@@ -115,6 +116,7 @@ const Chat: React.FC<Props> = ({
115
116
  user,
116
117
  userAvatar,
117
118
  experts,
119
+ useMathFormatting = false,
118
120
  }) => {
119
121
  const scrollToBottom = () => {
120
122
  setTimeout(() => {
@@ -132,7 +134,7 @@ const Chat: React.FC<Props> = ({
132
134
 
133
135
  const onTextareaFocus = () => {
134
136
  stopListening();
135
- const hasTouch = hasTouchscreen();
137
+ const hasTouch = hasTouchscreen();
136
138
 
137
139
  if (hasTouch) setEnableFocusChatInput(true);
138
140
  // if the user is on mobile and had not recorded audio, add the chat-focused class to the chat wrapper
@@ -229,6 +231,7 @@ const Chat: React.FC<Props> = ({
229
231
  userAvatar={userAvatar}
230
232
  experts={experts}
231
233
  showCopyButton={showCopyButton}
234
+ useMathFormatting={useMathFormatting}
232
235
  />
233
236
  {showDates && !!message.timestamp && (
234
237
  <small
@@ -341,11 +341,38 @@ MarkdownWithSquareBrackets.args = {
341
341
  },
342
342
  };
343
343
 
344
+ export const MarkdownWithSquareBracketsAndTable = Template.bind({});
345
+ MarkdownWithSquareBracketsAndTable.args = {
346
+ memori,
347
+ apiUrl: 'https://backend.memori.ai',
348
+ tenant,
349
+ message: {
350
+ fromUser: false,
351
+ initial: false,
352
+ generatedByAI: true,
353
+ text: `<table border="1" style="border-collapse: collapse; width: 100%; table-layout: fixed; font-family: Arial, sans-serif;">
354
+ <tr style="background-color: #f0f0f0;">
355
+ <th style="text-align: left; padding: 8px; width: 200px;">Attività</th>
356
+ <th colspan="4" style="text-align: center; padding: 8px;">Dic 2024</th>
357
+ <th colspan="4" style="text-align: center; padding: 8px;">Gen 2025</th>
358
+ <th colspan="4" style="text-align: center; padding: 8px;">Feb 2025</th>
359
+ <th colspan="4" style="text-align: center; padding: 8px;">Mar 2025</th>
360
+ <th colspan="4" style="text-align: center; padding: 8px;">Apr 2025</th>
361
+ <th colspan="4" style="text-align: center; padding: 8px;">Mag 2025</th>
362
+ <th colspan="4" style="text-align: center; padding: 8px;">Giu 2025</th>
363
+ </tr>
364
+ </table>
365
+
366
+ [Vuoi che continui con l'intera tabella mantenendo lo stesso formato della precedente ma con table-layout: fixed per garantire celle di uguale dimensione?]`,
367
+ },
368
+ };
369
+
344
370
  export const ComplexMarkdownMath1 = Template.bind({});
345
371
  ComplexMarkdownMath1.args = {
346
372
  memori,
347
373
  apiUrl: 'https://backend.memori.ai',
348
374
  tenant,
375
+ useMathFormatting: true,
349
376
  message: {
350
377
  fromUser: false,
351
378
  initial: false,
@@ -359,6 +386,7 @@ ComplexMarkdownMath2.args = {
359
386
  memori,
360
387
  apiUrl: 'https://backend.memori.ai',
361
388
  tenant,
389
+ useMathFormatting: true,
362
390
  message: {
363
391
  fromUser: false,
364
392
  initial: false,
@@ -372,6 +400,7 @@ ComplexMarkdownMath3.args = {
372
400
  memori,
373
401
  apiUrl: 'https://backend.memori.ai',
374
402
  tenant,
403
+ useMathFormatting: true,
375
404
  message: {
376
405
  fromUser: false,
377
406
  initial: false,
@@ -385,6 +414,7 @@ ComplexMarkdownMath4.args = {
385
414
  memori,
386
415
  apiUrl: 'https://backend.memori.ai',
387
416
  tenant,
417
+ useMathFormatting: true,
388
418
  message: {
389
419
  fromUser: false,
390
420
  initial: false,
@@ -397,6 +427,7 @@ export const ComplexMarkdownMath5 = Template.bind({});
397
427
  ComplexMarkdownMath5.args = {
398
428
  memori,
399
429
  tenant,
430
+ useMathFormatting: true,
400
431
  apiUrl: 'https://backend.memori.ai',
401
432
  message: {
402
433
  fromUser: false,
@@ -410,6 +441,7 @@ export const ComplexMarkdownMath6 = Template.bind({});
410
441
  ComplexMarkdownMath6.args = {
411
442
  memori,
412
443
  tenant,
444
+ useMathFormatting: true,
413
445
  apiUrl: 'https://backend.memori.ai',
414
446
  message: {
415
447
  fromUser: false,
@@ -439,6 +471,7 @@ ComplexMarkdownMath7.args = {
439
471
  memori,
440
472
  tenant,
441
473
  apiUrl: 'https://backend.memori.ai',
474
+ useMathFormatting: true,
442
475
  message: {
443
476
  fromUser: false,
444
477
  initial: false,
@@ -266,12 +266,30 @@ it('renders ChatBubble with markdown table unchanged', () => {
266
266
  expect(container).toMatchSnapshot();
267
267
  });
268
268
 
269
+ it('renders ChatBubble with complex markdown and math but disabled unchanged', () => {
270
+ const { container } = render(
271
+ <ChatBubble
272
+ memori={memori}
273
+ tenant={tenant}
274
+ sessionID={sessionID}
275
+ useMathFormatting={false}
276
+ message={{
277
+ fromUser: false,
278
+ text: `Per calcolare l'ipotenusa di un triangolo rettangolo, puoi usare il Teorema di Pitagora. Il teorema afferma che in un triangolo rettangolo, il quadrato dell'ipotenusa (la lato opposto all'angolo retto) è uguale alla somma dei quadrati degli altri due lati.\n\nLa formula è:\n\n\\[ c = \\sqrt{a^2 + b^2} \\]\n\nDove:\n- $c$ è l'ipotenusa.\n- $a$ e $ b $ sono i due cateti del triangolo.\n\n### Passaggi per il Calcolo\n\n1. **Misura o identifica i cateti $ a $ e $ b $**:\n I cateti sono i due lati che formano l'angolo retto.\n\n2. **Calcola i quadrati dei cateti**:\n Eleva al quadrato entrambe le misure dei cateti: $ a^2 $ e $ b^2 $.\n\n3. **Somma i quadrati dei cateti**:\n Somma i risultati ottenuti: $ a^2 + b^2 $.\n\n4. **Calcola la radice quadrata della somma**:\n Prendi la radice quadrata della somma per trovare l'ipotenusa: $ c = \\sqrt{a^2 + b^2} $.\n\n### Esempio di Calcolo\n\nSupponiamo di avere un triangolo rettangolo con i cateti di lunghezza 3 cm e 4 cm.\n\n1. **Cateto $ a $**: 3 cm\n2. **Cateto $ b $**: 4 cm\n\nUsiamo la formula:\n\n\\[ c = \\sqrt{a^2 + b^2} \\]\n\n\\[ c = \\sqrt{(3 \\, \\text{cm})^2 + (4 \\, \\text{cm})^2} \\]\n\n\\[ c = \\sqrt{9 \\, \\text{cm}^2 + 16 \\, \\text{cm}^2} \\]\n\n\\[ c = \\sqrt{25 \\, \\text{cm}^2} \\]\n\n\\[ c = 5 \\, \\text{cm} \\]\n\nQuindi, l'ipotenusa del triangolo è di 5 cm.`,
279
+ initial: false,
280
+ }}
281
+ />
282
+ );
283
+ expect(container).toMatchSnapshot();
284
+ });
285
+
269
286
  it('renders ChatBubble with complex markdown and math 1 unchanged', () => {
270
287
  const { container } = render(
271
288
  <ChatBubble
272
289
  memori={memori}
273
290
  tenant={tenant}
274
291
  sessionID={sessionID}
292
+ useMathFormatting
275
293
  message={{
276
294
  fromUser: false,
277
295
  text: `Per calcolare l'ipotenusa di un triangolo rettangolo, puoi usare il Teorema di Pitagora. Il teorema afferma che in un triangolo rettangolo, il quadrato dell'ipotenusa (la lato opposto all'angolo retto) è uguale alla somma dei quadrati degli altri due lati.\n\nLa formula è:\n\n\\[ c = \\sqrt{a^2 + b^2} \\]\n\nDove:\n- $c$ è l'ipotenusa.\n- $a$ e $ b $ sono i due cateti del triangolo.\n\n### Passaggi per il Calcolo\n\n1. **Misura o identifica i cateti $ a $ e $ b $**:\n I cateti sono i due lati che formano l'angolo retto.\n\n2. **Calcola i quadrati dei cateti**:\n Eleva al quadrato entrambe le misure dei cateti: $ a^2 $ e $ b^2 $.\n\n3. **Somma i quadrati dei cateti**:\n Somma i risultati ottenuti: $ a^2 + b^2 $.\n\n4. **Calcola la radice quadrata della somma**:\n Prendi la radice quadrata della somma per trovare l'ipotenusa: $ c = \\sqrt{a^2 + b^2} $.\n\n### Esempio di Calcolo\n\nSupponiamo di avere un triangolo rettangolo con i cateti di lunghezza 3 cm e 4 cm.\n\n1. **Cateto $ a $**: 3 cm\n2. **Cateto $ b $**: 4 cm\n\nUsiamo la formula:\n\n\\[ c = \\sqrt{a^2 + b^2} \\]\n\n\\[ c = \\sqrt{(3 \\, \\text{cm})^2 + (4 \\, \\text{cm})^2} \\]\n\n\\[ c = \\sqrt{9 \\, \\text{cm}^2 + 16 \\, \\text{cm}^2} \\]\n\n\\[ c = \\sqrt{25 \\, \\text{cm}^2} \\]\n\n\\[ c = 5 \\, \\text{cm} \\]\n\nQuindi, l'ipotenusa del triangolo è di 5 cm.`,
@@ -288,6 +306,7 @@ it('renders ChatBubble with complex markdown and math 2 unchanged', () => {
288
306
  memori={memori}
289
307
  tenant={tenant}
290
308
  sessionID={sessionID}
309
+ useMathFormatting
291
310
  message={{
292
311
  fromUser: false,
293
312
  text: "Per calcolare le resistenze dei materiali per le verifiche rispetto ad azioni antropiche e ambientali secondo la Specifica Tecnica ST-VAL4, si procede in questo modo:\n\n1. **Determinazione della resistenza dei materiali:**\n La resistenza dei materiali da utilizzare nelle verifiche accurate si determina a partire dalle indagini sull’opera. La caratterizzazione deve essere distinta per tutti i materiali presenti nell'opera, in accordo con la ST-PI. Le resistenze per verifiche rispetto ad azioni antropiche come i carichi da traffico e le azioni ambientali sono definite dalle LG20. Questo include la distinzione rispetto a quelle utilizzate per le azioni sismiche, come dettagliato dalle NTC18 e CIR19 .\n\n2. **Calcolo della resistenza:**\n Il valore della resistenza dei materiali $ f_d $ da utilizzare nelle verifiche per carichi gravitazionali si ottiene mediante la seguente espressione:\n\n \\[\n f_d = \\min \\left( \\frac{f_m}{FC \\cdot \\gamma_M}, \\frac{f_k}{FC} \\right)\n \\]\n\n Dove:\n - $ f_m $ è il valor medio della resistenza valutato dai risultati delle prove effettuate sui campioni prelevati in situ;\n - $ f_k $ è la resistenza caratteristica calcolata sulla base dei risultati delle prove sui campioni prelevati in situ;\n - $ FC $ è il Fattore di Confidenza associato al Livello di Conoscenza raggiunto;\n - $ \\gamma_M $ è il fattore parziale di sicurezza del materiale .\n\n3. **Caratterizzazione dei materiali:**\n I valori medi, caratteristici e di progetto delle resistenze a compressione e trazione per il calcestruzzo, e delle resistenze a snervamento per l’acciaio ordinario sono determinati a partire dai risultati delle prove. Specifiche espressioni e metodi sono forniti per diversi materiali nell'Appendice 13 della ST-VAL4 .\n\n4. **Stima della resistenza da prove non distruttive:**\n Per valutare le resistenze dei materiali, si possono anche utilizzare prove non distruttive come le prove SonReb per il calcestruzzo e le prove di durezza per l'acciaio ordinario e armonico. Formulazioni specifiche per queste prove si trovano in Appendice al §13.1 della ST-VAL4 .\n\n5. **Analisi dei risultati delle indagini:**\n Gli esiti delle prove devono essere attentamente analizzati per confermare le caratteristiche originarie o rilevare eventuali decadimenti del materiale. Questo processo include anche l'identificazione delle cause di dispersione significative nei risultati delle prove .\n\nSeguendo questi passaggi, si ottiene una stima accurata delle resistenze dei materiali che possono essere utilizzate per le verifiche di sicurezza rispetto ad azioni antropiche e ambientali.",
@@ -304,6 +323,7 @@ it('renders ChatBubble with complex markdown and math 3 unchanged', () => {
304
323
  memori={memori}
305
324
  tenant={tenant}
306
325
  sessionID={sessionID}
326
+ useMathFormatting
307
327
  message={{
308
328
  fromUser: false,
309
329
  initial: false,
@@ -320,6 +340,7 @@ it('renders ChatBubble with complex markdown and math 4 unchanged', () => {
320
340
  memori={memori}
321
341
  tenant={tenant}
322
342
  sessionID={sessionID}
343
+ useMathFormatting
323
344
  message={{
324
345
  fromUser: false,
325
346
  initial: false,
@@ -336,6 +357,7 @@ it('renders ChatBubble with complex markdown and math 5 unchanged', () => {
336
357
  memori={memori}
337
358
  tenant={tenant}
338
359
  sessionID={sessionID}
360
+ useMathFormatting
339
361
  message={{
340
362
  fromUser: false,
341
363
  initial: false,
@@ -29,24 +29,6 @@ import markedLinkifyIt from 'marked-linkify-it';
29
29
  import markedKatex from 'marked-katex-extension';
30
30
  import markedExtendedTables from '../../helpers/markedExtendedTables';
31
31
 
32
- export interface Props {
33
- message: Message;
34
- memori: Memori;
35
- sessionID: string;
36
- tenant?: Tenant;
37
- baseUrl?: string;
38
- apiUrl?: string;
39
- showFeedback?: boolean;
40
- showWhyThisAnswer?: boolean;
41
- showCopyButton?: boolean;
42
- showTranslationOriginal?: boolean;
43
- simulateUserPrompt?: (msg: string) => void;
44
- showAIicon?: boolean;
45
- isFirst?: boolean;
46
- userAvatar?: MemoriProps['userAvatar'];
47
- user?: User;
48
- experts?: ExpertReference[];
49
- }
50
32
 
51
33
  marked.use({
52
34
  async: false,
@@ -55,6 +37,7 @@ marked.use({
55
37
  renderer: {
56
38
  link: ({ href, title, text }) => {
57
39
  const cleanHref = cleanUrl(href);
40
+
58
41
  if (cleanHref === null) {
59
42
  return text;
60
43
  }
@@ -69,14 +52,100 @@ marked.use({
69
52
  },
70
53
  });
71
54
  marked.use(markedLinkifyIt());
72
- marked.use(
73
- markedKatex({
74
- throwOnError: false,
75
- output: 'htmlAndMathml',
76
- })
77
- );
78
55
  marked.use(markedExtendedTables());
79
56
 
57
+ const parseSquaredBrackets = (text: string) => {
58
+ const rows = text.split('\n');
59
+
60
+ return rows.reduce((acc, row) => {
61
+ if (row.includes('=')) {
62
+ let result = '';
63
+ let isEscaped = false;
64
+ for (let i = 0; i < row.length; i++) {
65
+ if (row[i] === '[' && !isEscaped) {
66
+ result += '\\[';
67
+ } else if (row[i] === ']' && !isEscaped) {
68
+ result += '\\]';
69
+ } else {
70
+ result += row[i];
71
+ }
72
+ isEscaped = row[i] === '\\' && !isEscaped;
73
+ }
74
+
75
+ return acc?.length ? `${acc}\n${result}` : result;
76
+ } else {
77
+ return acc?.length ? `${acc}\n${row}` : row;
78
+ }
79
+ }, '');
80
+ };
81
+
82
+ const renderMsg = (text: string, useMathFormatting = false) => {
83
+ try {
84
+ let parsedText = DOMPurify.sanitize(
85
+ (
86
+ marked.parse(
87
+ text
88
+ // remove leading and trailing whitespaces
89
+ .trim()
90
+ // remove markdown links
91
+ .replaceAll(
92
+ /\[([^\]]+)\]\(([^\)]+)\)/g,
93
+ '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>'
94
+ )
95
+ // remove markdown multiline code blocks but keep the content
96
+ .replaceAll(/```markdown([^```]+)```/g, '$1')
97
+ .replaceAll('($', '( $')
98
+ .replaceAll(':$', ': $')
99
+ .replaceAll('\frac', '\\frac')
100
+ .replaceAll('\beta', '\\beta')
101
+ .replaceAll('cdot', '\\cdot')
102
+ ) as string
103
+ )
104
+ .trim()
105
+ .replace(/\n/g, '<br>'),
106
+ {
107
+ ADD_ATTR: ['target'],
108
+ }
109
+ );
110
+
111
+ if (useMathFormatting) {
112
+ parsedText = parseSquaredBrackets(parsedText);
113
+ }
114
+
115
+ return (
116
+ parsedText
117
+ // replace consecutive <br> with a single <br>
118
+ .replace(/(<br>)+/g, '<br>')
119
+ // remove empty paragraphs
120
+ .replace(/<p><\/p>/g, '<br>')
121
+ .replace(/<p><br><\/p>/g, '<br>')
122
+ );
123
+ } catch (e) {
124
+ console.error(e);
125
+ return text;
126
+ }
127
+ };
128
+
129
+ export interface Props {
130
+ message: Message;
131
+ memori: Memori;
132
+ sessionID: string;
133
+ tenant?: Tenant;
134
+ baseUrl?: string;
135
+ apiUrl?: string;
136
+ showFeedback?: boolean;
137
+ showWhyThisAnswer?: boolean;
138
+ showCopyButton?: boolean;
139
+ showTranslationOriginal?: boolean;
140
+ simulateUserPrompt?: (msg: string) => void;
141
+ showAIicon?: boolean;
142
+ useMathFormatting?: boolean;
143
+ isFirst?: boolean;
144
+ userAvatar?: MemoriProps['userAvatar'];
145
+ user?: User;
146
+ experts?: ExpertReference[];
147
+ }
148
+
80
149
  const ChatBubble: React.FC<Props> = ({
81
150
  message,
82
151
  memori,
@@ -91,6 +160,7 @@ const ChatBubble: React.FC<Props> = ({
91
160
  simulateUserPrompt,
92
161
  showAIicon = true,
93
162
  isFirst = false,
163
+ useMathFormatting = false,
94
164
  user,
95
165
  userAvatar,
96
166
  experts,
@@ -99,83 +169,29 @@ const ChatBubble: React.FC<Props> = ({
99
169
  const lang = i18n.language || 'en';
100
170
  const [showingWhyThisAnswer, setShowingWhyThisAnswer] = useState(false);
101
171
 
102
- const text = message.translatedText || message.text;
103
-
104
- const parseSquaredBrackets = (text: string) => {
105
- const rows = text.split('\n');
172
+ if (useMathFormatting) {
173
+ marked.use(
174
+ markedKatex({
175
+ throwOnError: false,
176
+ output: 'htmlAndMathml',
177
+ })
178
+ );
179
+ }
106
180
 
107
- return rows.reduce((acc, row) => {
108
- if (row.includes('=')) {
109
- let result = '';
110
- let isEscaped = false;
111
- for (let i = 0; i < row.length; i++) {
112
- if (row[i] === '[' && !isEscaped) {
113
- result += '\\[';
114
- } else if (row[i] === ']' && !isEscaped) {
115
- result += '\\]';
116
- } else {
117
- result += row[i];
118
- }
119
- isEscaped = row[i] === '\\' && !isEscaped;
120
- }
121
-
122
- return acc?.length ? `${acc}\n${result}` : result;
123
- } else {
124
- return acc?.length ? `${acc}\n${row}` : row;
125
- }
126
- }, '');
127
- };
181
+ const text = message.translatedText || message.text;
128
182
 
129
- const renderMsg = (text: string) => {
130
- try {
131
- return (
132
- parseSquaredBrackets(
133
- DOMPurify.sanitize(
134
- (
135
- marked.parse(
136
- text
137
- // remove leading and trailing whitespaces
138
- .trim()
139
- // remove markdown links
140
- .replaceAll(
141
- /\[([^\]]+)\]\(([^\)]+)\)/g,
142
- '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>'
143
- )
144
- // remove markdown multiline code blocks but keep the content
145
- .replaceAll(/```markdown([^```]+)```/g, '$1')
146
- .replaceAll('($', '( $')
147
- .replaceAll(':$', ': $')
148
- .replaceAll('\frac', '\\frac')
149
- .replaceAll('\beta', '\\beta')
150
- .replaceAll('cdot', '\\cdot')
151
- ) as string
152
- )
153
- .trim()
154
- .replace(/\n/g, '<br>'),
155
- {
156
- ADD_ATTR: ['target'],
157
- }
158
- )
159
- )
160
- // replace consecutive <br> with a single <br>
161
- .replace(/(<br>)+/g, '<br>')
162
- // remove empty paragraphs
163
- .replace(/<p><\/p>/g, '<br>')
164
- .replace(/<p><br><\/p>/g, '<br>')
165
- );
166
- } catch (e) {
167
- console.error(e);
168
- return text;
169
- }
170
- };
171
- const renderedText = renderMsg(text);
183
+ const renderedText = renderMsg(text, useMathFormatting);
172
184
 
173
185
  const plainText = message.fromUser
174
186
  ? text
175
187
  : stripHTML(stripOutputTags(renderedText));
176
188
 
177
189
  useLayoutEffect(() => {
178
- if (typeof window !== 'undefined' && !message.fromUser) {
190
+ if (
191
+ typeof window !== 'undefined' &&
192
+ !message.fromUser &&
193
+ useMathFormatting
194
+ ) {
179
195
  // @ts-ignore
180
196
  // eslint-disable-next-line no-undef
181
197
  if ('MathJax' in window && window.MathJax.typesetPromise)
@@ -183,7 +199,7 @@ const ChatBubble: React.FC<Props> = ({
183
199
  // eslint-disable-next-line no-undef
184
200
  window.MathJax.typesetPromise(['.memori-chat--bubble-content']);
185
201
  }
186
- }, [message.text, message.fromUser]);
202
+ }, [message.text, message.fromUser, useMathFormatting]);
187
203
 
188
204
  return (
189
205
  <>