@memori.ai/memori-react 9.0.0-alpha.7 → 9.0.0-alpha.8

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/esm/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "9.0.0-alpha.7";
1
+ export declare const version = "9.0.0-alpha.8";
package/esm/version.js CHANGED
@@ -1,2 +1,2 @@
1
- export const version = '9.0.0-alpha.7';
1
+ export const version = '9.0.0-alpha.8';
2
2
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "9.0.0-alpha.7",
2
+ "version": "9.0.0-alpha.8",
3
3
  "name": "@memori.ai/memori-react",
4
4
  "author": "Memori Srl",
5
5
  "main": "dist/index.js",
@@ -24,6 +24,7 @@ import type {
24
24
  PiiDetectionConfig,
25
25
  } from '../../types/layout';
26
26
  import { checkPii } from '../../helpers/piiDetection'; // PII check when integrationConfig.layout has piiDetection.enabled
27
+ import { shouldRestartSessionOnPositionPopoverClose } from '../../helpers/positionPopover';
27
28
 
28
29
  // Libraries
29
30
  import React, {
@@ -3264,10 +3265,17 @@ const MemoriWidget = ({
3264
3265
 
3265
3266
  const setPositionPopoverOpen = useCallback(
3266
3267
  (open: boolean) => {
3267
- setPositionPopoverOpenState(open);
3268
- if (!open && autoStart) {
3269
- onClickStart();
3270
- }
3268
+ setPositionPopoverOpenState(wasOpen => {
3269
+ if (
3270
+ shouldRestartSessionOnPositionPopoverClose(wasOpen, open, autoStart)
3271
+ ) {
3272
+ // Defer: React setState updaters must stay pure.
3273
+ queueMicrotask(() => {
3274
+ onClickStart();
3275
+ });
3276
+ }
3277
+ return open;
3278
+ });
3271
3279
  },
3272
3280
  [autoStart, onClickStart]
3273
3281
  );
@@ -1,32 +1,13 @@
1
1
  /* eslint-disable react/prop-types */
2
2
  import React, { useState, useEffect, useRef, useMemo } from 'react';
3
- import { Button, Spin } from '@memori.ai/ui';
3
+ import { Spin } from '@memori.ai/ui';
4
4
  import { LayoutProps } from '../MemoriWidget/MemoriWidget';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import { useArtifact } from '../MemoriArtifactSystem/context/ArtifactContext';
7
- import {
8
- EllipsisVertical,
9
- HelpCircle,
10
- LogIn,
11
- LogOut,
12
- MapPin,
13
- MessageCircle,
14
- RefreshCw,
15
- Settings,
16
- Share2,
17
- Trash2,
18
- User,
19
- Users,
20
- Volume2,
21
- VolumeX,
22
- Maximize,
23
- X,
24
- } from 'lucide-react';
7
+ import { HelpCircle, X } from 'lucide-react';
25
8
  import { getResourceUrl } from '../../helpers/media';
26
9
  import ArtifactDrawer from '../MemoriArtifactSystem/components/ArtifactDrawer/ArtifactDrawer';
27
10
  import ChatInputs from '../ChatInputs/ChatInputs';
28
- import ShareButton from '../ShareButton/ShareButton';
29
- import MobileSessionPanel from '../MobileSessionPanel/MobileSessionPanel';
30
11
  import {
31
12
  maxDocumentsPerMessage,
32
13
  maxDocumentContentLength,
@@ -47,11 +28,10 @@ const HiddenChatLayout: React.FC<LayoutProps> = ({
47
28
  StartPanel,
48
29
  onSidebarToggle,
49
30
  }) => {
50
- const { t, i18n } = useTranslation();
31
+ const { t } = useTranslation();
51
32
  const [isOpen, setIsOpen] = useState(false);
52
33
  const [fullScreen, setFullScreen] = useState(false);
53
34
  const [hasTriggeredAutostart, setHasTriggeredAutostart] = useState(false);
54
- const [sessionPanelOpen, setSessionPanelOpen] = useState(false);
55
35
 
56
36
  const { state, closeArtifact } = useArtifact();
57
37
  const useSideArtifactChrome =
@@ -61,18 +41,6 @@ const HiddenChatLayout: React.FC<LayoutProps> = ({
61
41
  const memori = headerProps?.memori;
62
42
  const tenant = headerProps?.tenant;
63
43
  const baseUrl = headerProps?.baseUrl;
64
- const hasSustainabilityData = useMemo(
65
- () =>
66
- !!headerProps?.history?.some(
67
- line =>
68
- !!(
69
- line as {
70
- llmUsage?: { energyImpact?: unknown };
71
- }
72
- ).llmUsage?.energyImpact
73
- ),
74
- [headerProps?.history]
75
- );
76
44
  const hiddenChatHeaderProps = useMemo(() => {
77
45
  if (!headerProps) return undefined;
78
46
  return {
@@ -95,260 +63,8 @@ const HiddenChatLayout: React.FC<LayoutProps> = ({
95
63
  };
96
64
  }, [headerProps]);
97
65
 
98
- const loggedUser =
99
- headerProps?.loginToken && headerProps?.user ? headerProps.user : undefined;
100
- const sharedUrl = useMemo(() => {
101
- if (!headerProps?.memori || !headerProps?.sessionID) return undefined;
102
- const currentLanguage = i18n.language === 'it' ? 'it' : 'en';
103
- if (headerProps.memori.ownerUserID) {
104
- return `${
105
- headerProps.baseUrl ?? 'https://www.aisuru.com'
106
- }/${currentLanguage}/shared/${headerProps.memori.ownerUserID}/${
107
- headerProps.memori.memoriID
108
- }/${headerProps.sessionID}`;
109
- }
110
- if (headerProps.memori.exposed) {
111
- return `${
112
- headerProps.baseUrl ?? 'https://www.aisuru.com'
113
- }/${currentLanguage}/shared/${headerProps.memori.ownerUserName}/${
114
- headerProps.memori.name
115
- }/${headerProps.sessionID}`;
116
- }
117
- return window.location.href;
118
- }, [headerProps, i18n.language]);
119
- const loggedUserDisplayName = useMemo(() => {
120
- if (!loggedUser) return '';
121
- const enrichedUser = loggedUser as typeof loggedUser & {
122
- name?: string;
123
- surname?: string;
124
- firstName?: string;
125
- lastName?: string;
126
- };
127
- const fullName =
128
- [enrichedUser.name, enrichedUser.surname]
129
- .filter(Boolean)
130
- .join(' ')
131
- .trim() ||
132
- [enrichedUser.firstName, enrichedUser.lastName]
133
- .filter(Boolean)
134
- .join(' ')
135
- .trim();
136
- return fullName || loggedUser.userName || loggedUser.eMail || '';
137
- }, [loggedUser]);
138
- const loggedUserInitial = useMemo(
139
- () => (loggedUserDisplayName || 'U').charAt(0).toUpperCase(),
140
- [loggedUserDisplayName]
141
- );
142
66
  const isSessionStarted = Boolean(sessionId && hasUserActivatedSpeak);
143
67
 
144
- const handleOverflowActionClick = (
145
- action:
146
- | 'reload'
147
- | 'clear'
148
- | 'chatHistory'
149
- | 'fullscreen'
150
- | 'deepThought'
151
- | 'experts'
152
- | 'audio'
153
- | 'settings'
154
- | 'share'
155
- | 'login'
156
- ) => {
157
- if (!headerProps) return;
158
- switch (action) {
159
- case 'reload':
160
- window.location.reload();
161
- break;
162
- case 'clear':
163
- headerProps.clearHistory();
164
- break;
165
- case 'chatHistory':
166
- headerProps.setShowChatHistoryDrawer(true);
167
- break;
168
- case 'fullscreen':
169
- handleFullscreenToggle();
170
- break;
171
- case 'deepThought':
172
- headerProps.setShowKnownFactsDrawer(true);
173
- break;
174
- case 'experts':
175
- headerProps.setShowExpertsDrawer(true);
176
- break;
177
- case 'audio':
178
- headerProps.setSpeakerMuted(!headerProps.speakerMuted);
179
- break;
180
- case 'settings':
181
- headerProps.setShowSettingsDrawer(true);
182
- break;
183
- case 'share': {
184
- const targetUrl = sharedUrl || window.location.href;
185
- if (navigator.share) {
186
- navigator
187
- .share({
188
- title: headerProps.memori?.name,
189
- url: targetUrl,
190
- })
191
- .catch(() => {
192
- // User dismissed share sheet or browser blocked it.
193
- });
194
- break;
195
- }
196
- if (navigator.clipboard?.writeText) {
197
- navigator.clipboard.writeText(targetUrl).catch(() => {
198
- const opened = window.open(targetUrl, '_blank');
199
- if (!opened) window.location.assign(targetUrl);
200
- });
201
- break;
202
- }
203
- {
204
- const opened = window.open(targetUrl, '_blank');
205
- if (!opened) window.location.assign(targetUrl);
206
- }
207
- break;
208
- }
209
- case 'login':
210
- if (loggedUser && headerProps.onLogout) {
211
- headerProps.onLogout();
212
- } else {
213
- headerProps.setShowLoginDrawer(true);
214
- }
215
- break;
216
- default:
217
- break;
218
- }
219
- };
220
-
221
- const handleEnableLocation = () => {
222
- if (!headerProps) return;
223
- if (!navigator.geolocation) {
224
- headerProps.setPositionPopoverOpen(true);
225
- return;
226
- }
227
- navigator.geolocation.getCurrentPosition(
228
- position => {
229
- const latitude = position.coords.latitude;
230
- const longitude = position.coords.longitude;
231
- headerProps.setVenue({
232
- latitude,
233
- longitude,
234
- placeName: `${latitude.toFixed(5)}, ${longitude.toFixed(5)}`,
235
- uncertainty: position.coords.accuracy / 1000,
236
- });
237
- },
238
- () => {
239
- headerProps.setPositionPopoverOpen(true);
240
- },
241
- { enableHighAccuracy: true, timeout: 15000, maximumAge: 0 }
242
- );
243
- };
244
-
245
- const handleDisableLocation = () => {
246
- headerProps?.setVenue({
247
- latitude: 0,
248
- longitude: 0,
249
- placeName: '',
250
- uncertainty: 0,
251
- });
252
- };
253
-
254
- const sessionPanelActions = useMemo(
255
- () => [
256
- {
257
- key: 'fullscreen',
258
- icon: <Maximize size={18} />,
259
- title: t('fullscreenEnter') || 'Full screen',
260
- subtitle: t('widget.expandToImmersive') || 'Expand to immersive view',
261
- onClick: () => {
262
- handleFullscreenToggle();
263
- setSessionPanelOpen(false);
264
- },
265
- },
266
- ...(headerProps?.loginToken
267
- ? [
268
- {
269
- key: 'chatHistory',
270
- icon: <MessageCircle size={18} />,
271
- title: t('write_and_speak.chatHistory') || 'Chat history',
272
- onClick: () => {
273
- handleOverflowActionClick('chatHistory');
274
- setSessionPanelOpen(false);
275
- },
276
- },
277
- ]
278
- : []),
279
- {
280
- key: 'share',
281
- icon: <Share2 size={18} />,
282
- title: t('widget.share') || 'Share chat',
283
- subtitle:
284
- t('widget.mobileSession.copyLinkOrDownload') ||
285
- 'Copy link or download',
286
- view: 'share' as const,
287
- },
288
- {
289
- key: 'location',
290
- icon: <MapPin size={18} />,
291
- title:
292
- t('widget.mobileSession.locationTracking') || 'Location tracking',
293
- subtitle:
294
- headerProps?.position?.placeName ||
295
- t('widget.mobileSession.currentlyOff') ||
296
- 'Currently off',
297
- view: 'location' as const,
298
- trailing: (
299
- <span className="memori-mobile-session-panel--chevron">{'>'}</span>
300
- ),
301
- },
302
- ...(headerProps?.showReload
303
- ? [
304
- {
305
- key: 'reload',
306
- icon: <RefreshCw size={18} />,
307
- title: t('reload') || 'Reload',
308
- onClick: () => handleOverflowActionClick('reload'),
309
- },
310
- ]
311
- : []),
312
- ...(headerProps?.showClear
313
- ? [
314
- {
315
- key: 'clear',
316
- icon: <Trash2 size={18} />,
317
- title: t('clearHistory') || 'Clear chat',
318
- onClick: () => handleOverflowActionClick('clear'),
319
- },
320
- ]
321
- : []),
322
- // ...(headerProps?.memori?.enableBoardOfExperts
323
- // ? [
324
- // {
325
- // key: 'experts',
326
- // icon: <Users size={18} />,
327
- // title:
328
- // t('widget.showExpertsInTheBoard') || 'Experts in this board',
329
- // disabled: !isSessionStarted,
330
- // onClick: () => handleOverflowActionClick('experts'),
331
- // },
332
- // ]
333
- // : []),
334
- // ...(headerProps?.enableAudio
335
- // ? [
336
- // {
337
- // key: 'audio',
338
- // icon: headerProps?.speakerMuted ? (
339
- // <VolumeX size={18} />
340
- // ) : (
341
- // <Volume2 size={18} />
342
- // ),
343
- // title: t('widget.sound') || 'Sound',
344
- // onClick: () => handleOverflowActionClick('audio'),
345
- // },
346
- // ]
347
- // : []),
348
- ],
349
- [t, headerProps, isSessionStarted]
350
- );
351
-
352
68
  const brandAvatarSrc = memori
353
69
  ? memori.avatarURL && memori.avatarURL.length > 0
354
70
  ? getResourceUrl({
@@ -585,21 +301,6 @@ const HiddenChatLayout: React.FC<LayoutProps> = ({
585
301
  {...hiddenChatHeaderProps}
586
302
  buttonVariant="outline"
587
303
  fullScreenHandler={handleFullscreenToggle}
588
- extraActions={
589
- <Button
590
- variant="outline"
591
- className={`memori-hidden-chat-layout--overflow-trigger ${
592
- sessionPanelOpen ? 'memori-button--active' : ''
593
- }`}
594
- aria-label={
595
- t('widget.moreActions') || 'More actions'
596
- }
597
- icon={<EllipsisVertical />}
598
- onClick={() =>
599
- setSessionPanelOpen(currentOpen => !currentOpen)
600
- }
601
- />
602
- }
603
304
  />
604
305
  )}
605
306
  </div>
@@ -607,93 +308,6 @@ const HiddenChatLayout: React.FC<LayoutProps> = ({
607
308
  )}
608
309
  </div>
609
310
  <div className="memori-chat-layout--body">
610
- {headerProps && (
611
- <MobileSessionPanel
612
- open={sessionPanelOpen}
613
- onClose={() => setSessionPanelOpen(false)}
614
- presentation="popover"
615
- title={t('widget.mobileSession.session') || 'Session'}
616
- loginToken={headerProps.loginToken}
617
- user={headerProps.user}
618
- apiClient={headerProps.apiClient}
619
- userName={loggedUserDisplayName || memori?.name || 'User'}
620
- userEmail={loggedUser?.eMail}
621
- userInitial={loggedUserInitial}
622
- avatarURL={loggedUser?.avatarURL}
623
- birthDate={loggedUser?.birthDate}
624
- actions={sessionPanelActions}
625
- knownFactsPageTitle={t('knownFacts.title') || 'Known facts'}
626
- sharePageTitle={t('widget.share') || 'Share'}
627
- locationPageTitle={
628
- t('widget.mobileSession.locationTracking') ||
629
- 'Location tracking'
630
- }
631
- backLabel={t('back') || 'Back'}
632
- locationStatusLabel={
633
- t('widget.mobileSession.locationStatus') || 'Status'
634
- }
635
- locationPlace={headerProps.position?.placeName}
636
- locationUnknownLabel={
637
- t('write_and_speak.unknownPosition') || 'Unknown position'
638
- }
639
- locationEnableLabel={
640
- t('widget.shareLocation') || 'Share location'
641
- }
642
- locationDisableLabel={
643
- t('widget.mobileSession.disableLocationSharing') ||
644
- 'Disable location sharing'
645
- }
646
- knownFactsDescription={
647
- t('knownFacts.description', {
648
- memoriName: memori?.name || '',
649
- }) || ''
650
- }
651
- knownFactsCtaLabel={
652
- t('widget.mobileSession.openKnownFacts') ||
653
- 'Open full known facts'
654
- }
655
- knownFactsCountLabel={
656
- (t('widget.mobileSession.knownFactsMessages', {
657
- count: headerProps.history?.length || 0,
658
- }) as string) || ''
659
- }
660
- shareContent={
661
- <ShareButton
662
- tenant={headerProps?.tenant}
663
- memori={headerProps?.memori}
664
- sessionID={headerProps?.sessionID}
665
- title={headerProps?.memori?.name}
666
- baseUrl={headerProps?.baseUrl}
667
- align="left"
668
- history={headerProps?.history}
669
- renderMode="inline"
670
- />
671
- }
672
- knownFactsDisabled={!isSessionStarted}
673
- showSessionInfo={isSessionStarted}
674
- history={headerProps.history ?? []}
675
- isLoggedIn={!!loggedUser}
676
- loginLabel={t('login.login') || 'Log in'}
677
- onLogin={() => {
678
- headerProps.setShowLoginDrawer(true);
679
- setSessionPanelOpen(false);
680
- }}
681
- onKnownFactsOpen={() => {
682
- if (!isSessionStarted) return;
683
- headerProps.setShowKnownFactsDrawer(true);
684
- setSessionPanelOpen(false);
685
- }}
686
- onLocationEnable={handleEnableLocation}
687
- onLocationDisable={handleDisableLocation}
688
- venue={headerProps.position}
689
- setVenue={headerProps.setVenue}
690
- logoutLabel={t('login.logout') || 'Log out'}
691
- onLogout={() => {
692
- handleOverflowActionClick('login');
693
- setSessionPanelOpen(false);
694
- }}
695
- />
696
- )}
697
311
  {sessionId && hasUserActivatedSpeak && Chat && chatProps ? (
698
312
  <Chat {...chatProps} />
699
313
  ) : !autoStart && startPanelProps ? (
@@ -156,45 +156,6 @@ exports[`renders HIDDEN_CHAT layout unchanged 1`] = `
156
156
  </span>
157
157
  </span>
158
158
  </span>
159
- <button
160
- aria-label="widget.moreActions"
161
- class="memori-button memori-button--outline memori-button--md memori-button--icon-only memori-hidden-chat-layout--overflow-trigger "
162
- tabindex="0"
163
- type="button"
164
- >
165
- <span
166
- class="memori-button__icon"
167
- >
168
- <svg
169
- class="lucide lucide-ellipsis-vertical"
170
- fill="none"
171
- height="24"
172
- stroke="currentColor"
173
- stroke-linecap="round"
174
- stroke-linejoin="round"
175
- stroke-width="2"
176
- viewBox="0 0 24 24"
177
- width="24"
178
- xmlns="http://www.w3.org/2000/svg"
179
- >
180
- <circle
181
- cx="12"
182
- cy="12"
183
- r="1"
184
- />
185
- <circle
186
- cx="12"
187
- cy="5"
188
- r="1"
189
- />
190
- <circle
191
- cx="12"
192
- cy="19"
193
- r="1"
194
- />
195
- </svg>
196
- </span>
197
- </button>
198
159
  </div>
199
160
  </div>
200
161
  </div>
@@ -514,9 +514,3 @@
514
514
  border: var(--memori-border) solid #fff;
515
515
  color: #fff;
516
516
  }
517
-
518
- .memori-widget.memori-layout-hidden_chat[data-theme='dark'] .memori-hidden-chat-layout--overflow-trigger,
519
- [data-theme='dark'] .memori-widget.memori-layout-hidden_chat .memori-hidden-chat-layout--overflow-trigger {
520
- border: var(--memori-border) solid #fff;
521
- color: #fff;
522
- }
@@ -0,0 +1,33 @@
1
+ import { shouldRestartSessionOnPositionPopoverClose } from './positionPopover';
2
+
3
+ describe('shouldRestartSessionOnPositionPopoverClose', () => {
4
+ it('restarts only when closing an open popover with autoStart', () => {
5
+ expect(shouldRestartSessionOnPositionPopoverClose(true, false, true)).toBe(
6
+ true
7
+ );
8
+ });
9
+
10
+ it('does not restart when already closed (user/info popover dismiss)', () => {
11
+ expect(shouldRestartSessionOnPositionPopoverClose(false, false, true)).toBe(
12
+ false
13
+ );
14
+ });
15
+
16
+ it('does not restart when opening the popover', () => {
17
+ expect(shouldRestartSessionOnPositionPopoverClose(false, true, true)).toBe(
18
+ false
19
+ );
20
+ });
21
+
22
+ it('does not restart when staying open', () => {
23
+ expect(shouldRestartSessionOnPositionPopoverClose(true, true, true)).toBe(
24
+ false
25
+ );
26
+ });
27
+
28
+ it('does not restart without autoStart', () => {
29
+ expect(shouldRestartSessionOnPositionPopoverClose(true, false, false)).toBe(
30
+ false
31
+ );
32
+ });
33
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Whether closing the position popover should re-run autoStart (`onClickStart`).
3
+ * Only true on a real open → closed transition, so incidental
4
+ * `setPositionPopoverOpen(false)` calls (e.g. opening user/info popovers)
5
+ * do not wipe chat history.
6
+ */
7
+ export function shouldRestartSessionOnPositionPopoverClose(
8
+ wasOpen: boolean,
9
+ nextOpen: boolean,
10
+ autoStart: boolean
11
+ ): boolean {
12
+ return wasOpen && !nextOpen && !!autoStart;
13
+ }
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // This file is auto-generated. Do not edit manually.
2
- export const version = '9.0.0-alpha.7';
2
+ export const version = '9.0.0-alpha.8';