@memori.ai/memori-react 2.0.11 → 2.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.
- package/CHANGELOG.md +29 -0
- package/README.md +74 -26
- package/dist/components/Chat/Chat.d.ts +1 -0
- package/dist/components/Chat/Chat.js +2 -2
- package/dist/components/Chat/Chat.js.map +1 -1
- package/dist/components/ChatBubble/ChatBubble.js +1 -1
- package/dist/components/ChatBubble/ChatBubble.js.map +1 -1
- package/dist/components/ChatInputs/ChatInputs.css +1 -41
- package/dist/components/ChatInputs/ChatInputs.d.ts +1 -0
- package/dist/components/ChatInputs/ChatInputs.js +9 -3
- package/dist/components/ChatInputs/ChatInputs.js.map +1 -1
- package/dist/components/FeedbackButtons/FeedbackButtons.js +1 -1
- package/dist/components/FeedbackButtons/FeedbackButtons.js.map +1 -1
- package/dist/components/MediaWidget/MediaItemWidget.js +1 -12
- package/dist/components/MediaWidget/MediaItemWidget.js.map +1 -1
- package/dist/components/MemoriWidget/MemoriWidget.d.ts +17 -1
- package/dist/components/MemoriWidget/MemoriWidget.js +31 -19
- package/dist/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/dist/components/MicrophoneButton/MicrophoneButton.css +101 -0
- package/dist/components/MicrophoneButton/MicrophoneButton.d.ts +9 -0
- package/dist/components/MicrophoneButton/MicrophoneButton.js +46 -0
- package/dist/components/MicrophoneButton/MicrophoneButton.js.map +1 -0
- package/dist/components/SettingsDrawer/SettingsDrawer.d.ts +3 -3
- package/dist/components/SettingsDrawer/SettingsDrawer.js +8 -6
- package/dist/components/SettingsDrawer/SettingsDrawer.js.map +1 -1
- package/dist/components/SettingsDrawer/SettingsDrawer.test.js +7 -7
- package/dist/components/SettingsDrawer/SettingsDrawer.test.js.map +1 -1
- package/dist/components/StartPanel/StartPanel.js +1 -1
- package/dist/components/StartPanel/StartPanel.js.map +1 -1
- package/dist/components/layouts/FullPage.d.ts +2 -15
- package/dist/components/layouts/FullPage.js.map +1 -1
- package/dist/components/layouts/Totem.d.ts +2 -15
- package/dist/components/layouts/Totem.js.map +1 -1
- package/dist/components/ui/Button.d.ts +5 -1
- package/dist/components/ui/Button.js +1 -1
- package/dist/components/ui/Button.js.map +1 -1
- package/dist/components/ui/Tooltip.css +33 -2
- package/dist/components/ui/Tooltip.d.ts +2 -1
- package/dist/components/ui/Tooltip.js +1 -2
- package/dist/components/ui/Tooltip.js.map +1 -1
- package/dist/components/ui/Tooltip.test.js +16 -0
- package/dist/components/ui/Tooltip.test.js.map +1 -1
- package/dist/helpers/configuration.js +1 -1
- package/dist/helpers/configuration.js.map +1 -1
- package/dist/helpers/media.js +25 -19
- package/dist/helpers/media.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/locales/en.json +4 -0
- package/dist/locales/it.json +4 -0
- package/dist/styles.css +3 -2
- package/esm/components/Chat/Chat.d.ts +1 -0
- package/esm/components/Chat/Chat.js +2 -2
- package/esm/components/Chat/Chat.js.map +1 -1
- package/esm/components/ChatBubble/ChatBubble.js +1 -1
- package/esm/components/ChatBubble/ChatBubble.js.map +1 -1
- package/esm/components/ChatInputs/ChatInputs.css +1 -41
- package/esm/components/ChatInputs/ChatInputs.d.ts +1 -0
- package/esm/components/ChatInputs/ChatInputs.js +9 -3
- package/esm/components/ChatInputs/ChatInputs.js.map +1 -1
- package/esm/components/FeedbackButtons/FeedbackButtons.js +1 -1
- package/esm/components/FeedbackButtons/FeedbackButtons.js.map +1 -1
- package/esm/components/MediaWidget/MediaItemWidget.js +1 -12
- package/esm/components/MediaWidget/MediaItemWidget.js.map +1 -1
- package/esm/components/MemoriWidget/MemoriWidget.d.ts +17 -1
- package/esm/components/MemoriWidget/MemoriWidget.js +31 -19
- package/esm/components/MemoriWidget/MemoriWidget.js.map +1 -1
- package/esm/components/MicrophoneButton/MicrophoneButton.css +101 -0
- package/esm/components/MicrophoneButton/MicrophoneButton.d.ts +9 -0
- package/esm/components/MicrophoneButton/MicrophoneButton.js +43 -0
- package/esm/components/MicrophoneButton/MicrophoneButton.js.map +1 -0
- package/esm/components/SettingsDrawer/SettingsDrawer.d.ts +3 -3
- package/esm/components/SettingsDrawer/SettingsDrawer.js +9 -7
- package/esm/components/SettingsDrawer/SettingsDrawer.js.map +1 -1
- package/esm/components/SettingsDrawer/SettingsDrawer.test.js +7 -7
- package/esm/components/SettingsDrawer/SettingsDrawer.test.js.map +1 -1
- package/esm/components/StartPanel/StartPanel.js +1 -1
- package/esm/components/StartPanel/StartPanel.js.map +1 -1
- package/esm/components/layouts/FullPage.d.ts +2 -15
- package/esm/components/layouts/FullPage.js.map +1 -1
- package/esm/components/layouts/Totem.d.ts +2 -15
- package/esm/components/layouts/Totem.js.map +1 -1
- package/esm/components/ui/Button.d.ts +5 -1
- package/esm/components/ui/Button.js +1 -1
- package/esm/components/ui/Button.js.map +1 -1
- package/esm/components/ui/Tooltip.css +33 -2
- package/esm/components/ui/Tooltip.d.ts +2 -1
- package/esm/components/ui/Tooltip.js +1 -2
- package/esm/components/ui/Tooltip.js.map +1 -1
- package/esm/components/ui/Tooltip.test.js +16 -0
- package/esm/components/ui/Tooltip.test.js.map +1 -1
- package/esm/helpers/configuration.js +1 -1
- package/esm/helpers/configuration.js.map +1 -1
- package/esm/helpers/media.js +25 -19
- package/esm/helpers/media.js.map +1 -1
- package/esm/index.d.ts +1 -0
- package/esm/index.js +2 -2
- package/esm/index.js.map +1 -1
- package/esm/locales/en.json +4 -0
- package/esm/locales/it.json +4 -0
- package/esm/styles.css +3 -2
- package/package.json +1 -1
- package/src/components/BlockedMemoriBadge/__snapshots__/BlockedMemoriBadge.test.tsx.snap +4 -4
- package/src/components/Chat/Chat.tsx +3 -0
- package/src/components/Chat/__snapshots__/Chat.test.tsx.snap +6 -6
- package/src/components/ChatBubble/ChatBubble.tsx +1 -1
- package/src/components/ChatBubble/__snapshots__/ChatBubble.test.tsx.snap +1 -1
- package/src/components/ChatInputs/ChatInputs.css +1 -41
- package/src/components/ChatInputs/ChatInputs.stories.tsx +50 -3
- package/src/components/ChatInputs/ChatInputs.tsx +20 -3
- package/src/components/ChatInputs/__snapshots__/ChatInputs.test.tsx.snap +160 -85
- package/src/components/FeedbackButtons/FeedbackButtons.tsx +1 -1
- package/src/components/Header/Header.stories.tsx +3 -0
- package/src/components/MediaWidget/MediaItemWidget.tsx +1 -12
- package/src/components/MediaWidget/__snapshots__/MediaItemWidget.test.tsx.snap +6 -6
- package/src/components/MediaWidget/__snapshots__/MediaWidget.test.tsx.snap +1 -1
- package/src/components/MemoriWidget/MemoriWidget.tsx +53 -18
- package/src/components/MicrophoneButton/MicrophoneButton.css +101 -0
- package/src/components/MicrophoneButton/MicrophoneButton.stories.tsx +49 -0
- package/src/components/MicrophoneButton/MicrophoneButton.tsx +95 -0
- package/src/components/SettingsDrawer/SettingsDrawer.stories.tsx +6 -4
- package/src/components/SettingsDrawer/SettingsDrawer.test.tsx +14 -14
- package/src/components/SettingsDrawer/SettingsDrawer.tsx +57 -25
- package/src/components/StartPanel/StartPanel.tsx +3 -3
- package/src/components/layouts/FullPage.tsx +2 -16
- package/src/components/layouts/Totem.tsx +2 -16
- package/src/components/layouts/layouts.stories.tsx +41 -1
- package/src/components/ui/Button.tsx +21 -1
- package/src/components/ui/Tooltip.css +33 -2
- package/src/components/ui/Tooltip.stories.tsx +40 -3
- package/src/components/ui/Tooltip.test.tsx +52 -0
- package/src/components/ui/Tooltip.tsx +12 -7
- package/src/components/ui/__snapshots__/Tooltip.test.tsx.snap +80 -4
- package/src/helpers/configuration.ts +1 -1
- package/src/helpers/media.ts +29 -23
- package/src/index.tsx +3 -0
- package/src/locales/en.json +4 -0
- package/src/locales/it.json +4 -0
- package/src/styles.css +3 -2
|
@@ -145,12 +145,28 @@ let speechSynthesizer: SpeechSynthesizer | null;
|
|
|
145
145
|
let audioDestination: SpeakerAudioDestination;
|
|
146
146
|
let audioContext: IAudioContext;
|
|
147
147
|
|
|
148
|
+
export interface LayoutProps {
|
|
149
|
+
header?: JSX.Element | null;
|
|
150
|
+
avatar: JSX.Element;
|
|
151
|
+
chat?: JSX.Element | null;
|
|
152
|
+
startPanel: JSX.Element;
|
|
153
|
+
integrationStyle?: JSX.Element | null;
|
|
154
|
+
integrationBackground?: JSX.Element | null;
|
|
155
|
+
changeMode?: JSX.Element | null;
|
|
156
|
+
poweredBy?: JSX.Element | null;
|
|
157
|
+
sessionId?: string;
|
|
158
|
+
hasUserActivatedSpeak?: boolean;
|
|
159
|
+
showInstruct?: boolean;
|
|
160
|
+
loading?: boolean;
|
|
161
|
+
}
|
|
162
|
+
|
|
148
163
|
export interface Props {
|
|
149
164
|
memori: Memori;
|
|
150
165
|
memoriConfigs?: MemoriConfig[];
|
|
151
166
|
memoriLang?: string;
|
|
152
167
|
integration?: Integration;
|
|
153
168
|
layout?: 'DEFAULT' | 'FULLPAGE' | 'TOTEM';
|
|
169
|
+
customLayout?: React.FC<LayoutProps>;
|
|
154
170
|
showShare?: boolean;
|
|
155
171
|
showInstruct?: boolean;
|
|
156
172
|
showInputs?: boolean;
|
|
@@ -184,6 +200,7 @@ const MemoriWidget = ({
|
|
|
184
200
|
memoriLang,
|
|
185
201
|
integration,
|
|
186
202
|
layout = 'DEFAULT',
|
|
203
|
+
customLayout,
|
|
187
204
|
showInstruct = false,
|
|
188
205
|
showShare = true,
|
|
189
206
|
preview = false,
|
|
@@ -234,18 +251,19 @@ const MemoriWidget = ({
|
|
|
234
251
|
?.find(c => c.memoriConfigID === memori.memoriConfigurationID)
|
|
235
252
|
?.culture?.split('-')?.[0]
|
|
236
253
|
?.toUpperCase();
|
|
237
|
-
|
|
254
|
+
const integrationConfig = integration?.customData
|
|
255
|
+
? JSON.parse(integration.customData)
|
|
256
|
+
: null;
|
|
257
|
+
const isMultilanguageEnabled = !!integrationConfig?.multilanguage;
|
|
238
258
|
const [userLang, setUserLang] = useState(
|
|
239
|
-
|
|
259
|
+
integrationConfig?.lang ??
|
|
260
|
+
memoriLang ??
|
|
240
261
|
memori?.culture?.split('-')?.[0] ??
|
|
241
262
|
language ??
|
|
263
|
+
integrationConfig?.uiLang ??
|
|
242
264
|
i18n.language ??
|
|
243
265
|
'IT'
|
|
244
266
|
);
|
|
245
|
-
const integrationConfig = integration?.customData
|
|
246
|
-
? JSON.parse(integration.customData)
|
|
247
|
-
: null;
|
|
248
|
-
const isMultilanguageEnabled = !!integrationConfig?.multilanguage;
|
|
249
267
|
|
|
250
268
|
const [loading, setLoading] = useState(false);
|
|
251
269
|
const [memoriTyping, setMemoriTyping] = useState(false);
|
|
@@ -258,7 +276,7 @@ const MemoriWidget = ({
|
|
|
258
276
|
const [showPositionDrawer, setShowPositionDrawer] = useState(false);
|
|
259
277
|
const [showSettingsDrawer, setShowSettingsDrawer] = useState(false);
|
|
260
278
|
const [muteSpeaker, setMuteSpeaker] = useState(false);
|
|
261
|
-
const [continuousSpeech, setContinuousSpeech] = useState(
|
|
279
|
+
const [continuousSpeech, setContinuousSpeech] = useState(false);
|
|
262
280
|
const [continuousSpeechTimeout, setContinuousSpeechTimeout] = useState(2);
|
|
263
281
|
const [isPlayingAudio, setIsPlayingAudio] = useState(false);
|
|
264
282
|
const [controlsPosition, setControlsPosition] = useState<'center' | 'bottom'>(
|
|
@@ -272,9 +290,16 @@ const MemoriWidget = ({
|
|
|
272
290
|
|
|
273
291
|
useEffect(() => {
|
|
274
292
|
let defaultControlsPosition: 'center' | 'bottom' = 'bottom';
|
|
275
|
-
|
|
293
|
+
let microphoneMode = getLocalConfig<string>(
|
|
294
|
+
'microphoneMode',
|
|
295
|
+
'HOLD_TO_TALK'
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
if (window.innerWidth <= 768) {
|
|
276
299
|
// on mobile, default position is bottom
|
|
277
300
|
defaultControlsPosition = 'bottom';
|
|
301
|
+
// on mobile, keep only HOLD_TO_TALK mode
|
|
302
|
+
microphoneMode = 'HOLD_TO_TALK';
|
|
278
303
|
} else if (
|
|
279
304
|
window.matchMedia('(orientation: portrait)').matches ||
|
|
280
305
|
window.innerHeight > window.innerWidth
|
|
@@ -287,7 +312,7 @@ const MemoriWidget = ({
|
|
|
287
312
|
}
|
|
288
313
|
|
|
289
314
|
setMuteSpeaker(getLocalConfig('muteSpeaker', false));
|
|
290
|
-
setContinuousSpeech(
|
|
315
|
+
setContinuousSpeech(microphoneMode === 'CONTINUOUS');
|
|
291
316
|
setContinuousSpeechTimeout(getLocalConfig('continuousSpeechTimeout', 2));
|
|
292
317
|
setControlsPosition(
|
|
293
318
|
getLocalConfig('controlsPosition', defaultControlsPosition)
|
|
@@ -927,6 +952,10 @@ const MemoriWidget = ({
|
|
|
927
952
|
}
|
|
928
953
|
}
|
|
929
954
|
|
|
955
|
+
if (memori.enableCompletions) {
|
|
956
|
+
timeout = timeout + 60;
|
|
957
|
+
}
|
|
958
|
+
|
|
930
959
|
let uiTimeout = setTimeout(handleTimeout, timeout * 1000);
|
|
931
960
|
setUserInteractionTimeout(uiTimeout);
|
|
932
961
|
timeoutRef.current = uiTimeout;
|
|
@@ -1425,6 +1454,7 @@ const MemoriWidget = ({
|
|
|
1425
1454
|
|
|
1426
1455
|
clearListening();
|
|
1427
1456
|
setTranscript('');
|
|
1457
|
+
resetTranscript();
|
|
1428
1458
|
|
|
1429
1459
|
try {
|
|
1430
1460
|
navigator.mediaDevices
|
|
@@ -1481,6 +1511,8 @@ const MemoriWidget = ({
|
|
|
1481
1511
|
recognizer.sessionStopped = (_s, _e) => {
|
|
1482
1512
|
stopListening();
|
|
1483
1513
|
};
|
|
1514
|
+
|
|
1515
|
+
resetTranscript();
|
|
1484
1516
|
recognizer.startContinuousRecognitionAsync();
|
|
1485
1517
|
})
|
|
1486
1518
|
.catch(console.error);
|
|
@@ -1578,7 +1610,8 @@ const MemoriWidget = ({
|
|
|
1578
1610
|
'sendOnEnter',
|
|
1579
1611
|
'keypress'
|
|
1580
1612
|
);
|
|
1581
|
-
setSendOnEnter(
|
|
1613
|
+
if (window.innerWidth <= 768) setSendOnEnter('click');
|
|
1614
|
+
else setSendOnEnter(stored);
|
|
1582
1615
|
}, []);
|
|
1583
1616
|
useEffect(() => {
|
|
1584
1617
|
setLocalConfig('sendOnEnter', sendOnEnter);
|
|
@@ -2200,6 +2233,7 @@ const MemoriWidget = ({
|
|
|
2200
2233
|
selectReceiverTag={selectReceiverTag}
|
|
2201
2234
|
preview={preview}
|
|
2202
2235
|
sendOnEnter={sendOnEnter}
|
|
2236
|
+
microphoneMode={continuousSpeech ? 'CONTINUOUS' : 'HOLD_TO_TALK'}
|
|
2203
2237
|
setSendOnEnter={setSendOnEnter}
|
|
2204
2238
|
attachmentsMenuOpen={attachmentsMenuOpen}
|
|
2205
2239
|
setAttachmentsMenuOpen={setAttachmentsMenuOpen}
|
|
@@ -2255,12 +2289,13 @@ const MemoriWidget = ({
|
|
|
2255
2289
|
|
|
2256
2290
|
const poweredBy = <PoweredBy tenant={tenant} userLang={userLang} />;
|
|
2257
2291
|
|
|
2258
|
-
const Layout =
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2292
|
+
const Layout = customLayout
|
|
2293
|
+
? customLayout
|
|
2294
|
+
: selectedLayout === 'TOTEM'
|
|
2295
|
+
? TotemLayout
|
|
2296
|
+
: selectedLayout === 'FULLPAGE'
|
|
2297
|
+
? FullPageLayout
|
|
2298
|
+
: FullPageLayout;
|
|
2264
2299
|
|
|
2265
2300
|
return (
|
|
2266
2301
|
<div
|
|
@@ -2387,9 +2422,9 @@ const MemoriWidget = ({
|
|
|
2387
2422
|
layout={selectedLayout}
|
|
2388
2423
|
open={!!showSettingsDrawer}
|
|
2389
2424
|
onClose={() => setShowSettingsDrawer(false)}
|
|
2390
|
-
|
|
2425
|
+
microphoneMode={continuousSpeech ? 'CONTINUOUS' : 'HOLD_TO_TALK'}
|
|
2391
2426
|
continuousSpeechTimeout={continuousSpeechTimeout}
|
|
2392
|
-
|
|
2427
|
+
setMicrophoneMode={mode => setContinuousSpeech(mode === 'CONTINUOUS')}
|
|
2393
2428
|
setContinuousSpeechTimeout={setContinuousSpeechTimeout}
|
|
2394
2429
|
controlsPosition={controlsPosition}
|
|
2395
2430
|
setControlsPosition={setControlsPosition}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
.memori-mic-btn-tooltip.memori-tooltip.memori-tooltip--align-topLeft:not(.memori-tooltip--disabled).memori-tooltip--visible .memori-tooltip--content,
|
|
2
|
+
.memori-mic-btn-tooltip.memori-tooltip.memori-tooltip--align-topLeft:not(.memori-tooltip--disabled):not(.memori-tooltip--visible):hover .memori-tooltip--content {
|
|
3
|
+
touch-action: none;
|
|
4
|
+
transform: translateY(-180%) translateX(27%);
|
|
5
|
+
-webkit-user-select: none;
|
|
6
|
+
user-select: none;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.memori-chat-inputs--mic {
|
|
10
|
+
z-index: 1;
|
|
11
|
+
margin-left: 0.33rem;
|
|
12
|
+
touch-action: none;
|
|
13
|
+
-webkit-user-select: none;
|
|
14
|
+
user-select: none;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.memori-chat-inputs--mic svg {
|
|
18
|
+
color: var(--memori-primary-text, #fff);
|
|
19
|
+
font-size: 1em;
|
|
20
|
+
touch-action: none;
|
|
21
|
+
-webkit-user-select: none;
|
|
22
|
+
user-select: none;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.memori-chat-inputs--mic:hover,
|
|
26
|
+
.memori-chat-inputs--mic:active,
|
|
27
|
+
.memori-chat-inputs--mic:focus {
|
|
28
|
+
border-color: var(--memori-primary) !important;
|
|
29
|
+
color: var(--memori-primary-text, #fff);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.memori-chat-inputs--mic:active,
|
|
33
|
+
.memori-chat-inputs--mic:focus {
|
|
34
|
+
box-shadow: 0 0.2rem 0.33rem var(--memori-primary) !important;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.memori-chat-inputs--mic:not(.memori-chat-inputs--mic--listening):active,
|
|
38
|
+
.memori-chat-inputs--mic:not(.memori-chat-inputs--mic--listening):focus {
|
|
39
|
+
color: var(--memori-primary) !important;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@keyframes micBtnActivePulse {
|
|
43
|
+
0% {
|
|
44
|
+
transform: scale(1.25);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
25% {
|
|
48
|
+
transform: scale(1.4);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
50% {
|
|
52
|
+
transform: scale(1.3);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
75% {
|
|
56
|
+
transform: scale(1.4);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
100% {
|
|
60
|
+
transform: scale(1.2);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.memori-chat-inputs--mic.memori-chat-inputs--mic--listening {
|
|
65
|
+
position: relative;
|
|
66
|
+
color: red !important;
|
|
67
|
+
transform: scale(1.5);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.memori-chat-inputs--mic.memori-chat-inputs--mic--listening::before,
|
|
71
|
+
.memori-chat-inputs--mic.memori-chat-inputs--mic--listening::after {
|
|
72
|
+
position: absolute;
|
|
73
|
+
z-index: -1;
|
|
74
|
+
top: 0;
|
|
75
|
+
left: 0;
|
|
76
|
+
width: 100%;
|
|
77
|
+
height: 100%;
|
|
78
|
+
border-radius: 50%;
|
|
79
|
+
animation: micBtnActivePulse 2s infinite;
|
|
80
|
+
background: var(--memori-primary);
|
|
81
|
+
content: "";
|
|
82
|
+
opacity: 0.2;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.memori-chat-inputs--mic.memori-chat-inputs--mic--listening::after {
|
|
86
|
+
top: 5%;
|
|
87
|
+
left: 5%;
|
|
88
|
+
width: 90%;
|
|
89
|
+
height: 90%;
|
|
90
|
+
animation-delay: 0.3s;
|
|
91
|
+
opacity: 0.3;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.memori-chat-inputs--mic.memori-chat-inputs--mic--listening:active,
|
|
95
|
+
.memori-chat-inputs--mic.memori-chat-inputs--mic--listening:focus {
|
|
96
|
+
border-color: red !important;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.memori-chat-inputs--mic.memori-chat-inputs--mic--listening svg {
|
|
100
|
+
color: red !important;
|
|
101
|
+
}
|
|
@@ -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 [
|
|
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'
|
|
28
|
+
'bottom' | 'center'
|
|
27
29
|
>('bottom');
|
|
28
30
|
return (
|
|
29
31
|
<SettingsDrawer
|
|
30
32
|
{...args}
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
14
|
+
microphoneMode?: 'HOLD_TO_TALK' | 'CONTINUOUS';
|
|
15
15
|
continuousSpeechTimeout?: number;
|
|
16
|
-
|
|
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
|
-
|
|
30
|
+
microphoneMode = 'HOLD_TO_TALK',
|
|
31
31
|
continuousSpeechTimeout,
|
|
32
|
-
|
|
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
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
69
|
-
|
|
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">
|
|
@@ -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
|
-
|
|
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
|
|
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
|
|
131
|
+
<Tooltip align="left" content={t('nsfw')}>
|
|
132
132
|
<span title={t('nsfw') || 'NSFW'}>🔞</span>
|
|
133
133
|
</Tooltip>
|
|
134
134
|
</div>
|
|
@@ -1,22 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import Spin from '../ui/Spin';
|
|
3
|
+
import { LayoutProps } from '../MemoriWidget/MemoriWidget';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
header?: JSX.Element | null;
|
|
6
|
-
avatar: JSX.Element;
|
|
7
|
-
chat?: JSX.Element | null;
|
|
8
|
-
startPanel: JSX.Element;
|
|
9
|
-
integrationStyle?: JSX.Element | null;
|
|
10
|
-
integrationBackground?: JSX.Element | null;
|
|
11
|
-
changeMode?: JSX.Element | null;
|
|
12
|
-
poweredBy?: JSX.Element | null;
|
|
13
|
-
sessionId?: string;
|
|
14
|
-
hasUserActivatedSpeak?: boolean;
|
|
15
|
-
showInstruct?: boolean;
|
|
16
|
-
loading?: boolean;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const FullPageLayout: React.FC<Props> = ({
|
|
5
|
+
const FullPageLayout: React.FC<LayoutProps> = ({
|
|
20
6
|
header,
|
|
21
7
|
avatar,
|
|
22
8
|
chat,
|