@memori.ai/memori-react 8.1.0-rc.0 → 8.2.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 (35) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/components/ChatHistoryDrawer/ChatHistory.css +12 -2
  3. package/dist/components/ChatHistoryDrawer/ChatHistory.d.ts +4 -1
  4. package/dist/components/ChatHistoryDrawer/ChatHistory.js +48 -7
  5. package/dist/components/ChatHistoryDrawer/ChatHistory.js.map +1 -1
  6. package/dist/components/MemoriWidget/MemoriWidget.js +11 -10
  7. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  8. package/dist/index.js +37 -8
  9. package/dist/index.js.map +1 -1
  10. package/dist/locales/de.json +1 -0
  11. package/dist/locales/es.json +1 -0
  12. package/dist/locales/fr.json +1 -0
  13. package/esm/components/ChatHistoryDrawer/ChatHistory.css +12 -2
  14. package/esm/components/ChatHistoryDrawer/ChatHistory.d.ts +4 -1
  15. package/esm/components/ChatHistoryDrawer/ChatHistory.js +48 -7
  16. package/esm/components/ChatHistoryDrawer/ChatHistory.js.map +1 -1
  17. package/esm/components/MemoriWidget/MemoriWidget.js +11 -10
  18. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  19. package/esm/index.js +37 -8
  20. package/esm/index.js.map +1 -1
  21. package/esm/locales/de.json +1 -0
  22. package/esm/locales/es.json +1 -0
  23. package/esm/locales/fr.json +1 -0
  24. package/package.json +1 -1
  25. package/src/__snapshots__/index.test.tsx.snap +19 -0
  26. package/src/components/ChatHistoryDrawer/ChatHistory.css +12 -2
  27. package/src/components/ChatHistoryDrawer/ChatHistory.stories.tsx +40 -17
  28. package/src/components/ChatHistoryDrawer/ChatHistory.test.tsx +8 -0
  29. package/src/components/ChatHistoryDrawer/ChatHistory.tsx +82 -3
  30. package/src/components/MemoriWidget/MemoriWidget.tsx +17 -26
  31. package/src/index.test.tsx +50 -0
  32. package/src/index.tsx +56 -38
  33. package/src/locales/de.json +1 -0
  34. package/src/locales/es.json +1 -0
  35. package/src/locales/fr.json +1 -0
@@ -105,7 +105,7 @@ const mockChatLogs: ChatLog[] = [
105
105
  {
106
106
  chatLogID: 'chat345678',
107
107
  memoriID: 'mem123',
108
- sessionID: 'session123',
108
+ sessionID: 'session1234',
109
109
  timestamp: '2023-01-04T09:30:00Z',
110
110
  lines: [
111
111
  {
@@ -158,6 +158,25 @@ const mockMemori: Memori = {
158
158
  voiceType: 'FEMALE'
159
159
  } as Memori;
160
160
 
161
+ const mockParams = {
162
+ open: true,
163
+ sessionId: 'session12345',
164
+ memori: mockMemori,
165
+ apiClient: mockApiClient as any,
166
+ onClose: () => console.log('Close button clicked'),
167
+ history: mockChatLogs.map(chatLog => ({
168
+ text: chatLog.lines[0].text,
169
+ fromUser: chatLog.lines[0].inbound,
170
+ timestamp: chatLog.lines[0].timestamp,
171
+ })),
172
+ resumeSession: (chatLog: ChatLog) => {
173
+ console.log('Resume session called with:', chatLog);
174
+ },
175
+ loginToken: 'mock-login-token',
176
+ language: 'EN',
177
+ userLang: 'EN',
178
+ }
179
+
161
180
  // Create a meta object for the component
162
181
  const meta: Meta<typeof ChatHistory> = {
163
182
  title: 'Widget/Chat History Drawer',
@@ -173,22 +192,7 @@ const meta: Meta<typeof ChatHistory> = {
173
192
  layout: 'fullscreen',
174
193
  },
175
194
  // Define common args that apply to all stories
176
- args: {
177
- open: true,
178
- sessionId: 'session123',
179
- memori: mockMemori,
180
- apiClient: mockApiClient as any,
181
- onClose: () => console.log('Close button clicked'),
182
- history: mockChatLogs.map(chatLog => ({
183
- text: chatLog.lines[0].text,
184
- fromUser: chatLog.lines[0].inbound,
185
- timestamp: chatLog.lines[0].timestamp,
186
- })),
187
- resumeSession: (chatLog: ChatLog) => {
188
- console.log('Resume session called with:', chatLog);
189
- },
190
- loginToken: 'mock-login-token',
191
- },
195
+ args: mockParams,
192
196
  // Add argTypes to configure controls in Storybook
193
197
  argTypes: {
194
198
  open: { control: 'boolean', description: 'Whether the drawer is open' },
@@ -335,4 +339,23 @@ export const WithPagination: Story = {
335
339
  }
336
340
  } as any
337
341
  }
342
+ }
343
+
344
+
345
+ // With translation
346
+ export const WithTranslation: Story = {
347
+ args: {
348
+ ...mockParams,
349
+ language: 'EN',
350
+ userLang: 'IT',
351
+ isMultilanguageEnabled: true
352
+ }
353
+ };
354
+
355
+ // With current chat disabled
356
+ export const WithCurrentChatDisabled: Story = {
357
+ args: {
358
+ ...mockParams,
359
+ sessionId: 'session1234',
360
+ }
338
361
  };
@@ -39,6 +39,8 @@ it('renders ChatHistoryDrawer unchanged', () => {
39
39
  baseUrl="https://www.aisuru.com"
40
40
  apiUrl="https://backend.memori.ai"
41
41
  loginToken="test-login-token"
42
+ language="EN"
43
+ userLang="EN"
42
44
  />
43
45
  );
44
46
  expect(container).toMatchSnapshot();
@@ -57,6 +59,8 @@ it('renders ChatHistoryDrawer with chat logs unchanged', () => {
57
59
  baseUrl="https://www.aisuru.com"
58
60
  apiUrl="https://backend.memori.ai"
59
61
  loginToken="test-login-token"
62
+ language="EN"
63
+ userLang="EN"
60
64
  />
61
65
  );
62
66
  expect(container).toMatchSnapshot();
@@ -75,6 +79,8 @@ it('renders ChatHistoryDrawer with selected chat log unchanged', () => {
75
79
  baseUrl="https://www.aisuru.com"
76
80
  apiUrl="https://backend.memori.ai"
77
81
  loginToken="test-login-token"
82
+ language="EN"
83
+ userLang="EN"
78
84
  />
79
85
  );
80
86
  expect(container).toMatchSnapshot();
@@ -93,6 +99,8 @@ it('renders ChatHistoryDrawer closed unchanged', () => {
93
99
  baseUrl="https://www.aisuru.com"
94
100
  apiUrl="https://backend.memori.ai"
95
101
  loginToken="test-login-token"
102
+ language="EN"
103
+ userLang="EN"
96
104
  />
97
105
  );
98
106
  expect(container).toMatchSnapshot();
@@ -28,6 +28,8 @@ import Download from '../icons/Download';
28
28
  import MessageIcon from '../icons/Message';
29
29
  import ArrowUpIcon from '../icons/ArrowUp';
30
30
  import Select from '../ui/Select';
31
+ // Helpers / Utils
32
+ import { getTranslation } from '../../helpers/translations';
31
33
 
32
34
  export interface Props {
33
35
  open: boolean;
@@ -40,6 +42,9 @@ export interface Props {
40
42
  apiUrl: string;
41
43
  history: Message[];
42
44
  loginToken?: string;
45
+ language: string;
46
+ userLang: string;
47
+ isMultilanguageEnabled?: boolean;
43
48
  }
44
49
 
45
50
  const ITEMS_PER_PAGE = 8;
@@ -352,6 +357,54 @@ const calculateTitle = (lines: ChatLogLine[]): string => {
352
357
  return title;
353
358
  };
354
359
 
360
+ // Function to translate all chat logs lines
361
+ const translateChatLogs = async (
362
+ chatLogs: ChatLog[],
363
+ fromLanguage: string,
364
+ toLanguage: string,
365
+ baseUrl: string
366
+ ): Promise<ChatLog[]> => {
367
+ try {
368
+ const translatedChatLogs = await Promise.all(
369
+ chatLogs.map(async (chatLog) => {
370
+ const translatedLines = await Promise.all(
371
+ chatLog.lines.map(async (line) => {
372
+ if (!line.text) return line;
373
+
374
+ try {
375
+ const translation = await getTranslation(
376
+ line.text,
377
+ toLanguage,
378
+ fromLanguage,
379
+ baseUrl
380
+ );
381
+
382
+ return {
383
+ ...line,
384
+ originalText: line.text,
385
+ text: translation.text,
386
+ };
387
+ } catch (e) {
388
+ console.error('Error translating line:', e);
389
+ return line; // Return original line if translation fails
390
+ }
391
+ })
392
+ );
393
+
394
+ return {
395
+ ...chatLog,
396
+ lines: translatedLines,
397
+ };
398
+ })
399
+ );
400
+
401
+ return translatedChatLogs;
402
+ } catch (e) {
403
+ console.error('Error translating chat logs:', e);
404
+ return chatLogs; // Return original chat logs if translation fails
405
+ }
406
+ };
407
+
355
408
  const downloadFile = (text: string, filename: string) => {
356
409
  const data = new Blob([text], { type: 'text/plain' });
357
410
  const url = URL.createObjectURL(data);
@@ -376,6 +429,9 @@ const ChatHistoryDrawer = ({
376
429
  apiUrl,
377
430
  history,
378
431
  loginToken,
432
+ language,
433
+ userLang,
434
+ isMultilanguageEnabled = false,
379
435
  }: Props) => {
380
436
  const { t } = useTranslation();
381
437
  const { getUserChatLogsByTokenPaged } = apiClient.chatLogs;
@@ -408,7 +464,7 @@ const ChatHistoryDrawer = ({
408
464
  const [minimumMessagesPerChat, setMinimumMessagesPerChat] =
409
465
  useState<number>(3);
410
466
 
411
- const [customMinimumMessages, setCustomMinimumMessages] = useState<number>(0);
467
+ const [customMinimumMessages, setCustomMinimumMessages] = useState<number>(1);
412
468
 
413
469
  const formatDateForAPI = (date: Date) => {
414
470
  const year = date.getFullYear();
@@ -528,7 +584,7 @@ const ChatHistoryDrawer = ({
528
584
  const res = response;
529
585
 
530
586
  // Sort the chat logs by date
531
- const sortedChatLogs = res.chatLogs.sort((a: ChatLog, b: ChatLog) => {
587
+ let sortedChatLogs = res.chatLogs.sort((a: ChatLog, b: ChatLog) => {
532
588
  const dateA = Math.max(
533
589
  ...a.lines.map((l: any) => new Date(l.timestamp).getTime())
534
590
  );
@@ -538,6 +594,19 @@ const ChatHistoryDrawer = ({
538
594
  return sortOrder === 'desc' ? dateB - dateA : dateA - dateB;
539
595
  });
540
596
 
597
+ // Translate chat logs if needed
598
+ if (
599
+ language.toUpperCase() !== userLang.toUpperCase() &&
600
+ isMultilanguageEnabled
601
+ ) {
602
+ sortedChatLogs = await translateChatLogs(
603
+ sortedChatLogs,
604
+ language,
605
+ userLang,
606
+ baseUrl
607
+ );
608
+ }
609
+
541
610
  setChatLogs(sortedChatLogs);
542
611
  setTotalItems(res.count || sortedChatLogs.length);
543
612
  setTotalPages(
@@ -689,8 +758,12 @@ const ChatHistoryDrawer = ({
689
758
 
690
759
  return (
691
760
  <Card
692
- hoverable
761
+ hoverable={chatLog?.sessionID !== sessionId}
693
762
  onClick={async () => {
763
+ // the active chat
764
+ if (chatLog?.sessionID === sessionId) {
765
+ return;
766
+ }
694
767
  if (selectedChatLog?.chatLogID === chatLog.chatLogID) {
695
768
  setSelectedChatLog(null);
696
769
  return;
@@ -724,6 +797,10 @@ const ChatHistoryDrawer = ({
724
797
  selectedChatLog?.chatLogID === chatLog.chatLogID
725
798
  ? 'memori-chat-history-drawer--card--selected'
726
799
  : ''
800
+ } ${
801
+ chatLog?.sessionID === sessionId
802
+ ? 'memori-chat-history-drawer--card--disabled'
803
+ : 'memori-chat-history-drawer--card--hoverable'
727
804
  }`}
728
805
  >
729
806
  <>
@@ -825,6 +902,7 @@ const ChatHistoryDrawer = ({
825
902
  <Button
826
903
  className="memori-chat-history-drawer--card--content--export-button"
827
904
  onClick={e => handleExportChat(chatLog, e)}
905
+ disabled={chatLog?.sessionID === sessionId}
828
906
  >
829
907
  <div className="memori-chat-history-drawer--card--content--export-button--content">
830
908
  <Download className="memori-chat-history-drawer--card--content--export-button--icon" />
@@ -1047,6 +1125,7 @@ const ChatHistoryDrawer = ({
1047
1125
  {minimumMessagesPerChat === 0 && (
1048
1126
  <input
1049
1127
  type="number"
1128
+ min={1}
1050
1129
  value={customMinimumMessages}
1051
1130
  onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
1052
1131
  const value = parseInt(e.target.value, 10);
@@ -2543,13 +2543,20 @@ const MemoriWidget = ({
2543
2543
  ) {
2544
2544
  try {
2545
2545
  translatedMessages = await Promise.all(
2546
- messages.map(async m => ({
2547
- ...m,
2548
- originalText: m.text,
2549
- text: (
2550
- await getTranslation(m.text, userLang, language, baseUrl)
2551
- ).text,
2552
- }))
2546
+ messages.map(async m => {
2547
+ // If original text is present, the message is already translated
2548
+ if ('originalText' in m && m.originalText) {
2549
+ return m;
2550
+ }
2551
+ // Otherwise translate the message
2552
+ return {
2553
+ ...m,
2554
+ originalText: m.text,
2555
+ text: (
2556
+ await getTranslation(m.text, userLang, language, baseUrl)
2557
+ ).text,
2558
+ };
2559
+ })
2553
2560
  );
2554
2561
  } catch (e) {
2555
2562
  console.error('[CLICK_START] Error translating messages:', e);
@@ -3324,25 +3331,9 @@ const MemoriWidget = ({
3324
3331
  history={history}
3325
3332
  apiUrl={client.constants.BACKEND_URL}
3326
3333
  loginToken={loginToken}
3327
- />
3328
- )}
3329
-
3330
- {showChatHistoryDrawer && (
3331
- <ChatHistoryDrawer
3332
- open={!!showChatHistoryDrawer}
3333
- onClose={() => setShowChatHistoryDrawer(false)}
3334
- resumeSession={chatLog => {
3335
- setChatLogID(chatLog.chatLogID);
3336
- onClickStart(undefined, false, chatLog);
3337
- setShowChatHistoryDrawer(false);
3338
- }}
3339
- apiClient={client}
3340
- sessionId={sessionId || ''}
3341
- memori={memori}
3342
- baseUrl={baseUrl}
3343
- history={history}
3344
- apiUrl={client.constants.BACKEND_URL}
3345
- loginToken={loginToken}
3334
+ language={language}
3335
+ userLang={userLang}
3336
+ isMultilanguageEnabled={isMultilanguageEnabled}
3346
3337
  />
3347
3338
  )}
3348
3339
 
@@ -43,6 +43,56 @@ it('renders client', () => {
43
43
  expect(container).toMatchSnapshot();
44
44
  });
45
45
 
46
+ it('renders client with all the props', () => {
47
+ const { container } = render(
48
+ <Memori
49
+ memoriID={memori.memoriID}
50
+ ownerUserID={memori.ownerUserID}
51
+ tenantID={tenant.tenantID}
52
+ integration={{
53
+ ...integration,
54
+ customData: JSON.stringify({}),
55
+ }}
56
+ secretToken="1234567890"
57
+ height="100%"
58
+ tag="🌻"
59
+ pin="123456"
60
+ showShare={true}
61
+ showChatHistory={true}
62
+ showCopyButton={true}
63
+ showTranslationOriginal={true}
64
+ showSettings={true}
65
+ showTypingText={true}
66
+ showClear={true}
67
+ showLogin={true}
68
+ showUpload={true}
69
+ showReasoning={true}
70
+ showContextPerLine={true}
71
+ context={{
72
+ name: 'John Doe',
73
+ email: 'john.doe@example.com',
74
+ phone: '+39 333 333 3333',
75
+ }}
76
+ initialQuestion="What is the capital of France?"
77
+ autoStart={true}
78
+ enableAudio={true}
79
+ defaultSpeakerActive={true}
80
+ useMathFormatting={true}
81
+ spokenLang="EL"
82
+ multilingual={true}
83
+ authToken="1234567890"
84
+ onStateChange={() => {}}
85
+ additionalInfo={{}}
86
+ customMediaRenderer={() => <div>Custom Media Renderer</div>}
87
+ additionalSettings={<div>Additional Settings</div>}
88
+ userAvatar="https://memori.ai/avatar.png"
89
+ applyVarsToRoot={true}
90
+ disableTextEnteredEvents={true}
91
+ />
92
+ );
93
+ expect(container).toMatchSnapshot();
94
+ });
95
+
46
96
  it('renders client with whiteListedDomains on allowed domains', () => {
47
97
  mockLocation.hostname = 'memori.ai';
48
98
  const { container } = render(
package/src/index.tsx CHANGED
@@ -283,11 +283,51 @@ const Memori: React.FC<Props> = ({
283
283
  }
284
284
  }
285
285
 
286
- const initialContextVars =
287
- context ?? getParsedContext(layoutIntegrationConfig.contextVars);
288
- const initialQuestionLayout =
289
- initialQuestion ??
290
- (layoutIntegrationConfig.initialQuestion as string | undefined);
286
+ const ignoreClientAttributes =
287
+ layoutIntegrationConfig.ignoreClientAttributes ?? false;
288
+
289
+ const clientAttributes = ignoreClientAttributes
290
+ ? {
291
+ initialContextVars: getParsedContext(
292
+ layoutIntegrationConfig.contextVars
293
+ ),
294
+ initialQuestion: layoutIntegrationConfig.initialQuestion as
295
+ | string
296
+ | undefined,
297
+ showLogin: memori?.enableDeepThought,
298
+ memoriLang: memori?.culture?.split('-')?.[0],
299
+ autoStart: layout === 'HIDDEN_CHAT',
300
+ }
301
+ : {
302
+ ...(tag && pin ? { personification: { tag, pin } } : {}),
303
+ multilingual,
304
+ showCopyButton,
305
+ showTranslationOriginal,
306
+ showSettings,
307
+ showChatHistory,
308
+ showShare,
309
+ showTypingText,
310
+ showClear,
311
+ showLogin: showLogin ?? memori?.enableDeepThought,
312
+ showUpload,
313
+ showReasoning,
314
+ showContextPerLine,
315
+ initialContextVars:
316
+ context ?? getParsedContext(layoutIntegrationConfig.contextVars),
317
+ initialQuestion:
318
+ initialQuestion ??
319
+ (layoutIntegrationConfig.initialQuestion as string | undefined),
320
+ autoStart:
321
+ autoStart !== undefined
322
+ ? autoStart
323
+ : layout === 'HIDDEN_CHAT'
324
+ ? true
325
+ : autoStart,
326
+ enableAudio,
327
+ defaultSpeakerActive,
328
+ useMathFormatting,
329
+ memoriLang: spokenLang ?? memori?.culture?.split('-')?.[0],
330
+ };
291
331
 
292
332
  return (
293
333
  <I18nWrapper>
@@ -295,6 +335,7 @@ const Memori: React.FC<Props> = ({
295
335
  <Toaster position="top-center" reverseOrder={true} />
296
336
  {memori ? (
297
337
  <MemoriWidget
338
+ // General props
298
339
  layout={layout}
299
340
  customLayout={customLayout}
300
341
  height={height}
@@ -312,49 +353,26 @@ const Memori: React.FC<Props> = ({
312
353
  }}
313
354
  ownerUserName={ownerUserName ?? memori.ownerUserName}
314
355
  ownerUserID={ownerUserID ?? memori.ownerUserID}
315
- tenantID={tenantID}
316
- memoriLang={spokenLang ?? memori.culture?.split('-')?.[0]}
317
- multilingual={multilingual}
318
- showChatHistory={showChatHistory}
319
356
  tenant={tenant}
320
- secret={secretToken}
357
+ tenantID={tenantID}
321
358
  sessionID={sessionID}
322
- showShare={showShare}
323
- showCopyButton={showCopyButton}
324
- showTranslationOriginal={showTranslationOriginal}
325
- showSettings={showSettings}
326
- showTypingText={showTypingText}
327
- showClear={showClear}
328
- showOnlyLastMessages={showOnlyLastMessages}
329
- showInputs={showInputs}
330
- showDates={showDates}
331
- showContextPerLine={showContextPerLine}
332
- showLogin={showLogin ?? memori?.enableDeepThought}
333
- showUpload={showUpload}
334
- showReasoning={showReasoning}
359
+ secret={secretToken}
360
+ ttsProvider={provider ? (provider as 'azure' | 'openai') : 'azure'}
335
361
  integration={layoutIntegration}
336
- initialContextVars={initialContextVars}
337
- initialQuestion={initialQuestionLayout}
338
362
  authToken={authToken}
339
- ttsProvider={provider ? (provider as 'azure' | 'openai') : 'azure'}
340
- autoStart={
341
- autoStart !== undefined
342
- ? autoStart
343
- : layout === 'HIDDEN_CHAT'
344
- ? true
345
- : autoStart
346
- }
347
- enableAudio={enableAudio}
348
- defaultSpeakerActive={defaultSpeakerActive}
349
- disableTextEnteredEvents={disableTextEnteredEvents}
350
363
  onStateChange={onStateChange}
351
364
  additionalInfo={additionalInfo}
352
365
  customMediaRenderer={customMediaRenderer}
353
366
  additionalSettings={additionalSettings}
354
367
  userAvatar={userAvatar}
355
- useMathFormatting={useMathFormatting}
356
368
  applyVarsToRoot={applyVarsToRoot}
357
- {...(tag && pin ? { personification: { tag, pin } } : {})}
369
+ disableTextEnteredEvents={disableTextEnteredEvents}
370
+ // From layout, from client if allowed
371
+ {...clientAttributes}
372
+ // Client only
373
+ showOnlyLastMessages={showOnlyLastMessages}
374
+ showInputs={showInputs}
375
+ showDates={showDates}
358
376
  />
359
377
  ) : (
360
378
  <div
@@ -137,6 +137,7 @@
137
137
  },
138
138
  "instructButton": "WEISE MICH AN",
139
139
  "tryMeButton": "START",
140
+ "resumeButton": "FORTSETZEN",
140
141
  "chatHistory": "Chatverlauf",
141
142
  "chatHistoryDescription": "Chatverlauf mit diesem Agenten anzeigen",
142
143
  "loadingChatHistory": "Chatverlauf wird geladen...",
@@ -137,6 +137,7 @@
137
137
  "instructButton": "INSTRUÍME",
138
138
  "downloadChat": "Descargar chat",
139
139
  "tryMeButton": "COMENZAR",
140
+ "resumeButton": "CONTINUAR",
140
141
  "chatHistory": "Historial de chat",
141
142
  "chatHistoryDescription": "Mostrar el historial de chat con este agente",
142
143
  "loadingChatHistory": "Cargando historial de chat...",
@@ -146,6 +146,7 @@
146
146
  "downloadChat": "Télécharger le chat",
147
147
  "instructButton": "INSTRUCTEZ-MOI",
148
148
  "tryMeButton": "COMMENCER",
149
+ "resumeButton": "RÉSUMER",
149
150
  "chatHistory": "Historique des discussions",
150
151
  "chatHistoryDescription": "Afficher l'historique des discussions avec cet agent",
151
152
  "loadingChatHistory": "Chargement de l'historique des discussions...",