@memori.ai/memori-react 2.1.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/components/Avatar/Avatar.css +0 -1
  3. package/dist/components/Blob/Blob.css +6 -1
  4. package/dist/components/ChangeMode/ChangeMode.css +1 -1
  5. package/dist/components/Chat/Chat.css +3 -2
  6. package/dist/components/Chat/Chat.d.ts +1 -0
  7. package/dist/components/Chat/Chat.js +2 -2
  8. package/dist/components/Chat/Chat.js.map +1 -1
  9. package/dist/components/ChatBubble/ChatBubble.css +1 -1
  10. package/dist/components/ChatBubble/ChatBubble.js +1 -1
  11. package/dist/components/ChatBubble/ChatBubble.js.map +1 -1
  12. package/dist/components/ChatInputs/ChatInputs.css +1 -41
  13. package/dist/components/ChatInputs/ChatInputs.d.ts +1 -0
  14. package/dist/components/ChatInputs/ChatInputs.js +9 -3
  15. package/dist/components/ChatInputs/ChatInputs.js.map +1 -1
  16. package/dist/components/ChatTextArea/ChatTextArea.css +7 -3
  17. package/dist/components/DateSelector/DateSelector.css +17 -15
  18. package/dist/components/ExportHistoryButton/ExportHistoryButton.css +1 -1
  19. package/dist/components/FeedbackButtons/FeedbackButtons.css +4 -4
  20. package/dist/components/FeedbackButtons/FeedbackButtons.js +1 -1
  21. package/dist/components/FeedbackButtons/FeedbackButtons.js.map +1 -1
  22. package/dist/components/Header/Header.css +2 -1
  23. package/dist/components/Header/Header.js +1 -1
  24. package/dist/components/Header/Header.js.map +1 -1
  25. package/dist/components/ImageUpload/ImageUpload.css +2 -3
  26. package/dist/components/MediaWidget/MediaItemWidget.css +2 -0
  27. package/dist/components/MediaWidget/MediaWidget.css +1 -1
  28. package/dist/components/MemoriWidget/MemoriWidget.css +1 -2
  29. package/dist/components/MemoriWidget/MemoriWidget.js +23 -13
  30. package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
  31. package/dist/components/MicrophoneButton/MicrophoneButton.css +107 -0
  32. package/dist/components/MicrophoneButton/MicrophoneButton.d.ts +9 -0
  33. package/dist/components/MicrophoneButton/MicrophoneButton.js +46 -0
  34. package/dist/components/MicrophoneButton/MicrophoneButton.js.map +1 -0
  35. package/dist/components/SettingsDrawer/SettingsDrawer.d.ts +3 -3
  36. package/dist/components/SettingsDrawer/SettingsDrawer.js +8 -6
  37. package/dist/components/SettingsDrawer/SettingsDrawer.js.map +1 -1
  38. package/dist/components/SettingsDrawer/SettingsDrawer.test.js +7 -7
  39. package/dist/components/SettingsDrawer/SettingsDrawer.test.js.map +1 -1
  40. package/dist/components/StartPanel/StartPanel.css +1 -1
  41. package/dist/components/StartPanel/StartPanel.js +1 -1
  42. package/dist/components/StartPanel/StartPanel.js.map +1 -1
  43. package/dist/components/layouts/totem.css +49 -8
  44. package/dist/components/ui/Button.css +1 -1
  45. package/dist/components/ui/Button.d.ts +5 -1
  46. package/dist/components/ui/Button.js +1 -1
  47. package/dist/components/ui/Button.js.map +1 -1
  48. package/dist/components/ui/Checkbox.css +0 -2
  49. package/dist/components/ui/Drawer.css +1 -1
  50. package/dist/components/ui/Modal.css +1 -1
  51. package/dist/components/ui/Select.css +17 -16
  52. package/dist/components/ui/Tooltip.css +38 -3
  53. package/dist/components/ui/Tooltip.d.ts +2 -1
  54. package/dist/components/ui/Tooltip.js +1 -2
  55. package/dist/components/ui/Tooltip.js.map +1 -1
  56. package/dist/components/ui/Tooltip.test.js +16 -0
  57. package/dist/components/ui/Tooltip.test.js.map +1 -1
  58. package/dist/helpers/configuration.js +1 -1
  59. package/dist/helpers/configuration.js.map +1 -1
  60. package/dist/helpers/translations.js +1 -2
  61. package/dist/helpers/translations.js.map +1 -1
  62. package/dist/locales/en.json +4 -0
  63. package/dist/locales/it.json +4 -0
  64. package/dist/styles.css +2 -1
  65. package/esm/components/Avatar/Avatar.css +0 -1
  66. package/esm/components/Blob/Blob.css +6 -1
  67. package/esm/components/ChangeMode/ChangeMode.css +1 -1
  68. package/esm/components/Chat/Chat.css +3 -2
  69. package/esm/components/Chat/Chat.d.ts +1 -0
  70. package/esm/components/Chat/Chat.js +2 -2
  71. package/esm/components/Chat/Chat.js.map +1 -1
  72. package/esm/components/ChatBubble/ChatBubble.css +1 -1
  73. package/esm/components/ChatBubble/ChatBubble.js +1 -1
  74. package/esm/components/ChatBubble/ChatBubble.js.map +1 -1
  75. package/esm/components/ChatInputs/ChatInputs.css +1 -41
  76. package/esm/components/ChatInputs/ChatInputs.d.ts +1 -0
  77. package/esm/components/ChatInputs/ChatInputs.js +9 -3
  78. package/esm/components/ChatInputs/ChatInputs.js.map +1 -1
  79. package/esm/components/ChatTextArea/ChatTextArea.css +7 -3
  80. package/esm/components/DateSelector/DateSelector.css +17 -15
  81. package/esm/components/ExportHistoryButton/ExportHistoryButton.css +1 -1
  82. package/esm/components/FeedbackButtons/FeedbackButtons.css +4 -4
  83. package/esm/components/FeedbackButtons/FeedbackButtons.js +1 -1
  84. package/esm/components/FeedbackButtons/FeedbackButtons.js.map +1 -1
  85. package/esm/components/Header/Header.css +2 -1
  86. package/esm/components/Header/Header.js +1 -1
  87. package/esm/components/Header/Header.js.map +1 -1
  88. package/esm/components/ImageUpload/ImageUpload.css +2 -3
  89. package/esm/components/MediaWidget/MediaItemWidget.css +2 -0
  90. package/esm/components/MediaWidget/MediaWidget.css +1 -1
  91. package/esm/components/MemoriWidget/MemoriWidget.css +1 -2
  92. package/esm/components/MemoriWidget/MemoriWidget.js +23 -13
  93. package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
  94. package/esm/components/MicrophoneButton/MicrophoneButton.css +107 -0
  95. package/esm/components/MicrophoneButton/MicrophoneButton.d.ts +9 -0
  96. package/esm/components/MicrophoneButton/MicrophoneButton.js +43 -0
  97. package/esm/components/MicrophoneButton/MicrophoneButton.js.map +1 -0
  98. package/esm/components/SettingsDrawer/SettingsDrawer.d.ts +3 -3
  99. package/esm/components/SettingsDrawer/SettingsDrawer.js +9 -7
  100. package/esm/components/SettingsDrawer/SettingsDrawer.js.map +1 -1
  101. package/esm/components/SettingsDrawer/SettingsDrawer.test.js +7 -7
  102. package/esm/components/SettingsDrawer/SettingsDrawer.test.js.map +1 -1
  103. package/esm/components/StartPanel/StartPanel.css +1 -1
  104. package/esm/components/StartPanel/StartPanel.js +1 -1
  105. package/esm/components/StartPanel/StartPanel.js.map +1 -1
  106. package/esm/components/layouts/totem.css +49 -8
  107. package/esm/components/ui/Button.css +1 -1
  108. package/esm/components/ui/Button.d.ts +5 -1
  109. package/esm/components/ui/Button.js +1 -1
  110. package/esm/components/ui/Button.js.map +1 -1
  111. package/esm/components/ui/Checkbox.css +0 -2
  112. package/esm/components/ui/Drawer.css +1 -1
  113. package/esm/components/ui/Modal.css +1 -1
  114. package/esm/components/ui/Select.css +17 -16
  115. package/esm/components/ui/Tooltip.css +38 -3
  116. package/esm/components/ui/Tooltip.d.ts +2 -1
  117. package/esm/components/ui/Tooltip.js +1 -2
  118. package/esm/components/ui/Tooltip.js.map +1 -1
  119. package/esm/components/ui/Tooltip.test.js +16 -0
  120. package/esm/components/ui/Tooltip.test.js.map +1 -1
  121. package/esm/helpers/configuration.js +1 -1
  122. package/esm/helpers/configuration.js.map +1 -1
  123. package/esm/helpers/translations.js +1 -2
  124. package/esm/helpers/translations.js.map +1 -1
  125. package/esm/locales/en.json +4 -0
  126. package/esm/locales/it.json +4 -0
  127. package/esm/styles.css +2 -1
  128. package/package.json +1 -1
  129. package/src/components/Avatar/Avatar.css +0 -1
  130. package/src/components/Blob/Blob.css +6 -1
  131. package/src/components/BlockedMemoriBadge/__snapshots__/BlockedMemoriBadge.test.tsx.snap +4 -4
  132. package/src/components/ChangeMode/ChangeMode.css +1 -1
  133. package/src/components/Chat/Chat.css +3 -2
  134. package/src/components/Chat/Chat.tsx +3 -0
  135. package/src/components/ChatBubble/ChatBubble.css +1 -1
  136. package/src/components/ChatBubble/ChatBubble.tsx +1 -1
  137. package/src/components/ChatBubble/__snapshots__/ChatBubble.test.tsx.snap +1 -1
  138. package/src/components/ChatInputs/ChatInputs.css +1 -41
  139. package/src/components/ChatInputs/ChatInputs.stories.tsx +50 -3
  140. package/src/components/ChatInputs/ChatInputs.tsx +20 -3
  141. package/src/components/ChatInputs/__snapshots__/ChatInputs.test.tsx.snap +160 -85
  142. package/src/components/ChatTextArea/ChatTextArea.css +7 -3
  143. package/src/components/DateSelector/DateSelector.css +17 -15
  144. package/src/components/ExportHistoryButton/ExportHistoryButton.css +1 -1
  145. package/src/components/FeedbackButtons/FeedbackButtons.css +4 -4
  146. package/src/components/FeedbackButtons/FeedbackButtons.tsx +1 -1
  147. package/src/components/Header/Header.css +2 -1
  148. package/src/components/Header/Header.stories.tsx +3 -0
  149. package/src/components/Header/Header.tsx +1 -1
  150. package/src/components/Header/__snapshots__/Header.test.tsx.snap +1 -1
  151. package/src/components/ImageUpload/ImageUpload.css +2 -3
  152. package/src/components/MediaWidget/MediaItemWidget.css +2 -0
  153. package/src/components/MediaWidget/MediaWidget.css +1 -1
  154. package/src/components/MemoriWidget/MemoriWidget.css +1 -2
  155. package/src/components/MemoriWidget/MemoriWidget.tsx +29 -12
  156. package/src/components/MicrophoneButton/MicrophoneButton.css +107 -0
  157. package/src/components/MicrophoneButton/MicrophoneButton.stories.tsx +49 -0
  158. package/src/components/MicrophoneButton/MicrophoneButton.tsx +95 -0
  159. package/src/components/SettingsDrawer/SettingsDrawer.stories.tsx +6 -4
  160. package/src/components/SettingsDrawer/SettingsDrawer.test.tsx +14 -14
  161. package/src/components/SettingsDrawer/SettingsDrawer.tsx +57 -25
  162. package/src/components/StartPanel/StartPanel.css +1 -1
  163. package/src/components/StartPanel/StartPanel.tsx +3 -3
  164. package/src/components/layouts/__snapshots__/FullPage.test.tsx.snap +1 -1
  165. package/src/components/layouts/__snapshots__/Totem.test.tsx.snap +1 -1
  166. package/src/components/layouts/totem.css +49 -8
  167. package/src/components/ui/Button.css +1 -1
  168. package/src/components/ui/Button.tsx +21 -1
  169. package/src/components/ui/Checkbox.css +0 -2
  170. package/src/components/ui/Drawer.css +1 -1
  171. package/src/components/ui/Modal.css +1 -1
  172. package/src/components/ui/Select.css +17 -16
  173. package/src/components/ui/Tooltip.css +38 -3
  174. package/src/components/ui/Tooltip.stories.tsx +40 -3
  175. package/src/components/ui/Tooltip.test.tsx +52 -0
  176. package/src/components/ui/Tooltip.tsx +12 -7
  177. package/src/components/ui/__snapshots__/Tooltip.test.tsx.snap +80 -4
  178. package/src/helpers/configuration.ts +1 -1
  179. package/src/helpers/translations.ts +3 -2
  180. package/src/locales/en.json +4 -0
  181. package/src/locales/it.json +4 -0
  182. package/src/styles.css +2 -1
@@ -251,18 +251,19 @@ const MemoriWidget = ({
251
251
  ?.find(c => c.memoriConfigID === memori.memoriConfigurationID)
252
252
  ?.culture?.split('-')?.[0]
253
253
  ?.toUpperCase();
254
- // eslint-disable-next-line
254
+ const integrationConfig = integration?.customData
255
+ ? JSON.parse(integration.customData)
256
+ : null;
257
+ const isMultilanguageEnabled = !!integrationConfig?.multilanguage;
255
258
  const [userLang, setUserLang] = useState(
256
- memoriLang ??
259
+ integrationConfig?.lang ??
260
+ memoriLang ??
257
261
  memori?.culture?.split('-')?.[0] ??
258
262
  language ??
263
+ integrationConfig?.uiLang ??
259
264
  i18n.language ??
260
265
  'IT'
261
266
  );
262
- const integrationConfig = integration?.customData
263
- ? JSON.parse(integration.customData)
264
- : null;
265
- const isMultilanguageEnabled = !!integrationConfig?.multilanguage;
266
267
 
267
268
  const [loading, setLoading] = useState(false);
268
269
  const [memoriTyping, setMemoriTyping] = useState(false);
@@ -275,7 +276,7 @@ const MemoriWidget = ({
275
276
  const [showPositionDrawer, setShowPositionDrawer] = useState(false);
276
277
  const [showSettingsDrawer, setShowSettingsDrawer] = useState(false);
277
278
  const [muteSpeaker, setMuteSpeaker] = useState(false);
278
- const [continuousSpeech, setContinuousSpeech] = useState(true);
279
+ const [continuousSpeech, setContinuousSpeech] = useState(false);
279
280
  const [continuousSpeechTimeout, setContinuousSpeechTimeout] = useState(2);
280
281
  const [isPlayingAudio, setIsPlayingAudio] = useState(false);
281
282
  const [controlsPosition, setControlsPosition] = useState<'center' | 'bottom'>(
@@ -289,9 +290,16 @@ const MemoriWidget = ({
289
290
 
290
291
  useEffect(() => {
291
292
  let defaultControlsPosition: 'center' | 'bottom' = 'bottom';
292
- if (window.innerWidth < 768) {
293
+ let microphoneMode = getLocalConfig<string>(
294
+ 'microphoneMode',
295
+ 'HOLD_TO_TALK'
296
+ );
297
+
298
+ if (window.innerWidth <= 768) {
293
299
  // on mobile, default position is bottom
294
300
  defaultControlsPosition = 'bottom';
301
+ // on mobile, keep only HOLD_TO_TALK mode
302
+ microphoneMode = 'HOLD_TO_TALK';
295
303
  } else if (
296
304
  window.matchMedia('(orientation: portrait)').matches ||
297
305
  window.innerHeight > window.innerWidth
@@ -304,7 +312,7 @@ const MemoriWidget = ({
304
312
  }
305
313
 
306
314
  setMuteSpeaker(getLocalConfig('muteSpeaker', false));
307
- setContinuousSpeech(getLocalConfig('continuousSpeech', true));
315
+ setContinuousSpeech(microphoneMode === 'CONTINUOUS');
308
316
  setContinuousSpeechTimeout(getLocalConfig('continuousSpeechTimeout', 2));
309
317
  setControlsPosition(
310
318
  getLocalConfig('controlsPosition', defaultControlsPosition)
@@ -944,6 +952,10 @@ const MemoriWidget = ({
944
952
  }
945
953
  }
946
954
 
955
+ if (memori.enableCompletions) {
956
+ timeout = timeout + 60;
957
+ }
958
+
947
959
  let uiTimeout = setTimeout(handleTimeout, timeout * 1000);
948
960
  setUserInteractionTimeout(uiTimeout);
949
961
  timeoutRef.current = uiTimeout;
@@ -1442,6 +1454,7 @@ const MemoriWidget = ({
1442
1454
 
1443
1455
  clearListening();
1444
1456
  setTranscript('');
1457
+ resetTranscript();
1445
1458
 
1446
1459
  try {
1447
1460
  navigator.mediaDevices
@@ -1498,6 +1511,8 @@ const MemoriWidget = ({
1498
1511
  recognizer.sessionStopped = (_s, _e) => {
1499
1512
  stopListening();
1500
1513
  };
1514
+
1515
+ resetTranscript();
1501
1516
  recognizer.startContinuousRecognitionAsync();
1502
1517
  })
1503
1518
  .catch(console.error);
@@ -1595,7 +1610,8 @@ const MemoriWidget = ({
1595
1610
  'sendOnEnter',
1596
1611
  'keypress'
1597
1612
  );
1598
- setSendOnEnter(stored);
1613
+ if (window.innerWidth <= 768) setSendOnEnter('click');
1614
+ else setSendOnEnter(stored);
1599
1615
  }, []);
1600
1616
  useEffect(() => {
1601
1617
  setLocalConfig('sendOnEnter', sendOnEnter);
@@ -2217,6 +2233,7 @@ const MemoriWidget = ({
2217
2233
  selectReceiverTag={selectReceiverTag}
2218
2234
  preview={preview}
2219
2235
  sendOnEnter={sendOnEnter}
2236
+ microphoneMode={continuousSpeech ? 'CONTINUOUS' : 'HOLD_TO_TALK'}
2220
2237
  setSendOnEnter={setSendOnEnter}
2221
2238
  attachmentsMenuOpen={attachmentsMenuOpen}
2222
2239
  setAttachmentsMenuOpen={setAttachmentsMenuOpen}
@@ -2405,9 +2422,9 @@ const MemoriWidget = ({
2405
2422
  layout={selectedLayout}
2406
2423
  open={!!showSettingsDrawer}
2407
2424
  onClose={() => setShowSettingsDrawer(false)}
2408
- continuousSpeech={continuousSpeech}
2425
+ microphoneMode={continuousSpeech ? 'CONTINUOUS' : 'HOLD_TO_TALK'}
2409
2426
  continuousSpeechTimeout={continuousSpeechTimeout}
2410
- setContinuousSpeech={setContinuousSpeech}
2427
+ setMicrophoneMode={mode => setContinuousSpeech(mode === 'CONTINUOUS')}
2411
2428
  setContinuousSpeechTimeout={setContinuousSpeechTimeout}
2412
2429
  controlsPosition={controlsPosition}
2413
2430
  setControlsPosition={setControlsPosition}
@@ -0,0 +1,107 @@
1
+ .memori-mic-btn-tooltip.memori-tooltip.memori-tooltip--align-topLeft:not(
2
+ .memori-tooltip--disabled
3
+ ).memori-tooltip--visible
4
+ .memori-tooltip--content,
5
+ .memori-mic-btn-tooltip.memori-tooltip.memori-tooltip--align-topLeft:not(.memori-tooltip--disabled):not(
6
+ .memori-tooltip--visible
7
+ ):hover
8
+ .memori-tooltip--content {
9
+ touch-action: none;
10
+ transform: translateY(-180%) translateX(27%);
11
+ -webkit-user-select: none;
12
+ user-select: none;
13
+ }
14
+
15
+ .memori-chat-inputs--mic {
16
+ z-index: 1;
17
+ margin-left: 0.33rem;
18
+ touch-action: none;
19
+ -webkit-user-select: none;
20
+ user-select: none;
21
+ }
22
+
23
+ .memori-chat-inputs--mic svg {
24
+ color: var(--memori-primary-text, #fff);
25
+ font-size: 1em;
26
+ touch-action: none;
27
+ -webkit-user-select: none;
28
+ user-select: none;
29
+ }
30
+
31
+ .memori-chat-inputs--mic:hover,
32
+ .memori-chat-inputs--mic:active,
33
+ .memori-chat-inputs--mic:focus {
34
+ border-color: var(--memori-primary) !important;
35
+ color: var(--memori-primary-text, #fff);
36
+ }
37
+
38
+ .memori-chat-inputs--mic:active,
39
+ .memori-chat-inputs--mic:focus {
40
+ box-shadow: 0 0.2rem 0.33rem var(--memori-primary) !important;
41
+ }
42
+
43
+ .memori-chat-inputs--mic:not(.memori-chat-inputs--mic--listening):active,
44
+ .memori-chat-inputs--mic:not(.memori-chat-inputs--mic--listening):focus {
45
+ color: var(--memori-primary) !important;
46
+ }
47
+
48
+ @keyframes micBtnActivePulse {
49
+ 0% {
50
+ transform: scale(1.25);
51
+ }
52
+
53
+ 25% {
54
+ transform: scale(1.4);
55
+ }
56
+
57
+ 50% {
58
+ transform: scale(1.3);
59
+ }
60
+
61
+ 75% {
62
+ transform: scale(1.4);
63
+ }
64
+
65
+ 100% {
66
+ transform: scale(1.2);
67
+ }
68
+ }
69
+
70
+ .memori-chat-inputs--mic.memori-chat-inputs--mic--listening {
71
+ position: relative;
72
+ color: red !important;
73
+ transform: scale(1.5);
74
+ }
75
+
76
+ .memori-chat-inputs--mic.memori-chat-inputs--mic--listening::before,
77
+ .memori-chat-inputs--mic.memori-chat-inputs--mic--listening::after {
78
+ position: absolute;
79
+ z-index: -1;
80
+ top: 0;
81
+ left: 0;
82
+ width: 100%;
83
+ height: 100%;
84
+ border-radius: 50%;
85
+ animation: micBtnActivePulse 2s infinite;
86
+ background: var(--memori-primary);
87
+ content: '';
88
+ opacity: 0.2;
89
+ }
90
+
91
+ .memori-chat-inputs--mic.memori-chat-inputs--mic--listening::after {
92
+ top: 5%;
93
+ left: 5%;
94
+ width: 90%;
95
+ height: 90%;
96
+ animation-delay: 0.3s;
97
+ opacity: 0.3;
98
+ }
99
+
100
+ .memori-chat-inputs--mic.memori-chat-inputs--mic--listening:active,
101
+ .memori-chat-inputs--mic.memori-chat-inputs--mic--listening:focus {
102
+ border-color: red !important;
103
+ }
104
+
105
+ .memori-chat-inputs--mic.memori-chat-inputs--mic--listening svg {
106
+ color: red !important;
107
+ }
@@ -0,0 +1,49 @@
1
+ import React from 'react';
2
+ import { Meta, Story } from '@storybook/react';
3
+ import MicrophoneButton, { Props } from './MicrophoneButton';
4
+
5
+ import './MicrophoneButton.css';
6
+
7
+ const meta: Meta = {
8
+ title: 'Widget/Microphone Button',
9
+ component: MicrophoneButton,
10
+ argTypes: {
11
+ disabled: {
12
+ control: {
13
+ type: 'boolean',
14
+ },
15
+ },
16
+ },
17
+ parameters: {
18
+ controls: { expanded: true },
19
+ },
20
+ };
21
+
22
+ export default meta;
23
+
24
+ const Template: Story<Props> = args => {
25
+ const [listening, setListening] = React.useState(args.listening);
26
+ const startListening = () => setListening(true);
27
+ const stopListening = () => setListening(false);
28
+
29
+ return (
30
+ <div style={{ paddingTop: '10rem', textAlign: 'right' }}>
31
+ <MicrophoneButton
32
+ {...args}
33
+ listening={listening}
34
+ startListening={startListening}
35
+ stopListening={stopListening}
36
+ />
37
+ </div>
38
+ );
39
+ };
40
+
41
+ // By passing using the Args format for exported stories, you can control the props for a component for reuse in a test
42
+ // https://storybook.js.org/docs/react/workflows/unit-testing
43
+ export const Default = Template.bind({});
44
+ Default.args = {
45
+ listening: false,
46
+ stopAudio: () => {},
47
+ startListening: () => {},
48
+ stopListening: () => {},
49
+ };
@@ -0,0 +1,95 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { Props as ChatInputProps } from '../ChatInputs/ChatInputs';
3
+ import Microphone from '../icons/Microphone';
4
+ import Button from '../ui/Button';
5
+ import Tooltip from '../ui/Tooltip';
6
+ import cx from 'classnames';
7
+ import { useTranslation } from 'react-i18next';
8
+
9
+ export interface Props {
10
+ listening?: ChatInputProps['listening'];
11
+ stopAudio: ChatInputProps['stopAudio'];
12
+ startListening: ChatInputProps['startListening'];
13
+ stopListening: ChatInputProps['stopListening'];
14
+ }
15
+
16
+ const MicrophoneButton = ({
17
+ listening,
18
+ stopAudio,
19
+ startListening,
20
+ stopListening,
21
+ }: Props) => {
22
+ const { t } = useTranslation();
23
+ const [micBtnTooltip, setMicBtnTooltip] = useState<string | undefined>();
24
+
25
+ const intervalRef = useRef<any>(null);
26
+
27
+ const startHold = (
28
+ e:
29
+ | React.TouchEvent<HTMLButtonElement>
30
+ | React.MouseEvent<Element, MouseEvent>
31
+ ) => {
32
+ e.preventDefault();
33
+
34
+ setMicBtnTooltip(t('write_and_speak.holdToSpeak') || 'Hold to record');
35
+
36
+ if (intervalRef.current) return;
37
+ intervalRef.current = setTimeout(() => {
38
+ stopAudio();
39
+ setMicBtnTooltip(
40
+ t('write_and_speak.releaseToEndListening') || 'Release to end listening'
41
+ );
42
+ startListening();
43
+ }, 300);
44
+ };
45
+
46
+ const stopHold = () => {
47
+ if (intervalRef.current) {
48
+ clearTimeout(intervalRef.current);
49
+ intervalRef.current = null;
50
+ }
51
+
52
+ stopListening();
53
+ setMicBtnTooltip(undefined);
54
+ };
55
+
56
+ useEffect(() => {
57
+ return () => stopHold();
58
+ }, []);
59
+
60
+ return (
61
+ <Tooltip
62
+ visible={!!micBtnTooltip}
63
+ content={
64
+ <span>
65
+ {micBtnTooltip ||
66
+ t('write_and_speak.pressAndHoldToSpeak') ||
67
+ 'Press and hold to speak'}
68
+ </span>
69
+ }
70
+ align="topLeft"
71
+ className="memori-mic-btn-tooltip"
72
+ >
73
+ <Button
74
+ primary
75
+ className={cx('memori-chat-inputs--mic', {
76
+ 'memori-chat-inputs--mic--listening': listening,
77
+ })}
78
+ title={
79
+ listening
80
+ ? t('write_and_speak.micButtonPopoverListening') || 'Listening'
81
+ : t('write_and_speak.micButtonPopover') || 'Start listening'
82
+ }
83
+ onMouseDown={startHold}
84
+ onTouchStart={startHold}
85
+ onMouseUp={stopHold}
86
+ onMouseLeave={stopHold}
87
+ onTouchEnd={stopHold}
88
+ shape="circle"
89
+ icon={<Microphone />}
90
+ />
91
+ </Tooltip>
92
+ );
93
+ };
94
+
95
+ export default MicrophoneButton;
@@ -21,15 +21,17 @@ const meta: Meta = {
21
21
  export default meta;
22
22
 
23
23
  const Template: Story<Props> = args => {
24
- const [continuousSpeech, setContinuousSpeech] = React.useState(false);
24
+ const [microphoneMode, setMicrophoneMode] = React.useState<
25
+ 'HOLD_TO_TALK' | 'CONTINUOUS'
26
+ >('HOLD_TO_TALK');
25
27
  const [controlsPosition, setControlsPosition] = React.useState<
26
- 'bottom' | 'center' | 'hidden'
28
+ 'bottom' | 'center'
27
29
  >('bottom');
28
30
  return (
29
31
  <SettingsDrawer
30
32
  {...args}
31
- continuousSpeech={continuousSpeech}
32
- setContinuousSpeech={setContinuousSpeech}
33
+ microphoneMode={microphoneMode}
34
+ setMicrophoneMode={setMicrophoneMode}
33
35
  controlsPosition={controlsPosition}
34
36
  setControlsPosition={setControlsPosition}
35
37
  />
@@ -16,9 +16,9 @@ it('renders SettingsDrawer unchanged', () => {
16
16
  <SettingsDrawer
17
17
  open={false}
18
18
  onClose={jest.fn()}
19
- continuousSpeech={false}
19
+ microphoneMode="HOLD_TO_TALK"
20
+ setMicrophoneMode={jest.fn()}
20
21
  continuousSpeechTimeout={2}
21
- setContinuousSpeech={jest.fn()}
22
22
  setContinuousSpeechTimeout={jest.fn()}
23
23
  controlsPosition="bottom"
24
24
  setControlsPosition={jest.fn()}
@@ -34,9 +34,9 @@ it('renders SettingsDrawer open unchanged', () => {
34
34
  <SettingsDrawer
35
35
  open={true}
36
36
  onClose={jest.fn()}
37
- continuousSpeech={false}
37
+ microphoneMode="HOLD_TO_TALK"
38
+ setMicrophoneMode={jest.fn()}
38
39
  continuousSpeechTimeout={2}
39
- setContinuousSpeech={jest.fn()}
40
40
  setContinuousSpeechTimeout={jest.fn()}
41
41
  controlsPosition="bottom"
42
42
  setControlsPosition={jest.fn()}
@@ -52,9 +52,9 @@ it('renders SettingsDrawer open with continuous speech enabled unchanged', () =>
52
52
  <SettingsDrawer
53
53
  open={true}
54
54
  onClose={jest.fn()}
55
- continuousSpeech={true}
55
+ microphoneMode="CONTINUOUS"
56
+ setMicrophoneMode={jest.fn()}
56
57
  continuousSpeechTimeout={2}
57
- setContinuousSpeech={jest.fn()}
58
58
  setContinuousSpeechTimeout={jest.fn()}
59
59
  controlsPosition="bottom"
60
60
  setControlsPosition={jest.fn()}
@@ -70,9 +70,9 @@ it('renders SettingsDrawer open with non-default continuous speech timeout uncha
70
70
  <SettingsDrawer
71
71
  open={true}
72
72
  onClose={jest.fn()}
73
- continuousSpeech={false}
73
+ microphoneMode="CONTINUOUS"
74
+ setMicrophoneMode={jest.fn()}
74
75
  continuousSpeechTimeout={10}
75
- setContinuousSpeech={jest.fn()}
76
76
  setContinuousSpeechTimeout={jest.fn()}
77
77
  controlsPosition="bottom"
78
78
  setControlsPosition={jest.fn()}
@@ -89,9 +89,9 @@ it('renders SettingsDrawer for totem layout open unchanged', () => {
89
89
  layout="TOTEM"
90
90
  open={true}
91
91
  onClose={jest.fn()}
92
- continuousSpeech={false}
92
+ microphoneMode="HOLD_TO_TALK"
93
+ setMicrophoneMode={jest.fn()}
93
94
  continuousSpeechTimeout={2}
94
- setContinuousSpeech={jest.fn()}
95
95
  setContinuousSpeechTimeout={jest.fn()}
96
96
  controlsPosition="bottom"
97
97
  setControlsPosition={jest.fn()}
@@ -108,9 +108,9 @@ it('renders SettingsDrawer for totem layout open with controls at center unchang
108
108
  layout="TOTEM"
109
109
  open={true}
110
110
  onClose={jest.fn()}
111
- continuousSpeech={false}
111
+ microphoneMode="HOLD_TO_TALK"
112
+ setMicrophoneMode={jest.fn()}
112
113
  continuousSpeechTimeout={2}
113
- setContinuousSpeech={jest.fn()}
114
114
  setContinuousSpeechTimeout={jest.fn()}
115
115
  controlsPosition="center"
116
116
  setControlsPosition={jest.fn()}
@@ -127,9 +127,9 @@ it('renders SettingsDrawer for totem layout with continuous speech and hide emis
127
127
  layout="TOTEM"
128
128
  open={true}
129
129
  onClose={jest.fn()}
130
- continuousSpeech={true}
130
+ microphoneMode="HOLD_TO_TALK"
131
+ setMicrophoneMode={jest.fn()}
131
132
  continuousSpeechTimeout={2}
132
- setContinuousSpeech={jest.fn()}
133
133
  setContinuousSpeechTimeout={jest.fn()}
134
134
  controlsPosition="bottom"
135
135
  setControlsPosition={jest.fn()}
@@ -11,9 +11,9 @@ export interface Props {
11
11
  open: boolean;
12
12
  layout?: 'FULLPAGE' | 'TOTEM' | 'DEFAULT';
13
13
  onClose: () => void;
14
- continuousSpeech?: boolean;
14
+ microphoneMode?: 'HOLD_TO_TALK' | 'CONTINUOUS';
15
15
  continuousSpeechTimeout?: number;
16
- setContinuousSpeech: (value: boolean) => void;
16
+ setMicrophoneMode: (value: 'HOLD_TO_TALK' | 'CONTINUOUS') => void;
17
17
  setContinuousSpeechTimeout: (value: number) => void;
18
18
  controlsPosition?: 'center' | 'bottom';
19
19
  setControlsPosition: (value: 'center' | 'bottom') => void;
@@ -27,9 +27,9 @@ const SettingsDrawer = ({
27
27
  open,
28
28
  layout = 'DEFAULT',
29
29
  onClose,
30
- continuousSpeech,
30
+ microphoneMode = 'HOLD_TO_TALK',
31
31
  continuousSpeechTimeout,
32
- setContinuousSpeech,
32
+ setMicrophoneMode,
33
33
  setContinuousSpeechTimeout,
34
34
  controlsPosition,
35
35
  setControlsPosition,
@@ -46,31 +46,63 @@ const SettingsDrawer = ({
46
46
  title={t('widget.settings') || 'Settings'}
47
47
  description={t('write_and_speak.settingsHeaderLabel')}
48
48
  >
49
- <div className="memori-settings-drawer--field">
50
- <Checkbox
51
- label={t('write_and_speak.continuousSpeechLabel')}
52
- name="continuousSpeech"
53
- checked={continuousSpeech}
54
- onChange={e => {
55
- setContinuousSpeech(e.target.checked);
56
- setLocalConfig('continuousSpeech', e.target.checked);
57
- }}
58
- />
59
- </div>
60
-
61
- <div className="memori-settings-drawer--field">
62
- <Select
63
- label={t('write_and_speak.secondsLabel') || 'Seconds'}
64
- placeholder={t('write_and_speak.secondsLabel') || 'Seconds'}
65
- options={silenceSeconds.map(s => ({ value: s, label: s }))}
66
- value={continuousSpeechTimeout}
49
+ <div className="memori-settings-drawer--field controls">
50
+ <label htmlFor="#microphoneMode">
51
+ {t('write_and_speak.microphoneMode') || 'Microphone mode'}:
52
+ </label>
53
+ <RadioGroup
54
+ id="microphoneMode"
55
+ name="microphoneMode"
56
+ value={microphoneMode}
57
+ defaultValue={microphoneMode}
58
+ className="memori-settings-drawer--microphoneMode-radio"
67
59
  onChange={value => {
68
- setContinuousSpeechTimeout(value);
69
- setLocalConfig('continuousSpeechTimeout', value);
60
+ let micMode =
61
+ value === 'CONTINUOUS' ? 'CONTINUOUS' : 'HOLD_TO_TALK';
62
+
63
+ setMicrophoneMode(micMode as 'CONTINUOUS' | 'HOLD_TO_TALK');
64
+ setLocalConfig('microphoneMode', micMode);
70
65
  }}
71
- />
66
+ >
67
+ <RadioGroup.Option
68
+ value="HOLD_TO_TALK"
69
+ className="memori-settings-drawer--microphoneMode-radio-button"
70
+ >
71
+ {({ checked }) => (
72
+ <Button primary={checked}>
73
+ {t('write_and_speak.holdToSpeak') || 'Hold to speak'}
74
+ </Button>
75
+ )}
76
+ </RadioGroup.Option>
77
+ <RadioGroup.Option
78
+ value="CONTINUOUS"
79
+ className="memori-settings-drawer--microphoneMode-radio-button"
80
+ >
81
+ {({ checked }) => (
82
+ <Button primary={checked}>
83
+ {t('write_and_speak.continuousSpeechLabel') ||
84
+ 'Continuous speech'}
85
+ </Button>
86
+ )}
87
+ </RadioGroup.Option>
88
+ </RadioGroup>
72
89
  </div>
73
90
 
91
+ {microphoneMode === 'CONTINUOUS' && (
92
+ <div className="memori-settings-drawer--field">
93
+ <Select
94
+ label={t('write_and_speak.secondsLabel') || 'Seconds'}
95
+ placeholder={t('write_and_speak.secondsLabel') || 'Seconds'}
96
+ options={silenceSeconds.map(s => ({ value: s, label: s }))}
97
+ value={continuousSpeechTimeout}
98
+ onChange={value => {
99
+ setContinuousSpeechTimeout(value);
100
+ setLocalConfig('continuousSpeechTimeout', value);
101
+ }}
102
+ />
103
+ </div>
104
+ )}
105
+
74
106
  {layout === 'TOTEM' && (
75
107
  <>
76
108
  <div className="memori-settings-drawer--field controls">
@@ -23,6 +23,7 @@
23
23
  padding: var(--memori-inner-content-pad, 0);
24
24
  border-radius: 10px;
25
25
  margin-top: 2rem;
26
+ -webkit-backdrop-filter: blur(10px);
26
27
  backdrop-filter: blur(10px);
27
28
  background-color: var(--memori-inner-bg, transparent);
28
29
  color: var(--memori-text-color, rgba(0, 0, 0, 0.85)) !important;
@@ -68,7 +69,6 @@
68
69
  box-shadow: 0 0 5px rgba(50, 50, 50, 0.3);
69
70
  }
70
71
 
71
-
72
72
  .memori--avatar img {
73
73
  width: 100%;
74
74
  height: 100%;
@@ -102,7 +102,7 @@ const StartPanel: React.FC<Props> = ({
102
102
  {!!gamificationLevel?.badge?.length && (
103
103
  <div className="memori--gamification-badge">
104
104
  <Tooltip
105
- alignLeft
105
+ align="left"
106
106
  content={`${t('gamification.level')} ${
107
107
  gamificationLevel.badge
108
108
  }, ${gamificationLevel.points} ${t('gamification.points')}`}
@@ -119,7 +119,7 @@ const StartPanel: React.FC<Props> = ({
119
119
  )}
120
120
  {!!memori.enableCompletions && (
121
121
  <div className="memori--completions-enabled">
122
- <Tooltip alignLeft content={t('completionsEnabled')}>
122
+ <Tooltip align="left" content={t('completionsEnabled')}>
123
123
  <span aria-label={t('completionsEnabled') || 'Completions'}>
124
124
  <AI />
125
125
  </span>
@@ -128,7 +128,7 @@ const StartPanel: React.FC<Props> = ({
128
128
  )}
129
129
  {!!memori.nsfw && (
130
130
  <div className="memori--nsfw">
131
- <Tooltip alignLeft content={t('nsfw')}>
131
+ <Tooltip align="left" content={t('nsfw')}>
132
132
  <span title={t('nsfw') || 'NSFW'}>🔞</span>
133
133
  </Tooltip>
134
134
  </div>
@@ -71,7 +71,7 @@ exports[`renders FullPage layout unchanged 1`] = `
71
71
  </button>
72
72
  </div>
73
73
  <button
74
- class="memori-button memori-button--primary memori-button--circle memori-button--padded memori-button--icon-only memori-header--button"
74
+ class="memori-button memori-button--primary memori-button--circle memori-button--padded memori-button--icon-only memori-header--button memori-header--button-settings"
75
75
  title="widget.settings"
76
76
  >
77
77
  <span
@@ -124,7 +124,7 @@ exports[`renders Totem layout unchanged 1`] = `
124
124
  </button>
125
125
  </div>
126
126
  <button
127
- class="memori-button memori-button--primary memori-button--circle memori-button--padded memori-button--icon-only memori-header--button"
127
+ class="memori-button memori-button--primary memori-button--circle memori-button--padded memori-button--icon-only memori-header--button memori-header--button-settings"
128
128
  title="widget.settings"
129
129
  >
130
130
  <span