@athoscommerce/snap-preact 1.2.3-beta.2 → 1.2.3-beta.4
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/dist/cjs/components/src/components/Molecules/ChatInspirationResultMessage/ChatInspirationResultMessage.d.ts.map +1 -1
- package/dist/cjs/components/src/components/Molecules/ChatInspirationResultMessage/ChatInspirationResultMessage.js +4 -1
- package/dist/cjs/components/src/components/Molecules/ChatProductComparisonMessage/ChatProductComparisonMessage.js +1 -1
- package/dist/cjs/components/src/components/Molecules/ChatProductQueryMessage/ChatProductQueryMessage.d.ts.map +1 -1
- package/dist/cjs/components/src/components/Molecules/ChatProductQueryMessage/ChatProductQueryMessage.js +8 -2
- package/dist/cjs/components/src/components/Organisms/Chat/Chat.d.ts +1 -0
- package/dist/cjs/components/src/components/Organisms/Chat/Chat.d.ts.map +1 -1
- package/dist/cjs/components/src/components/Organisms/Chat/Chat.js +238 -53
- package/dist/cjs/components/src/components/Organisms/Chat/ChatResult.d.ts +2 -3
- package/dist/cjs/components/src/components/Organisms/Chat/ChatResult.d.ts.map +1 -1
- package/dist/cjs/components/src/components/Organisms/Chat/ChatResult.js +26 -7
- package/dist/cjs/components/src/components/Organisms/Chat/MessageUser.d.ts.map +1 -1
- package/dist/cjs/components/src/components/Organisms/Chat/MessageUser.js +54 -23
- package/dist/cjs/components/src/components/Organisms/Chat/ResultsDisplay.d.ts.map +1 -1
- package/dist/cjs/components/src/components/Organisms/Chat/ResultsDisplay.js +14 -6
- package/dist/cjs/components/src/components/Organisms/Facet/Facet.js +2 -1
- package/dist/cjs/components/src/components/Organisms/FacetsHorizontal/FacetsHorizontal.js +2 -1
- package/dist/esm/components/src/components/Molecules/ChatInspirationResultMessage/ChatInspirationResultMessage.d.ts.map +1 -1
- package/dist/esm/components/src/components/Molecules/ChatInspirationResultMessage/ChatInspirationResultMessage.js +4 -1
- package/dist/esm/components/src/components/Molecules/ChatProductComparisonMessage/ChatProductComparisonMessage.js +1 -1
- package/dist/esm/components/src/components/Molecules/ChatProductQueryMessage/ChatProductQueryMessage.d.ts.map +1 -1
- package/dist/esm/components/src/components/Molecules/ChatProductQueryMessage/ChatProductQueryMessage.js +8 -2
- package/dist/esm/components/src/components/Organisms/Chat/Chat.d.ts +1 -0
- package/dist/esm/components/src/components/Organisms/Chat/Chat.d.ts.map +1 -1
- package/dist/esm/components/src/components/Organisms/Chat/Chat.js +231 -49
- package/dist/esm/components/src/components/Organisms/Chat/ChatResult.d.ts +2 -3
- package/dist/esm/components/src/components/Organisms/Chat/ChatResult.d.ts.map +1 -1
- package/dist/esm/components/src/components/Organisms/Chat/ChatResult.js +26 -7
- package/dist/esm/components/src/components/Organisms/Chat/MessageUser.d.ts.map +1 -1
- package/dist/esm/components/src/components/Organisms/Chat/MessageUser.js +51 -21
- package/dist/esm/components/src/components/Organisms/Chat/ResultsDisplay.d.ts.map +1 -1
- package/dist/esm/components/src/components/Organisms/Chat/ResultsDisplay.js +14 -6
- package/dist/esm/components/src/components/Organisms/Facet/Facet.js +1 -1
- package/dist/esm/components/src/components/Organisms/FacetsHorizontal/FacetsHorizontal.js +1 -1
- package/package.json +12 -12
|
@@ -170,11 +170,11 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
|
|
|
170
170
|
padding: '12px 16px',
|
|
171
171
|
fontSize: '14px',
|
|
172
172
|
color: '#333',
|
|
173
|
-
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.
|
|
173
|
+
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.2)',
|
|
174
174
|
cursor: 'pointer',
|
|
175
175
|
lineHeight: 1.4,
|
|
176
176
|
'&:hover': {
|
|
177
|
-
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.
|
|
177
|
+
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.2)',
|
|
178
178
|
},
|
|
179
179
|
},
|
|
180
180
|
},
|
|
@@ -220,6 +220,7 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
|
|
|
220
220
|
display: 'flex',
|
|
221
221
|
'.ss__chat__header__title__logo': {
|
|
222
222
|
height: '44px',
|
|
223
|
+
width: 'auto',
|
|
223
224
|
},
|
|
224
225
|
'.ss__chat__header__title__text': {
|
|
225
226
|
display: 'flex',
|
|
@@ -390,7 +391,19 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
|
|
|
390
391
|
borderBottomRightRadius: mobile ? 0 : '12px',
|
|
391
392
|
borderBottomLeftRadius: mobile ? 0 : '12px',
|
|
392
393
|
flexGrow: 1,
|
|
394
|
+
position: 'relative',
|
|
393
395
|
'.ss__chat__content__header': {
|
|
396
|
+
// In overlay mode (only applied when messages overflow), the comparisons
|
|
397
|
+
// tray floats over the messages so toggling it doesn't shift the layout
|
|
398
|
+
// or push the latest message out of view. When messages don't overflow,
|
|
399
|
+
// the tray stays in flow so older messages remain reachable above it.
|
|
400
|
+
'&.ss__chat__content__header--overlay': {
|
|
401
|
+
position: 'absolute',
|
|
402
|
+
top: 0,
|
|
403
|
+
left: 0,
|
|
404
|
+
right: 0,
|
|
405
|
+
zIndex: 20,
|
|
406
|
+
},
|
|
394
407
|
// '.ss__chat__attachments': {
|
|
395
408
|
// '.ss__chat__attachment': {
|
|
396
409
|
// borderRadius: 0,
|
|
@@ -435,9 +448,9 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
|
|
|
435
448
|
alignItems: 'stretch',
|
|
436
449
|
'.ss__chat__content__header__comparisons__content__items': {
|
|
437
450
|
display: 'flex',
|
|
438
|
-
gap: '1em',
|
|
439
451
|
flex: '1 1 calc(100% - 100px)',
|
|
440
452
|
justifyContent: 'space-between',
|
|
453
|
+
gap: '0.5em',
|
|
441
454
|
},
|
|
442
455
|
'.ss__chat__content__header__comparisons__content__comparison': {
|
|
443
456
|
background: '#fff',
|
|
@@ -446,7 +459,7 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
|
|
|
446
459
|
display: 'flex',
|
|
447
460
|
flexDirection: 'column',
|
|
448
461
|
gap: '0.5em',
|
|
449
|
-
flex: '0 1
|
|
462
|
+
flex: '0 1 25%',
|
|
450
463
|
position: 'relative',
|
|
451
464
|
'&.ss__chat__content__header__comparisons__content__comparison--placeholder': {
|
|
452
465
|
border: '2px dashed #ddd',
|
|
@@ -511,6 +524,32 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
|
|
|
511
524
|
'.ss__chat__messages__end': {
|
|
512
525
|
height: '1px',
|
|
513
526
|
},
|
|
527
|
+
'.ss__chat__new-messages': {
|
|
528
|
+
position: 'sticky',
|
|
529
|
+
bottom: '10px',
|
|
530
|
+
display: 'flex',
|
|
531
|
+
alignItems: 'center',
|
|
532
|
+
justifyContent: 'center',
|
|
533
|
+
gap: '6px',
|
|
534
|
+
margin: '0 auto',
|
|
535
|
+
width: 'fit-content',
|
|
536
|
+
padding: '6px 16px',
|
|
537
|
+
background: colorPrimary,
|
|
538
|
+
color: '#fff',
|
|
539
|
+
borderRadius: '999px',
|
|
540
|
+
fontSize: '13px',
|
|
541
|
+
fontWeight: 500,
|
|
542
|
+
cursor: 'pointer',
|
|
543
|
+
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)',
|
|
544
|
+
zIndex: 5,
|
|
545
|
+
'&:hover': {
|
|
546
|
+
background: '#1a2a5c',
|
|
547
|
+
},
|
|
548
|
+
svg: {
|
|
549
|
+
fill: '#fff',
|
|
550
|
+
stroke: '#fff',
|
|
551
|
+
},
|
|
552
|
+
},
|
|
514
553
|
'.ss__chat__message': {
|
|
515
554
|
marginBottom: '30px',
|
|
516
555
|
ul: {
|
|
@@ -537,7 +576,7 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
|
|
|
537
576
|
'.ss__chat__welcome': {
|
|
538
577
|
display: 'flex',
|
|
539
578
|
flexDirection: 'column',
|
|
540
|
-
|
|
579
|
+
minHeight: '100%',
|
|
541
580
|
'.ss__chat__welcome__message': {
|
|
542
581
|
fontSize: '15px',
|
|
543
582
|
lineHeight: 1.5,
|
|
@@ -675,13 +714,19 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
|
|
|
675
714
|
display: 'flex',
|
|
676
715
|
justifyContent: 'flex-end',
|
|
677
716
|
padding: '0 2px',
|
|
717
|
+
// reserve the slot so toggling the button's visibility doesn't reflow the header
|
|
718
|
+
visibility: 'hidden',
|
|
719
|
+
'&.ss__chat__actions__facets-apply--active': {
|
|
720
|
+
visibility: 'visible',
|
|
721
|
+
},
|
|
678
722
|
'.ss__button': {
|
|
679
723
|
background: '#253B80',
|
|
680
724
|
color: '#fff',
|
|
681
725
|
border: 'none',
|
|
682
726
|
borderRadius: '999px',
|
|
683
|
-
padding: '
|
|
684
|
-
fontSize: '
|
|
727
|
+
padding: '2px 10px',
|
|
728
|
+
fontSize: '12px',
|
|
729
|
+
lineHeight: '1.4',
|
|
685
730
|
cursor: 'pointer',
|
|
686
731
|
'&:not(.ss__button--disabled):hover': {
|
|
687
732
|
background: '#1a2a5c',
|
|
@@ -926,6 +971,7 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
|
|
|
926
971
|
padding: '0.5em 0',
|
|
927
972
|
margin: '0 0 0 1em',
|
|
928
973
|
flex: '1 0 auto',
|
|
974
|
+
border: 'none',
|
|
929
975
|
'&::placeholder': {
|
|
930
976
|
color: '#999',
|
|
931
977
|
opacity: 0.7,
|
|
@@ -983,9 +1029,10 @@ export const Chat = observer((properties) => {
|
|
|
983
1029
|
title: 'Athos Conversational Assistant',
|
|
984
1030
|
subtitle: 'Ready to assist you',
|
|
985
1031
|
multiselectFacets: true,
|
|
1032
|
+
disableBubbleSuggestedQuestions: false,
|
|
986
1033
|
};
|
|
987
1034
|
let props = mergeProps('facets', globalTheme, defaultProps, properties);
|
|
988
|
-
const { className, internalClassName, controller, logo, title, subtitle, offset, multiselectFacets } = props;
|
|
1035
|
+
const { className, internalClassName, controller, logo, title, subtitle, offset, multiselectFacets, disableBubbleSuggestedQuestions } = props;
|
|
989
1036
|
const { store } = controller;
|
|
990
1037
|
const themeDefaults = {
|
|
991
1038
|
components: {},
|
|
@@ -999,6 +1046,7 @@ export const Chat = observer((properties) => {
|
|
|
999
1046
|
const fileInputRef = useRef(null);
|
|
1000
1047
|
const messagesEndRef = useRef(null);
|
|
1001
1048
|
const messagesContainerRef = useRef(null);
|
|
1049
|
+
const headerRef = useRef(null);
|
|
1002
1050
|
const footerRef = useRef(null);
|
|
1003
1051
|
// Swipe-to-dismiss state for mobile secondary panel
|
|
1004
1052
|
const secondaryRef = useRef(null);
|
|
@@ -1010,22 +1058,124 @@ export const Chat = observer((properties) => {
|
|
|
1010
1058
|
const [swipeAnimating, setSwipeAnimating] = useState(false);
|
|
1011
1059
|
const [mobileProductInfoOpen, setMobileProductInfoOpen] = useState(false);
|
|
1012
1060
|
const [footerHeight, setFooterHeight] = useState(0);
|
|
1061
|
+
const [showNewMessages, setShowNewMessages] = useState(false);
|
|
1062
|
+
const [messagesOverflow, setMessagesOverflow] = useState(false);
|
|
1063
|
+
const isNearBottomRef = useRef(true);
|
|
1013
1064
|
const updateSwipeOffset = (value) => {
|
|
1014
1065
|
swipeOffsetRef.current = value;
|
|
1015
1066
|
setSwipeOffsetRaw(value);
|
|
1016
1067
|
};
|
|
1017
1068
|
const scrollToBottom = () => {
|
|
1018
|
-
|
|
1069
|
+
// Scroll the messages container directly (instead of scrollIntoView on the
|
|
1070
|
+
// bottom anchor) so the browser never walks up the ancestor chain. On small
|
|
1071
|
+
// viewports a smooth scrollIntoView could otherwise animate the document/
|
|
1072
|
+
// visual viewport and visually push the fixed-position chat off-screen.
|
|
1073
|
+
const container = messagesContainerRef.current;
|
|
1074
|
+
if (container) {
|
|
1075
|
+
container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' });
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
// Conditional scroll: only scrolls if the user is already near the bottom.
|
|
1079
|
+
// Passed to child components (e.g. image onLoad) so they don't yank the user
|
|
1080
|
+
// back to the bottom when they have scrolled up to read older messages.
|
|
1081
|
+
const scrollToBottomIfNear = () => {
|
|
1082
|
+
if (isNearBottomRef.current) {
|
|
1083
|
+
scrollToBottom();
|
|
1084
|
+
}
|
|
1019
1085
|
};
|
|
1020
|
-
//
|
|
1086
|
+
// productQuery messages are filtered out of the main chat render (they only drive the side-chat panel),
|
|
1087
|
+
// so don't auto-scroll when one is added — otherwise clicking a product in the side chat scrolls the main thread.
|
|
1088
|
+
const visibleChatLength = (store.currentChat?.chat || []).filter((m) => m.messageType !== 'productQuery').length;
|
|
1089
|
+
// Scroll to bottom when chat opens
|
|
1021
1090
|
useEffect(() => {
|
|
1022
|
-
// if (store.open && messagesContainerRef.current) {
|
|
1023
|
-
// messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight;
|
|
1024
|
-
// }
|
|
1025
1091
|
if (store.open) {
|
|
1026
1092
|
scrollToBottom();
|
|
1093
|
+
setShowNewMessages(false);
|
|
1094
|
+
isNearBottomRef.current = true;
|
|
1027
1095
|
}
|
|
1028
|
-
}, [store.
|
|
1096
|
+
}, [store.open]);
|
|
1097
|
+
// Handle new messages — auto-scroll for user messages, show popup for response messages when scrolled up
|
|
1098
|
+
useEffect(() => {
|
|
1099
|
+
if (!store.open)
|
|
1100
|
+
return;
|
|
1101
|
+
const chat = store.currentChat?.chat || [];
|
|
1102
|
+
const visibleChat = chat.filter((m) => m.messageType !== 'productQuery');
|
|
1103
|
+
const lastMessage = visibleChat[visibleChat.length - 1];
|
|
1104
|
+
if (!lastMessage)
|
|
1105
|
+
return;
|
|
1106
|
+
if (lastMessage.messageType === 'user') {
|
|
1107
|
+
scrollToBottom();
|
|
1108
|
+
setShowNewMessages(false);
|
|
1109
|
+
}
|
|
1110
|
+
else {
|
|
1111
|
+
if (isNearBottomRef.current) {
|
|
1112
|
+
scrollToBottom();
|
|
1113
|
+
setShowNewMessages(false);
|
|
1114
|
+
}
|
|
1115
|
+
else {
|
|
1116
|
+
setShowNewMessages(true);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
}, [visibleChatLength]);
|
|
1120
|
+
// Scroll to show loading indicator when user sends a message
|
|
1121
|
+
useEffect(() => {
|
|
1122
|
+
if (store.open && store.loading) {
|
|
1123
|
+
scrollToBottom();
|
|
1124
|
+
}
|
|
1125
|
+
}, [store.loading]);
|
|
1126
|
+
// Track scroll position to dismiss the "New messages" popup when user scrolls to bottom
|
|
1127
|
+
useEffect(() => {
|
|
1128
|
+
const container = messagesContainerRef.current;
|
|
1129
|
+
if (!container)
|
|
1130
|
+
return;
|
|
1131
|
+
const handleScroll = () => {
|
|
1132
|
+
const threshold = 100;
|
|
1133
|
+
const isNearBottom = container.scrollHeight - container.scrollTop - container.clientHeight < threshold;
|
|
1134
|
+
isNearBottomRef.current = isNearBottom;
|
|
1135
|
+
if (isNearBottom) {
|
|
1136
|
+
setShowNewMessages(false);
|
|
1137
|
+
}
|
|
1138
|
+
};
|
|
1139
|
+
container.addEventListener('scroll', handleScroll);
|
|
1140
|
+
return () => container.removeEventListener('scroll', handleScroll);
|
|
1141
|
+
}, [store.open]);
|
|
1142
|
+
// Track whether the messages container is overflowing so the comparisons header
|
|
1143
|
+
// can switch between in-flow (short conversations — top content stays reachable)
|
|
1144
|
+
// and overlay (overflowing — avoid layout shift when toggling the tray).
|
|
1145
|
+
// Hysteresis: when already in overlay, only return to in-flow if the content
|
|
1146
|
+
// would still fit *with* the header re-inserted — otherwise the two states
|
|
1147
|
+
// fight each other and the layout flickers.
|
|
1148
|
+
useEffect(() => {
|
|
1149
|
+
const container = messagesContainerRef.current;
|
|
1150
|
+
if (!container || typeof ResizeObserver === 'undefined')
|
|
1151
|
+
return;
|
|
1152
|
+
const update = () => {
|
|
1153
|
+
setMessagesOverflow((prev) => {
|
|
1154
|
+
const headerHeight = headerRef.current?.offsetHeight || 0;
|
|
1155
|
+
if (prev) {
|
|
1156
|
+
// Currently overlay: header isn't taking flow space. Only switch
|
|
1157
|
+
// back to in-flow if the content would fit with the header added.
|
|
1158
|
+
return container.scrollHeight > container.clientHeight - headerHeight + 1;
|
|
1159
|
+
}
|
|
1160
|
+
// Currently in-flow: header already takes space — straightforward check.
|
|
1161
|
+
return container.scrollHeight > container.clientHeight + 1;
|
|
1162
|
+
});
|
|
1163
|
+
};
|
|
1164
|
+
update();
|
|
1165
|
+
const observer = new ResizeObserver(update);
|
|
1166
|
+
observer.observe(container);
|
|
1167
|
+
if (headerRef.current)
|
|
1168
|
+
observer.observe(headerRef.current);
|
|
1169
|
+
return () => observer.disconnect();
|
|
1170
|
+
}, [store.open, visibleChatLength]);
|
|
1171
|
+
// Re-focus the chat input on desktop after a search completes.
|
|
1172
|
+
// The input is disabled while loading, which causes the browser to drop focus.
|
|
1173
|
+
// On mobile we intentionally skip this so the virtual keyboard dismisses.
|
|
1174
|
+
useEffect(() => {
|
|
1175
|
+
if (!store.loading && !isMobile && store.open) {
|
|
1176
|
+
controller.focusInput();
|
|
1177
|
+
}
|
|
1178
|
+
}, [store.loading]);
|
|
1029
1179
|
// const subProps: ChatSubProps = {};
|
|
1030
1180
|
const activeMessage = store.currentChat?.activeMessage;
|
|
1031
1181
|
// On mobile, only auto-open the secondary panel for the first productComparison
|
|
@@ -1090,6 +1240,26 @@ export const Chat = observer((properties) => {
|
|
|
1090
1240
|
return text.length > 50 ? `${text.slice(0, 50)}...` : text;
|
|
1091
1241
|
})() }), _jsx("div", { className: "ss__chat__history__chat__button__date", children: chat.createdAt.toLocaleString() })] }) }, chat.id))) })) : null] }));
|
|
1092
1242
|
const FacetButton = (props) => _jsx(Button, { icon: props.open ? 'angle-down' : 'angle-up', children: props.label });
|
|
1243
|
+
const getFacetButtonLabel = (facet) => {
|
|
1244
|
+
const applied = new Set();
|
|
1245
|
+
// Count values marked as filtered (already applied on the server)
|
|
1246
|
+
(facet.values || []).forEach((value) => {
|
|
1247
|
+
if (value?.filtered) {
|
|
1248
|
+
if ('low' in value && 'high' in value) {
|
|
1249
|
+
applied.add(`${value.low ?? '*'}:${value.high ?? '*'}`);
|
|
1250
|
+
}
|
|
1251
|
+
else {
|
|
1252
|
+
applied.add(value.value);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
1256
|
+
// Count pending facet attachments (newly selected, not yet sent)
|
|
1257
|
+
(controller.store.currentChat?.attachments.items || [])
|
|
1258
|
+
.filter((item) => item.type === 'facet' && item.key === facet.field && (item.state === 'active' || item.state === 'attached'))
|
|
1259
|
+
.forEach((item) => applied.add(item.value));
|
|
1260
|
+
const base = facet.label || facet.field;
|
|
1261
|
+
return applied.size > 0 ? `${base} (${applied.size})` : base;
|
|
1262
|
+
};
|
|
1093
1263
|
// TODO: if starting a new chat and it's expired, this button would then disappear
|
|
1094
1264
|
if (!controller.store.chatEnabled) {
|
|
1095
1265
|
return _jsx(_Fragment, {});
|
|
@@ -1178,7 +1348,7 @@ export const Chat = observer((properties) => {
|
|
|
1178
1348
|
'ss__chat--open': store.open,
|
|
1179
1349
|
'ss__chat--minimized': !store.open,
|
|
1180
1350
|
'ss__chat--mobile': isMobile,
|
|
1181
|
-
}, className, internalClassName), ...styling, children: [!store.open && !store.currentChat && store.suggestedQuestions?.length > 0 && (_jsx("div", { className: "ss__chat__suggested-questions", children: store.suggestedQuestions.map((question, index) => (_jsx("div", { className: "ss__chat__suggested-questions__item", onClick: () => {
|
|
1351
|
+
}, className, internalClassName), ...styling, children: [!disableBubbleSuggestedQuestions && !store.open && !store.currentChat && store.suggestedQuestions?.length > 0 && (_jsx("div", { className: "ss__chat__suggested-questions", children: store.suggestedQuestions.map((question, index) => (_jsx("div", { className: "ss__chat__suggested-questions__item", onClick: () => {
|
|
1182
1352
|
controller.openChat(question);
|
|
1183
1353
|
}, children: question }, index))) })), _jsx("div", { className: 'ss__chat__bubble', onClick: () => controller.handlers.button.click(), children: _jsx(Icon, { icon: "chat", title: "Open Chat" }) }), store.open && shouldShowSideChat && activeMessage ? (_jsxs("div", { ref: secondaryRef, className: classnames('ss__chat__secondary'), style: isMobile
|
|
1184
1354
|
? {
|
|
@@ -1192,8 +1362,7 @@ export const Chat = observer((properties) => {
|
|
|
1192
1362
|
productQuery: 'Product Information',
|
|
1193
1363
|
}[activeMessage.messageType] || null }), _jsx("div", { className: "ss__chat__header__title__secondary", children: {
|
|
1194
1364
|
inspirationResult: 'Choose a style direction to explore',
|
|
1195
|
-
productComparison: `Comparing ${activeMessage?.
|
|
1196
|
-
activeMessage?.searchResults?.length} products`,
|
|
1365
|
+
productComparison: `Comparing ${activeMessage?.searchResults?.length || ''} products`,
|
|
1197
1366
|
productQuery: 'Complete product details',
|
|
1198
1367
|
}[activeMessage.messageType] || null })] }), _jsx("div", { className: "ss__chat__header__buttons", children: _jsx(Button, { className: "ss__chat__header__button--close", icon: { icon: 'close2', title: 'Close Chat' }, onClick: () => {
|
|
1199
1368
|
// clear any product attachments tied to the side chat (discuss product flow)
|
|
@@ -1228,7 +1397,9 @@ export const Chat = observer((properties) => {
|
|
|
1228
1397
|
store.currentChat.save();
|
|
1229
1398
|
}
|
|
1230
1399
|
}, children: _jsx(Icon, { icon: "close-thin", size: "14px" }) }))] })) : null;
|
|
1231
|
-
})(), _jsxs("div", { className: "ss__chat__content", children: [_jsx("div", {
|
|
1400
|
+
})(), _jsxs("div", { className: "ss__chat__content", children: [_jsx("div", { ref: headerRef, className: classnames('ss__chat__content__header', {
|
|
1401
|
+
'ss__chat__content__header--overlay': messagesOverflow,
|
|
1402
|
+
}), children: store.currentChat?.comparisons.compared && store.currentChat.comparisons.compared.length > 0 && (_jsxs("div", { className: 'ss__chat__content__header__comparisons', children: [_jsxs("div", { className: 'ss__chat__content__header__comparisons__header', children: [_jsxs("div", { className: 'ss__chat__content__header__comparisons__header__title', children: [_jsx(Icon, { className: 'ss__chat__content__header__comparisons__header__title__icon', icon: 'clipboard' }), _jsxs("span", { className: 'ss__chat__content__header__comparisons__header__title__text', children: ["Compare Products (", store.currentChat?.comparisons.compared.length, "/", store.currentChat?.comparisons.maxItems, ")"] })] }), _jsx("div", { className: 'ss__chat__content__header__comparisons__header__actions', children: _jsx(Button, { onClick: () => store.currentChat?.comparisons.reset(), children: "clear" }) })] }), _jsxs("div", { className: 'ss__chat__content__header__comparisons__content', children: [_jsx("div", { className: 'ss__chat__content__header__comparisons__content__items', children: Array.from({ length: store.currentChat?.comparisons.maxItems }).map((_, index) => {
|
|
1232
1403
|
const comparisonItem = store.currentChat?.comparisons.compared[index];
|
|
1233
1404
|
return (_jsx("div", { className: classnames('ss__chat__content__header__comparisons__content__comparison', {
|
|
1234
1405
|
'ss__chat__content__header__comparisons__content__comparison--placeholder': !comparisonItem,
|
|
@@ -1245,37 +1416,43 @@ export const Chat = observer((properties) => {
|
|
|
1245
1416
|
}, children: _jsx(Icon, { icon: 'close-thin', size: '12px' }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Icon, { icon: 'plus-thin' }), _jsx("div", { className: 'ss__chat__content__header__comparisons__content__comparison--placeholder__text', children: "Add" })] })) }, index));
|
|
1246
1417
|
}) }), _jsx("div", { className: classnames('ss__chat__content__header__comparisons__action', {
|
|
1247
1418
|
'ss__chat__content__header__comparisons__action--hidden': (store.currentChat?.comparisons.compared.length || 0) < 2,
|
|
1248
|
-
}), children: _jsx(Button, { onClick: () => controller.search(), icon: { icon: 'compare', title: 'Compare' }, children: "Compare" }) })] })] })) }), _jsxs("div", { className: 'ss__chat__messages', ref: messagesContainerRef, children: [(!store.currentChat?.chat || store.currentChat.chat.length === 0) && store.welcomeMessage && (_jsxs("div", { className: "ss__chat__welcome", children: [_jsx("div", { className: "ss__chat__welcome__message", children: store.welcomeMessage }), _jsx(SuggestedQuestions, { questions: store.suggestedQuestions, controller: controller })] })), store.currentChat?.chat
|
|
1419
|
+
}), children: _jsx(Button, { onClick: () => controller.search(), icon: { icon: 'compare', title: 'Compare' }, children: "Compare" }) })] })] })) }), _jsxs("div", { className: 'ss__chat__messages', ref: messagesContainerRef, style: visibleChatLength ? { scrollbarGutter: 'stable' } : undefined, children: [(!store.currentChat?.chat || store.currentChat.chat.length === 0) && store.welcomeMessage && !store.currentChat?.isExpired && (_jsxs("div", { className: "ss__chat__welcome", children: [_jsx("div", { className: "ss__chat__welcome__message", children: store.welcomeMessage }), _jsx(SuggestedQuestions, { questions: store.suggestedQuestions, controller: controller })] })), store.currentChat?.chat
|
|
1249
1420
|
.filter((chatItem) => chatItem.messageType !== 'productQuery')
|
|
1250
1421
|
.map((chatItem, index) => (_jsx("div", { className: `ss__chat__message ss__chat__message--${chatItem.messageType}`, children: {
|
|
1251
1422
|
user: (_jsx(MessageUser, { chatItem: chatItem, controller: controller, onViewProduct: isMobile ? () => setMobileProductInfoOpen(true) : undefined })),
|
|
1252
|
-
text: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom:
|
|
1253
|
-
content: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom:
|
|
1254
|
-
productSearchResult: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom:
|
|
1255
|
-
inspirationResult: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom:
|
|
1256
|
-
productAnswer: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom:
|
|
1423
|
+
text: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, onViewProduct: isMobile ? () => setMobileProductInfoOpen(true) : undefined, sideChatOpen: !!shouldShowSideChat })),
|
|
1424
|
+
content: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, onViewProduct: isMobile ? () => setMobileProductInfoOpen(true) : undefined, sideChatOpen: !!shouldShowSideChat })),
|
|
1425
|
+
productSearchResult: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, onViewProduct: isMobile ? () => setMobileProductInfoOpen(true) : undefined, sideChatOpen: !!shouldShowSideChat })),
|
|
1426
|
+
inspirationResult: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, onViewProduct: isMobile ? () => setMobileProductInfoOpen(true) : undefined, sideChatOpen: !!shouldShowSideChat })),
|
|
1427
|
+
productAnswer: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, onViewProduct: isMobile ? () => setMobileProductInfoOpen(true) : undefined, sideChatOpen: !!shouldShowSideChat })),
|
|
1257
1428
|
productComparison: (() => {
|
|
1258
1429
|
// On mobile, only the first productComparison in the session
|
|
1259
1430
|
// auto-opens the secondary panel. Subsequent ones get a
|
|
1260
1431
|
// prominent "Show Details" button instead.
|
|
1261
1432
|
const comparisonMessages = (store.currentChat?.chat || []).filter((m) => m.messageType === 'productComparison');
|
|
1262
1433
|
const isFirst = comparisonMessages.length > 0 && comparisonMessages[0].id === chatItem.id;
|
|
1263
|
-
return (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom:
|
|
1434
|
+
return (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, onViewProduct: isMobile ? () => setMobileProductInfoOpen(true) : undefined, showDetailsButton: isMobile && !isFirst, sideChatOpen: !!shouldShowSideChat }));
|
|
1264
1435
|
})(),
|
|
1265
|
-
productRecommendation: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom:
|
|
1436
|
+
productRecommendation: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, onViewProduct: isMobile ? () => setMobileProductInfoOpen(true) : undefined, sideChatOpen: !!shouldShowSideChat })),
|
|
1266
1437
|
actions: _jsx(SuggestedQuestions, { actions: chatItem.actions, controller: controller }),
|
|
1267
|
-
errorResponse: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom:
|
|
1268
|
-
topicDrift: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom:
|
|
1438
|
+
errorResponse: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, sideChatOpen: !!shouldShowSideChat })),
|
|
1439
|
+
topicDrift: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, sideChatOpen: !!shouldShowSideChat })),
|
|
1269
1440
|
productQuery: null,
|
|
1270
|
-
}[chatItem.messageType] || null }, index))), _jsx("div", { className: "ss__chat__messages__end", ref: messagesEndRef })
|
|
1441
|
+
}[chatItem.messageType] || null }, index))), _jsx("div", { className: "ss__chat__messages__end", ref: messagesEndRef }), showNewMessages && messagesOverflow && (_jsxs("div", { className: "ss__chat__new-messages", onClick: () => {
|
|
1442
|
+
scrollToBottom();
|
|
1443
|
+
setShowNewMessages(false);
|
|
1444
|
+
}, children: [_jsx(Icon, { icon: "angle-down", size: "12px" }), _jsx("span", { children: "New messages" })] }))] }), _jsx(ChatLoadingIndicator, { loading: store.loading, verbs: loadingVerbs }), !store.currentChat?.isExpired ? (_jsxs("div", { className: "ss__chat__content__footer", ref: footerRef, children: [store.error && _jsx("div", { className: "ss__chat__error", children: store.error.message }), store.currentChat?.sessionLimitReached && (_jsxs("div", { className: 'ss__chat__topic-drift', children: [_jsx(Icon, { icon: "info", size: "18px", className: 'ss__chat__topic-drift__icon--info' }), _jsxs("div", { className: 'ss__chat__topic-drift__text', children: [_jsx("span", { children: "Session limit reached" }), _jsx("span", { children: "This chat has reached its maximum number of interactions. Start a new session to continue." })] }), _jsx(Button, { className: 'ss__chat__topic-drift__button', onClick: () => {
|
|
1271
1445
|
controller.store.createChat();
|
|
1272
1446
|
}, children: "New Session" })] })), store.currentChat?.actions && store.currentChat.actions.length > 0 && (_jsx("div", { className: 'ss__chat__actions', children: store.currentChat?.actions.map((action, index) => (_jsx("div", { className: "ss__chat__actions__wrap", children: {
|
|
1273
|
-
facets: action.data.length ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "ss__chat__actions__facets-header", children: [_jsx("div", { className: "ss__chat__actions--title", children: "Filter by:" }), multiselectFacets && (store.currentChat?.attachments.attached.some((item) => item.type === 'facet') ||
|
|
1447
|
+
facets: action.data.length ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "ss__chat__actions__facets-header", children: [_jsx("div", { className: "ss__chat__actions--title", children: "Filter by:" }), multiselectFacets && (_jsx("div", { className: `ss__chat__actions__facets-apply${store.currentChat?.attachments.attached.some((item) => item.type === 'facet') ||
|
|
1448
|
+
(store.currentChat?.removedFacets && store.currentChat.removedFacets.length > 0)
|
|
1449
|
+
? ' ss__chat__actions__facets-apply--active'
|
|
1450
|
+
: ''}`, children: _jsx(Button, { onClick: () => {
|
|
1274
1451
|
controller.search();
|
|
1275
|
-
}, children: "Apply
|
|
1452
|
+
}, children: "Apply" }) }))] }), _jsx("div", { className: "ss__chat__actions--facets", children: action.data.slice(0, 10).map((facet, idx) => {
|
|
1276
1453
|
if (!facet.values?.length)
|
|
1277
1454
|
return null;
|
|
1278
|
-
return (_jsx("div", { className: `ss__chat__actions__facet ss__chat__actions__facet--${facet.type}`, children: _jsx(Dropdown, { usePortal: true, dropUp: true, button: _jsx(FacetButton, { label: facet
|
|
1455
|
+
return (_jsx("div", { className: `ss__chat__actions__facet ss__chat__actions__facet--${facet.type}`, children: _jsx(Dropdown, { usePortal: true, dropUp: true, button: _jsx(FacetButton, { label: getFacetButtonLabel(facet) }), style: {
|
|
1279
1456
|
'.ss__dropdown__content': {
|
|
1280
1457
|
width: '150px',
|
|
1281
1458
|
maxHeight: '200px',
|
|
@@ -1339,9 +1516,11 @@ export const Chat = observer((properties) => {
|
|
|
1339
1516
|
},
|
|
1340
1517
|
}, children: _jsx("div", { className: "ss__chat__actions__facet__options", children: facet.type === 'range-buckets'
|
|
1341
1518
|
? facet.values.map((option) => {
|
|
1342
|
-
const
|
|
1519
|
+
const rangeOption = option;
|
|
1520
|
+
const optionValue = `${rangeOption.low ?? '*'}:${rangeOption.high ?? '*'}`;
|
|
1343
1521
|
if (multiselectFacets) {
|
|
1344
|
-
const isSelected = controller.store.
|
|
1522
|
+
const isSelected = (rangeOption.filtered && !controller.store.isFacetRemoved(facet.field, optionValue)) ||
|
|
1523
|
+
controller.store.isFacetSelected(facet.field, optionValue);
|
|
1345
1524
|
return (_jsxs("div", { className: "ss__chat__actions__facet__option--checkbox", onClick: () => {
|
|
1346
1525
|
if (isSelected) {
|
|
1347
1526
|
controller.store.removeFacet(facet.field, optionValue);
|
|
@@ -1351,29 +1530,31 @@ export const Chat = observer((properties) => {
|
|
|
1351
1530
|
key: facet.field,
|
|
1352
1531
|
facetLabel: facet.label,
|
|
1353
1532
|
value: optionValue,
|
|
1354
|
-
label:
|
|
1355
|
-
count:
|
|
1533
|
+
label: rangeOption.label,
|
|
1534
|
+
count: rangeOption.count,
|
|
1356
1535
|
});
|
|
1357
1536
|
}
|
|
1358
1537
|
}, children: [_jsx("div", { className: classnames('ss__chat__actions__facet__option__checkbox', {
|
|
1359
1538
|
'ss__chat__actions__facet__option__checkbox--checked': isSelected,
|
|
1360
|
-
}), children: isSelected && _jsx(Icon, { icon: "check-thin", size: "10px" }) }), _jsx("span", { className: "ss__chat__actions__facet__option__label", children:
|
|
1539
|
+
}), children: isSelected && _jsx(Icon, { icon: "check-thin", size: "10px" }) }), _jsx("span", { className: "ss__chat__actions__facet__option__label", children: rangeOption.label })] }, optionValue));
|
|
1361
1540
|
}
|
|
1362
1541
|
return (_jsx(Button, { onClick: () => {
|
|
1363
1542
|
controller.store.addFacet({
|
|
1364
1543
|
key: facet.field,
|
|
1365
1544
|
facetLabel: facet.label,
|
|
1366
1545
|
value: optionValue,
|
|
1367
|
-
label:
|
|
1368
|
-
count:
|
|
1546
|
+
label: rangeOption.label,
|
|
1547
|
+
count: rangeOption.count,
|
|
1369
1548
|
});
|
|
1370
1549
|
controller.search();
|
|
1371
|
-
}, children:
|
|
1550
|
+
}, children: rangeOption.label }, optionValue));
|
|
1372
1551
|
})
|
|
1373
1552
|
: facet.values.map((option) => {
|
|
1374
|
-
const
|
|
1553
|
+
const valueOption = option;
|
|
1554
|
+
const optionValue = valueOption.value || valueOption.label;
|
|
1375
1555
|
if (multiselectFacets) {
|
|
1376
|
-
const isSelected = controller.store.
|
|
1556
|
+
const isSelected = (valueOption.filtered && !controller.store.isFacetRemoved(facet.field, optionValue)) ||
|
|
1557
|
+
controller.store.isFacetSelected(facet.field, optionValue);
|
|
1377
1558
|
return (_jsxs("div", { className: "ss__chat__actions__facet__option--checkbox", onClick: () => {
|
|
1378
1559
|
if (isSelected) {
|
|
1379
1560
|
controller.store.removeFacet(facet.field, optionValue);
|
|
@@ -1383,24 +1564,24 @@ export const Chat = observer((properties) => {
|
|
|
1383
1564
|
key: facet.field,
|
|
1384
1565
|
facetLabel: facet.label,
|
|
1385
1566
|
value: optionValue,
|
|
1386
|
-
label:
|
|
1387
|
-
count:
|
|
1567
|
+
label: valueOption.label,
|
|
1568
|
+
count: valueOption.count,
|
|
1388
1569
|
});
|
|
1389
1570
|
}
|
|
1390
1571
|
}, children: [_jsx("div", { className: classnames('ss__chat__actions__facet__option__checkbox', {
|
|
1391
1572
|
'ss__chat__actions__facet__option__checkbox--checked': isSelected,
|
|
1392
|
-
}), children: isSelected && _jsx(Icon, { icon: "check-thin", size: "10px" }) }), _jsx("span", { className: "ss__chat__actions__facet__option__label", children:
|
|
1573
|
+
}), children: isSelected && _jsx(Icon, { icon: "check-thin", size: "10px" }) }), _jsx("span", { className: "ss__chat__actions__facet__option__label", children: valueOption.label })] }, optionValue));
|
|
1393
1574
|
}
|
|
1394
1575
|
return (_jsx(Button, { onClick: () => {
|
|
1395
1576
|
controller.store.addFacet({
|
|
1396
1577
|
key: facet.field,
|
|
1397
1578
|
facetLabel: facet.label,
|
|
1398
1579
|
value: optionValue,
|
|
1399
|
-
label:
|
|
1400
|
-
count:
|
|
1580
|
+
label: valueOption.label,
|
|
1581
|
+
count: valueOption.count,
|
|
1401
1582
|
});
|
|
1402
1583
|
controller.search();
|
|
1403
|
-
}, children:
|
|
1584
|
+
}, children: valueOption.label }, optionValue));
|
|
1404
1585
|
}) }) }, facet.field) }, idx));
|
|
1405
1586
|
}) })] })) : null,
|
|
1406
1587
|
actions: (_jsx("div", { className: "ss__chat__actions--suggested" })),
|
|
@@ -1509,6 +1690,7 @@ export const Chat = observer((properties) => {
|
|
|
1509
1690
|
const messageText = store.currentChat?.handleTopicDrift();
|
|
1510
1691
|
if (messageText) {
|
|
1511
1692
|
controller.store.createChat();
|
|
1693
|
+
controller.search({ data: { message: messageText } });
|
|
1512
1694
|
}
|
|
1513
1695
|
}, children: buttonText })), _jsx("span", { onClick: () => {
|
|
1514
1696
|
store.currentChat?.dismissTopicDrift();
|
|
@@ -1547,7 +1729,7 @@ export const Chat = observer((properties) => {
|
|
|
1547
1729
|
}, multiple: true, type: "file", accept: "image/*", className: "ss__chat__input__input__file" })] }))] }), _jsx("div", { className: 'ss__chat__input__actions', children: (() => {
|
|
1548
1730
|
const hasImageAttachment = (store.currentChat?.attachments.attached || []).some((item) => item.type === 'image' && !item.error);
|
|
1549
1731
|
const messageEmpty = !controller.store.inputValue.trim();
|
|
1550
|
-
const sendDisabled = store.blocked || store.currentChat?.sessionLimitReached || (hasImageAttachment && messageEmpty);
|
|
1732
|
+
const sendDisabled = store.blocked || store.currentChat?.sessionLimitReached || (hasImageAttachment && messageEmpty) || messageEmpty;
|
|
1551
1733
|
return (_jsx(Button, { className: "ss__chat__send-button", icon: { icon: 'send', title: 'Send Message' }, disabled: sendDisabled, onClick: () => {
|
|
1552
1734
|
if (sendDisabled)
|
|
1553
1735
|
return;
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { ChatController } from '@athoscommerce/snap-controller';
|
|
2
2
|
import { Product } from '@athoscommerce/snap-store-mobx';
|
|
3
3
|
import { ComponentProps } from '../../../types';
|
|
4
|
-
export declare const ChatResult: (
|
|
5
|
-
displayName: string;
|
|
6
|
-
};
|
|
4
|
+
export declare const ChatResult: (props: ChatResultProps) => import("@emotion/react/jsx-runtime").JSX.Element;
|
|
7
5
|
export interface ChatResultProps extends ComponentProps {
|
|
8
6
|
result: Product;
|
|
9
7
|
controller: ChatController;
|
|
10
8
|
scrollToBottom: () => void;
|
|
9
|
+
trackingRef?: (el: HTMLElement | null) => void;
|
|
11
10
|
}
|
|
12
11
|
//# sourceMappingURL=ChatResult.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatResult.d.ts","sourceRoot":"","sources":["../../../../../../../components/src/components/Organisms/Chat/ChatResult.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAMzD,OAAO,EAAE,cAAc,EAAe,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"ChatResult.d.ts","sourceRoot":"","sources":["../../../../../../../components/src/components/Organisms/Chat/ChatResult.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAMzD,OAAO,EAAE,cAAc,EAAe,MAAM,gBAAgB,CAAC;AAkH7D,eAAO,MAAM,UAAU,8EA+GtB,CAAC;AAMF,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACtD,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,cAAc,CAAC;IAC3B,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;CAC/C"}
|