@athoscommerce/snap-preact 1.2.3-beta.3 → 1.2.3-beta.5

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 (218) hide show
  1. package/README.md +16 -0
  2. package/dist/cjs/components/src/components/Atoms/TemplateSelect/TemplateSelect.d.ts +3 -3
  3. package/dist/cjs/components/src/components/Atoms/TemplateSelect/TemplateSelect.d.ts.map +1 -1
  4. package/dist/cjs/components/src/components/Atoms/TemplateSelect/TemplateSelect.js +7 -8
  5. package/dist/cjs/components/src/components/Molecules/ChatInspirationResultMessage/ChatInspirationResultMessage.d.ts +1 -1
  6. package/dist/cjs/components/src/components/Molecules/ChatInspirationResultMessage/ChatInspirationResultMessage.d.ts.map +1 -1
  7. package/dist/cjs/components/src/components/Molecules/ChatInspirationResultMessage/ChatInspirationResultMessage.js +3 -3
  8. package/dist/cjs/components/src/components/Molecules/ChatProductQueryMessage/ChatProductQueryMessage.d.ts +2 -0
  9. package/dist/cjs/components/src/components/Molecules/ChatProductQueryMessage/ChatProductQueryMessage.d.ts.map +1 -1
  10. package/dist/cjs/components/src/components/Molecules/ChatProductQueryMessage/ChatProductQueryMessage.js +29 -27
  11. package/dist/cjs/components/src/components/Molecules/Filter/Filter.d.ts +1 -1
  12. package/dist/cjs/components/src/components/Molecules/Filter/Filter.d.ts.map +1 -1
  13. package/dist/cjs/components/src/components/Molecules/Result/Result.d.ts.map +1 -1
  14. package/dist/cjs/components/src/components/Molecules/Result/Result.js +11 -6
  15. package/dist/cjs/components/src/components/Molecules/SearchInput/SearchInput.d.ts +4 -1
  16. package/dist/cjs/components/src/components/Molecules/SearchInput/SearchInput.d.ts.map +1 -1
  17. package/dist/cjs/components/src/components/Molecules/SearchInput/SearchInput.js +3 -3
  18. package/dist/cjs/components/src/components/Molecules/TemplatesEditor/Components/AbstractedControls.js +5 -5
  19. package/dist/cjs/components/src/components/Molecules/TemplatesEditor/Components/Control.d.ts.map +1 -1
  20. package/dist/cjs/components/src/components/Molecules/TemplatesEditor/Components/Control.js +31 -31
  21. package/dist/cjs/components/src/components/Molecules/TemplatesEditor/TemplatesEditor.d.ts.map +1 -1
  22. package/dist/cjs/components/src/components/Molecules/TemplatesEditor/TemplatesEditor.js +22 -36
  23. package/dist/cjs/components/src/components/Organisms/Chat/Chat.d.ts +8 -0
  24. package/dist/cjs/components/src/components/Organisms/Chat/Chat.d.ts.map +1 -1
  25. package/dist/cjs/components/src/components/Organisms/Chat/Chat.js +715 -427
  26. package/dist/cjs/components/src/components/Organisms/Chat/ChatResult.d.ts.map +1 -1
  27. package/dist/cjs/components/src/components/Organisms/Chat/ChatResult.js +13 -3
  28. package/dist/cjs/components/src/components/Organisms/Chat/MessageText.d.ts +3 -1
  29. package/dist/cjs/components/src/components/Organisms/Chat/MessageText.d.ts.map +1 -1
  30. package/dist/cjs/components/src/components/Organisms/Chat/MessageText.js +22 -19
  31. package/dist/cjs/components/src/components/Organisms/Chat/MessageUser.d.ts +3 -1
  32. package/dist/cjs/components/src/components/Organisms/Chat/MessageUser.d.ts.map +1 -1
  33. package/dist/cjs/components/src/components/Organisms/Chat/MessageUser.js +40 -23
  34. package/dist/cjs/components/src/components/Organisms/Chat/ResultsDisplay.d.ts +1 -1
  35. package/dist/cjs/components/src/components/Organisms/Chat/ResultsDisplay.d.ts.map +1 -1
  36. package/dist/cjs/components/src/components/Organisms/Chat/ResultsDisplay.js +8 -7
  37. package/dist/cjs/components/src/components/Organisms/Facet/Facet.js +2 -1
  38. package/dist/cjs/components/src/components/Organisms/FacetsHorizontal/FacetsHorizontal.js +2 -1
  39. package/dist/cjs/components/src/components/Organisms/FilterSummary/FilterSummary.d.ts +2 -2
  40. package/dist/cjs/components/src/components/Organisms/FilterSummary/FilterSummary.d.ts.map +1 -1
  41. package/dist/cjs/components/src/components/Organisms/ProductQuickView/ProductQuickView.d.ts +11 -0
  42. package/dist/cjs/components/src/components/Organisms/ProductQuickView/ProductQuickView.d.ts.map +1 -0
  43. package/dist/cjs/components/src/components/Organisms/ProductQuickView/ProductQuickView.js +461 -0
  44. package/dist/cjs/components/src/components/Organisms/ProductQuickView/index.d.ts +2 -0
  45. package/dist/cjs/components/src/components/Organisms/ProductQuickView/index.d.ts.map +1 -0
  46. package/dist/cjs/components/src/components/Organisms/ProductQuickView/index.js +17 -0
  47. package/dist/cjs/components/src/components/Templates/AutocompleteModal/AutocompleteModal.d.ts.map +1 -1
  48. package/dist/cjs/components/src/components/Templates/AutocompleteModal/AutocompleteModal.js +0 -2
  49. package/dist/cjs/components/src/hooks/useAcRenderedInput.d.ts.map +1 -1
  50. package/dist/cjs/components/src/hooks/useAcRenderedInput.js +65 -15
  51. package/dist/cjs/components/src/hooks/useMediaQuery.d.ts.map +1 -1
  52. package/dist/cjs/components/src/hooks/useMediaQuery.js +3 -1
  53. package/dist/cjs/components/src/index.d.ts +1 -0
  54. package/dist/cjs/components/src/index.d.ts.map +1 -1
  55. package/dist/cjs/components/src/index.js +1 -0
  56. package/dist/cjs/components/src/providers/theme.d.ts +1 -1
  57. package/dist/cjs/components/src/providers/theme.d.ts.map +1 -1
  58. package/dist/cjs/components/src/themes/base/base.js +3 -3
  59. package/dist/cjs/components/src/utilities/Colour/Colour.d.ts +11 -0
  60. package/dist/cjs/components/src/utilities/Colour/Colour.d.ts.map +1 -1
  61. package/dist/cjs/components/src/utilities/Colour/Colour.js +32 -0
  62. package/dist/cjs/src/Instantiators/RecommendationInstantiator.d.ts +1 -0
  63. package/dist/cjs/src/Instantiators/RecommendationInstantiator.d.ts.map +1 -1
  64. package/dist/cjs/src/Instantiators/RecommendationInstantiator.js +161 -123
  65. package/dist/cjs/src/Snap.d.ts +1 -1
  66. package/dist/cjs/src/Snap.d.ts.map +1 -1
  67. package/dist/cjs/src/Snap.js +18 -6
  68. package/dist/cjs/src/SnapHybrid.d.ts.map +1 -1
  69. package/dist/cjs/src/Templates/SnapTemplates.d.ts +13 -72
  70. package/dist/cjs/src/Templates/SnapTemplates.d.ts.map +1 -1
  71. package/dist/cjs/src/Templates/SnapTemplates.js +123 -72
  72. package/dist/cjs/src/Templates/Stores/TargetStore.d.ts +7 -6
  73. package/dist/cjs/src/Templates/Stores/TargetStore.d.ts.map +1 -1
  74. package/dist/cjs/src/Templates/Stores/TargetStore.js +14 -8
  75. package/dist/cjs/src/Templates/Stores/TemplateEditor/TemplateEditorStore.d.ts +58 -8
  76. package/dist/cjs/src/Templates/Stores/TemplateEditor/TemplateEditorStore.d.ts.map +1 -1
  77. package/dist/cjs/src/Templates/Stores/TemplateEditor/TemplateEditorStore.js +467 -96
  78. package/dist/cjs/src/Templates/Stores/TemplateEditor/uiAbstractions/autocompleteControllerUI.d.ts +1 -0
  79. package/dist/cjs/src/Templates/Stores/TemplateEditor/uiAbstractions/autocompleteControllerUI.d.ts.map +1 -1
  80. package/dist/cjs/src/Templates/Stores/TemplateEditor/uiAbstractions/autocompleteControllerUI.js +123 -70
  81. package/dist/cjs/src/Templates/Stores/TemplateEditor/uiAbstractions/configUI.d.ts.map +1 -1
  82. package/dist/cjs/src/Templates/Stores/TemplateEditor/uiAbstractions/configUI.js +9 -3
  83. package/dist/cjs/src/Templates/Stores/TemplateEditor/uiAbstractions/searchControllerUI.d.ts +1 -0
  84. package/dist/cjs/src/Templates/Stores/TemplateEditor/uiAbstractions/searchControllerUI.d.ts.map +1 -1
  85. package/dist/cjs/src/Templates/Stores/TemplateEditor/uiAbstractions/searchControllerUI.js +86 -1
  86. package/dist/cjs/src/Templates/Stores/TemplateEditor/uiAbstractions/themeUI.d.ts.map +1 -1
  87. package/dist/cjs/src/Templates/Stores/TemplateEditor/uiAbstractions/themeUI.js +33 -14
  88. package/dist/cjs/src/Templates/Stores/TemplateStore.d.ts +93 -30
  89. package/dist/cjs/src/Templates/Stores/TemplateStore.d.ts.map +1 -1
  90. package/dist/cjs/src/Templates/Stores/TemplateStore.js +33 -41
  91. package/dist/cjs/src/Templates/Stores/ThemeStore.d.ts +2 -8
  92. package/dist/cjs/src/Templates/Stores/ThemeStore.d.ts.map +1 -1
  93. package/dist/cjs/src/Templates/Stores/ThemeStore.js +3 -28
  94. package/dist/cjs/src/Templates/index.d.ts +1 -1
  95. package/dist/cjs/src/Templates/index.d.ts.map +1 -1
  96. package/dist/cjs/src/create/createChatController.js +1 -1
  97. package/dist/cjs/src/index.d.ts +1 -0
  98. package/dist/cjs/src/index.d.ts.map +1 -1
  99. package/dist/cjs/src/setupEvents.d.ts +13 -0
  100. package/dist/cjs/src/setupEvents.d.ts.map +1 -1
  101. package/dist/cjs/src/setupEvents.js +56 -8
  102. package/dist/cjs/src/types.d.ts +3 -1
  103. package/dist/cjs/src/types.d.ts.map +1 -1
  104. package/dist/cjs/src/utils/combineMerge.d.ts +2 -0
  105. package/dist/cjs/src/utils/combineMerge.d.ts.map +1 -0
  106. package/dist/cjs/src/utils/combineMerge.js +22 -0
  107. package/dist/cjs/src/utils/index.d.ts +1 -0
  108. package/dist/cjs/src/utils/index.d.ts.map +1 -1
  109. package/dist/cjs/src/utils/index.js +3 -1
  110. package/dist/esm/components/src/components/Atoms/TemplateSelect/TemplateSelect.d.ts +3 -3
  111. package/dist/esm/components/src/components/Atoms/TemplateSelect/TemplateSelect.d.ts.map +1 -1
  112. package/dist/esm/components/src/components/Atoms/TemplateSelect/TemplateSelect.js +7 -8
  113. package/dist/esm/components/src/components/Molecules/ChatInspirationResultMessage/ChatInspirationResultMessage.d.ts +1 -1
  114. package/dist/esm/components/src/components/Molecules/ChatInspirationResultMessage/ChatInspirationResultMessage.d.ts.map +1 -1
  115. package/dist/esm/components/src/components/Molecules/ChatInspirationResultMessage/ChatInspirationResultMessage.js +3 -3
  116. package/dist/esm/components/src/components/Molecules/ChatProductQueryMessage/ChatProductQueryMessage.d.ts +2 -0
  117. package/dist/esm/components/src/components/Molecules/ChatProductQueryMessage/ChatProductQueryMessage.d.ts.map +1 -1
  118. package/dist/esm/components/src/components/Molecules/ChatProductQueryMessage/ChatProductQueryMessage.js +29 -28
  119. package/dist/esm/components/src/components/Molecules/Filter/Filter.d.ts +1 -1
  120. package/dist/esm/components/src/components/Molecules/Filter/Filter.d.ts.map +1 -1
  121. package/dist/esm/components/src/components/Molecules/Result/Result.d.ts.map +1 -1
  122. package/dist/esm/components/src/components/Molecules/Result/Result.js +9 -4
  123. package/dist/esm/components/src/components/Molecules/SearchInput/SearchInput.d.ts +4 -1
  124. package/dist/esm/components/src/components/Molecules/SearchInput/SearchInput.d.ts.map +1 -1
  125. package/dist/esm/components/src/components/Molecules/SearchInput/SearchInput.js +3 -3
  126. package/dist/esm/components/src/components/Molecules/TemplatesEditor/Components/AbstractedControls.js +5 -5
  127. package/dist/esm/components/src/components/Molecules/TemplatesEditor/Components/Control.d.ts.map +1 -1
  128. package/dist/esm/components/src/components/Molecules/TemplatesEditor/Components/Control.js +29 -29
  129. package/dist/esm/components/src/components/Molecules/TemplatesEditor/TemplatesEditor.d.ts.map +1 -1
  130. package/dist/esm/components/src/components/Molecules/TemplatesEditor/TemplatesEditor.js +22 -35
  131. package/dist/esm/components/src/components/Organisms/Chat/Chat.d.ts +8 -0
  132. package/dist/esm/components/src/components/Organisms/Chat/Chat.d.ts.map +1 -1
  133. package/dist/esm/components/src/components/Organisms/Chat/Chat.js +636 -359
  134. package/dist/esm/components/src/components/Organisms/Chat/ChatResult.d.ts.map +1 -1
  135. package/dist/esm/components/src/components/Organisms/Chat/ChatResult.js +13 -3
  136. package/dist/esm/components/src/components/Organisms/Chat/MessageText.d.ts +3 -1
  137. package/dist/esm/components/src/components/Organisms/Chat/MessageText.d.ts.map +1 -1
  138. package/dist/esm/components/src/components/Organisms/Chat/MessageText.js +21 -19
  139. package/dist/esm/components/src/components/Organisms/Chat/MessageUser.d.ts +3 -1
  140. package/dist/esm/components/src/components/Organisms/Chat/MessageUser.d.ts.map +1 -1
  141. package/dist/esm/components/src/components/Organisms/Chat/MessageUser.js +37 -23
  142. package/dist/esm/components/src/components/Organisms/Chat/ResultsDisplay.d.ts +1 -1
  143. package/dist/esm/components/src/components/Organisms/Chat/ResultsDisplay.d.ts.map +1 -1
  144. package/dist/esm/components/src/components/Organisms/Chat/ResultsDisplay.js +8 -7
  145. package/dist/esm/components/src/components/Organisms/Facet/Facet.js +1 -1
  146. package/dist/esm/components/src/components/Organisms/FacetsHorizontal/FacetsHorizontal.js +1 -1
  147. package/dist/esm/components/src/components/Organisms/FilterSummary/FilterSummary.d.ts +2 -2
  148. package/dist/esm/components/src/components/Organisms/FilterSummary/FilterSummary.d.ts.map +1 -1
  149. package/dist/esm/components/src/components/Organisms/ProductQuickView/ProductQuickView.d.ts +11 -0
  150. package/dist/esm/components/src/components/Organisms/ProductQuickView/ProductQuickView.d.ts.map +1 -0
  151. package/dist/esm/components/src/components/Organisms/ProductQuickView/ProductQuickView.js +436 -0
  152. package/dist/esm/components/src/components/Organisms/ProductQuickView/index.d.ts +2 -0
  153. package/dist/esm/components/src/components/Organisms/ProductQuickView/index.d.ts.map +1 -0
  154. package/dist/esm/components/src/components/Organisms/ProductQuickView/index.js +1 -0
  155. package/dist/esm/components/src/components/Templates/AutocompleteModal/AutocompleteModal.d.ts.map +1 -1
  156. package/dist/esm/components/src/components/Templates/AutocompleteModal/AutocompleteModal.js +0 -2
  157. package/dist/esm/components/src/hooks/useAcRenderedInput.d.ts.map +1 -1
  158. package/dist/esm/components/src/hooks/useAcRenderedInput.js +61 -11
  159. package/dist/esm/components/src/hooks/useMediaQuery.d.ts.map +1 -1
  160. package/dist/esm/components/src/hooks/useMediaQuery.js +3 -1
  161. package/dist/esm/components/src/index.d.ts +1 -0
  162. package/dist/esm/components/src/index.d.ts.map +1 -1
  163. package/dist/esm/components/src/index.js +1 -0
  164. package/dist/esm/components/src/providers/theme.d.ts +1 -1
  165. package/dist/esm/components/src/providers/theme.d.ts.map +1 -1
  166. package/dist/esm/components/src/themes/base/base.js +3 -3
  167. package/dist/esm/components/src/utilities/Colour/Colour.d.ts +11 -0
  168. package/dist/esm/components/src/utilities/Colour/Colour.d.ts.map +1 -1
  169. package/dist/esm/components/src/utilities/Colour/Colour.js +30 -0
  170. package/dist/esm/src/Instantiators/RecommendationInstantiator.d.ts +1 -0
  171. package/dist/esm/src/Instantiators/RecommendationInstantiator.d.ts.map +1 -1
  172. package/dist/esm/src/Instantiators/RecommendationInstantiator.js +130 -84
  173. package/dist/esm/src/Snap.d.ts +1 -1
  174. package/dist/esm/src/Snap.d.ts.map +1 -1
  175. package/dist/esm/src/Snap.js +21 -6
  176. package/dist/esm/src/SnapHybrid.d.ts.map +1 -1
  177. package/dist/esm/src/Templates/SnapTemplates.d.ts +13 -72
  178. package/dist/esm/src/Templates/SnapTemplates.d.ts.map +1 -1
  179. package/dist/esm/src/Templates/SnapTemplates.js +137 -78
  180. package/dist/esm/src/Templates/Stores/TargetStore.d.ts +7 -6
  181. package/dist/esm/src/Templates/Stores/TargetStore.d.ts.map +1 -1
  182. package/dist/esm/src/Templates/Stores/TargetStore.js +14 -8
  183. package/dist/esm/src/Templates/Stores/TemplateEditor/TemplateEditorStore.d.ts +58 -8
  184. package/dist/esm/src/Templates/Stores/TemplateEditor/TemplateEditorStore.d.ts.map +1 -1
  185. package/dist/esm/src/Templates/Stores/TemplateEditor/TemplateEditorStore.js +447 -89
  186. package/dist/esm/src/Templates/Stores/TemplateEditor/uiAbstractions/autocompleteControllerUI.d.ts +1 -0
  187. package/dist/esm/src/Templates/Stores/TemplateEditor/uiAbstractions/autocompleteControllerUI.d.ts.map +1 -1
  188. package/dist/esm/src/Templates/Stores/TemplateEditor/uiAbstractions/autocompleteControllerUI.js +118 -66
  189. package/dist/esm/src/Templates/Stores/TemplateEditor/uiAbstractions/configUI.d.ts.map +1 -1
  190. package/dist/esm/src/Templates/Stores/TemplateEditor/uiAbstractions/configUI.js +9 -3
  191. package/dist/esm/src/Templates/Stores/TemplateEditor/uiAbstractions/searchControllerUI.d.ts +1 -0
  192. package/dist/esm/src/Templates/Stores/TemplateEditor/uiAbstractions/searchControllerUI.d.ts.map +1 -1
  193. package/dist/esm/src/Templates/Stores/TemplateEditor/uiAbstractions/searchControllerUI.js +82 -1
  194. package/dist/esm/src/Templates/Stores/TemplateEditor/uiAbstractions/themeUI.d.ts.map +1 -1
  195. package/dist/esm/src/Templates/Stores/TemplateEditor/uiAbstractions/themeUI.js +27 -8
  196. package/dist/esm/src/Templates/Stores/TemplateStore.d.ts +93 -30
  197. package/dist/esm/src/Templates/Stores/TemplateStore.d.ts.map +1 -1
  198. package/dist/esm/src/Templates/Stores/TemplateStore.js +29 -38
  199. package/dist/esm/src/Templates/Stores/ThemeStore.d.ts +2 -8
  200. package/dist/esm/src/Templates/Stores/ThemeStore.d.ts.map +1 -1
  201. package/dist/esm/src/Templates/Stores/ThemeStore.js +3 -26
  202. package/dist/esm/src/Templates/index.d.ts +1 -1
  203. package/dist/esm/src/Templates/index.d.ts.map +1 -1
  204. package/dist/esm/src/create/createChatController.js +1 -1
  205. package/dist/esm/src/index.d.ts +1 -0
  206. package/dist/esm/src/index.d.ts.map +1 -1
  207. package/dist/esm/src/setupEvents.d.ts +13 -0
  208. package/dist/esm/src/setupEvents.d.ts.map +1 -1
  209. package/dist/esm/src/setupEvents.js +25 -8
  210. package/dist/esm/src/types.d.ts +3 -1
  211. package/dist/esm/src/types.d.ts.map +1 -1
  212. package/dist/esm/src/utils/combineMerge.d.ts +2 -0
  213. package/dist/esm/src/utils/combineMerge.d.ts.map +1 -0
  214. package/dist/esm/src/utils/combineMerge.js +16 -0
  215. package/dist/esm/src/utils/index.d.ts +1 -0
  216. package/dist/esm/src/utils/index.d.ts.map +1 -1
  217. package/dist/esm/src/utils/index.js +1 -0
  218. package/package.json +20 -14
@@ -4,7 +4,7 @@ import classnames from 'classnames';
4
4
  import deepmerge from 'deepmerge';
5
5
  import { observer } from 'mobx-react-lite';
6
6
  import { useTheme, CacheProvider, useTreePath } from '../../../providers';
7
- import { mergeProps, mergeStyles } from '../../../utilities';
7
+ import { Colour, mergeProps, mergeStyles } from '../../../utilities';
8
8
  import { Button } from '../../Atoms/Button';
9
9
  import { useRef, useEffect, useMemo, useState } from 'preact/hooks';
10
10
  import { MessageUser } from './MessageUser';
@@ -13,16 +13,27 @@ import { SuggestedQuestions } from './SuggestedQuestions';
13
13
  import { ChatAttachmentContext } from './ChatAttachmentContext';
14
14
  import { Image } from '../../Atoms/Image';
15
15
  import { ChatLoadingIndicator } from '../../Atoms/ChatLoadingIndicator';
16
- import { Dropdown, Icon, Overlay, useMediaQuery } from '../../..';
16
+ import { Dropdown, FacetSlider, Icon, Overlay, useMediaQuery } from '../../..';
17
17
  import { ChatInspirationResultMessage } from '../../Molecules/ChatInspirationResultMessage';
18
18
  import { ChatProductComparisonMessage } from '../../Molecules/ChatProductComparisonMessage/ChatProductComparisonMessage';
19
19
  import { ChatProductQueryMessage } from '../../Molecules/ChatProductQueryMessage/ChatProductQueryMessage';
20
- const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
21
- const colorPrimary = '#253B80';
20
+ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight, primaryColorBg, primaryColorFg, primaryAccentColorBg, primaryAccentColorFg, secondaryAccentColorBg, secondaryAccentColorFg, position, }) => {
21
+ const isRight = position === 'right';
22
+ // horizontal anchor for the root + bubble + suggested questions
23
+ const horizontalAnchor = isRight ? 'right' : 'left';
24
+ const colorPrimary = primaryColorBg;
25
+ const colorPrimaryText = primaryColorFg;
26
+ const colorPrimaryHover = new Colour(primaryColorBg).darkenHex();
27
+ const colorPrimaryAccent = primaryAccentColorBg;
28
+ const colorPrimaryAccentText = primaryAccentColorFg;
29
+ const colorPrimaryAccentHover = new Colour(primaryAccentColorBg).darkenHex();
30
+ const colorSecondaryAccent = secondaryAccentColorBg;
31
+ const colorSecondaryAccentText = secondaryAccentColorFg;
32
+ const colorSecondaryAccentHover = new Colour(secondaryAccentColorBg).darkenHex();
22
33
  const offsetValue = offset !== undefined ? (typeof offset === 'number' ? `${offset}px` : offset) : undefined;
23
34
  return css({
24
35
  position: 'fixed',
25
- left: '20px',
36
+ [horizontalAnchor]: '20px',
26
37
  bottom: '20px',
27
38
  zIndex: 1002,
28
39
  color: '#333',
@@ -40,7 +51,7 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
40
51
  flexDirection: 'column',
41
52
  justifyContent: 'flex-end',
42
53
  overflow: 'hidden',
43
- order: 1,
54
+ order: isRight ? 2 : 1,
44
55
  '.ss__chat__header__title': {
45
56
  gap: '22px',
46
57
  },
@@ -60,7 +71,7 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
60
71
  display: 'flex',
61
72
  flexDirection: 'column',
62
73
  overflow: 'hidden',
63
- order: 2,
74
+ order: isRight ? 1 : 2,
64
75
  ...(mobile
65
76
  ? {
66
77
  position: 'absolute',
@@ -121,7 +132,7 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
121
132
  background: 'none',
122
133
  color: 'inherit',
123
134
  '&:not(.ss__button--disabled):hover': {
124
- background: '#dfeaf6',
135
+ background: new Colour(colorPrimary).lightenHex(0.85),
125
136
  },
126
137
  svg: {
127
138
  fill: colorPrimary,
@@ -131,12 +142,12 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
131
142
  '.ss__chat__bubble': {
132
143
  position: mobile ? 'fixed' : 'absolute',
133
144
  bottom: mobile ? '20px' : 0,
134
- left: mobile ? '20px' : 0,
145
+ [horizontalAnchor]: mobile ? '20px' : 0,
135
146
  width: '60px',
136
147
  height: '60px',
137
148
  borderRadius: '50%',
138
149
  background: colorPrimary,
139
- color: 'white',
150
+ color: colorPrimaryText,
140
151
  display: 'flex',
141
152
  alignItems: 'center',
142
153
  justifyContent: 'center',
@@ -145,20 +156,20 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
145
156
  boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)',
146
157
  zIndex: 10,
147
158
  '&:hover': {
148
- background: '#0052a3',
159
+ background: colorPrimaryHover,
149
160
  transform: 'scale(1.05)',
150
161
  },
151
162
  '.ss__icon': {
152
163
  height: '33px',
153
164
  width: '33px',
154
- fill: '#fff',
155
- stroke: '#fff',
165
+ fill: colorPrimaryText,
166
+ stroke: colorPrimaryText,
156
167
  },
157
168
  },
158
169
  '.ss__chat__suggested-questions': {
159
170
  position: mobile ? 'fixed' : 'absolute',
160
171
  bottom: mobile ? '90px' : '70px',
161
- left: mobile ? '20px' : 0,
172
+ [horizontalAnchor]: mobile ? '20px' : 0,
162
173
  display: 'flex',
163
174
  flexDirection: 'column',
164
175
  gap: '8px',
@@ -197,7 +208,10 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
197
208
  height: offsetValue ? `calc(100% - ${offsetValue})` : '100%',
198
209
  }
199
210
  : {
200
- width: hasSideChat ? 'calc(100vw - 60px)' : 'auto',
211
+ // Hug the content width — two 600px panels + 12px gap — so the
212
+ // container stays anchored to its side instead of stretching to
213
+ // the opposite viewport edge when the side chat opens.
214
+ width: hasSideChat ? 'min(calc(100vw - 60px), 1212px)' : 'auto',
201
215
  maxWidth: 'calc(100vw - 60px)',
202
216
  maxHeight: 'calc(100vh - 40px)',
203
217
  }),
@@ -212,7 +226,7 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
212
226
  fontSize: '16px',
213
227
  position: 'relative',
214
228
  padding: '10px 15px',
215
- color: '#fff',
229
+ color: colorPrimaryText,
216
230
  background: colorPrimary,
217
231
  borderTopLeftRadius: mobile ? 0 : '12px',
218
232
  borderTopRightRadius: mobile ? 0 : '12px',
@@ -246,8 +260,8 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
246
260
  backgroundColor: 'transparent',
247
261
  },
248
262
  svg: {
249
- fill: '#fff',
250
- stroke: '#fff',
263
+ fill: colorPrimaryText,
264
+ stroke: colorPrimaryText,
251
265
  },
252
266
  },
253
267
  },
@@ -302,6 +316,8 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
302
316
  display: 'flex',
303
317
  flexDirection: 'column-reverse',
304
318
  gap: 5,
319
+ maxHeight: '300px',
320
+ overflowY: 'auto',
305
321
  '.ss__chat__history__chats__chat': {
306
322
  '.ss__button': {
307
323
  borderRadius: '12px',
@@ -325,16 +341,16 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
325
341
  alignItems: 'center',
326
342
  gap: '10px',
327
343
  padding: '8px 15px',
328
- background: '#40528e',
329
- color: '#fff',
344
+ background: new Colour(colorPrimary).darkenHex(0.1),
345
+ color: colorPrimaryText,
330
346
  fontSize: '14px',
331
347
  '.ss__chat__session-feedback__icon': {
332
348
  flex: '0 0 auto',
333
349
  display: 'flex',
334
350
  alignItems: 'center',
335
351
  svg: {
336
- fill: '#fff',
337
- stroke: '#fff',
352
+ fill: colorPrimaryText,
353
+ stroke: colorPrimaryText,
338
354
  },
339
355
  },
340
356
  '.ss__chat__session-feedback__label': {
@@ -343,25 +359,32 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
343
359
  },
344
360
  '.ss__chat__session-feedback__actions': {
345
361
  display: 'flex',
346
- gap: '10px',
362
+ gap: '6px',
347
363
  alignItems: 'center',
348
364
  '> span': {
349
365
  display: 'flex',
350
366
  alignItems: 'center',
351
367
  justifyContent: 'center',
352
- },
353
- svg: {
354
368
  cursor: 'pointer',
355
- fill: '#fff',
356
- stroke: '#fff',
357
- opacity: 0.7,
369
+ opacity: 0.85,
370
+ padding: '4px',
371
+ borderRadius: '4px',
372
+ border: '1px solid transparent',
373
+ transition: 'opacity 0.15s ease, border-color 0.15s ease',
358
374
  '&:hover': {
359
375
  opacity: 1,
360
376
  },
361
377
  },
362
- '&.ss__chat__session-feedback__actions--rated svg': {
378
+ svg: {
379
+ fill: colorPrimaryText,
380
+ stroke: colorPrimaryText,
381
+ },
382
+ '&.ss__chat__session-feedback__actions--rated > span': {
363
383
  opacity: 1,
364
384
  cursor: 'default',
385
+ '&:hover': {
386
+ borderColor: 'transparent',
387
+ },
365
388
  },
366
389
  },
367
390
  '.ss__chat__session-feedback__close': {
@@ -369,13 +392,14 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
369
392
  display: 'flex',
370
393
  alignItems: 'center',
371
394
  cursor: 'pointer',
395
+ opacity: 0.85,
396
+ transition: 'opacity 0.15s ease',
397
+ '&:hover': {
398
+ opacity: 1,
399
+ },
372
400
  svg: {
373
- fill: '#fff',
374
- stroke: '#fff',
375
- opacity: 0.7,
376
- '&:hover': {
377
- opacity: 1,
378
- },
401
+ fill: colorPrimaryText,
402
+ stroke: colorPrimaryText,
379
403
  },
380
404
  },
381
405
  },
@@ -385,13 +409,25 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
385
409
  overflow: 'hidden',
386
410
  display: 'flex',
387
411
  flexDirection: 'column',
388
- border: '1px solid #0066cc',
412
+ border: `1px solid ${colorPrimary}`,
389
413
  borderTop: 'none',
390
414
  marginTop: '-1px',
391
415
  borderBottomRightRadius: mobile ? 0 : '12px',
392
416
  borderBottomLeftRadius: mobile ? 0 : '12px',
393
417
  flexGrow: 1,
418
+ position: 'relative',
394
419
  '.ss__chat__content__header': {
420
+ // In overlay mode (only applied when messages overflow), the comparisons
421
+ // tray floats over the messages so toggling it doesn't shift the layout
422
+ // or push the latest message out of view. When messages don't overflow,
423
+ // the tray stays in flow so older messages remain reachable above it.
424
+ '&.ss__chat__content__header--overlay': {
425
+ position: 'absolute',
426
+ top: 0,
427
+ left: 0,
428
+ right: 0,
429
+ zIndex: 20,
430
+ },
395
431
  // '.ss__chat__attachments': {
396
432
  // '.ss__chat__attachment': {
397
433
  // borderRadius: 0,
@@ -403,7 +439,7 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
403
439
  display: 'flex',
404
440
  flexDirection: 'column',
405
441
  gap: '1em',
406
- background: '#f4f4ff',
442
+ background: new Colour(colorPrimary).lightenHex(0.95),
407
443
  padding: '1em',
408
444
  '.ss__chat__content__header__comparisons__header': {
409
445
  display: 'flex',
@@ -418,7 +454,7 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
418
454
  background: colorPrimary,
419
455
  borderRadius: '50%',
420
456
  padding: '5px',
421
- fill: '#fff',
457
+ fill: colorPrimaryText,
422
458
  height: '25px',
423
459
  width: '25px',
424
460
  },
@@ -491,11 +527,11 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
491
527
  borderRadius: '1em',
492
528
  padding: '0.5em 1em',
493
529
  background: colorPrimary,
494
- color: '#fff',
530
+ color: colorPrimaryText,
495
531
  textAlign: 'center',
496
532
  svg: {
497
- fill: '#fff',
498
- stroke: '#fff',
533
+ fill: colorPrimaryText,
534
+ stroke: colorPrimaryText,
499
535
  },
500
536
  },
501
537
  },
@@ -504,6 +540,7 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
504
540
  },
505
541
  '.ss__chat__messages': {
506
542
  flex: '1 1 auto',
543
+ overflowY: 'auto',
507
544
  overscrollBehavior: 'contain',
508
545
  margin: 0,
509
546
  maxHeight: '100%',
@@ -522,7 +559,7 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
522
559
  width: 'fit-content',
523
560
  padding: '6px 16px',
524
561
  background: colorPrimary,
525
- color: '#fff',
562
+ color: colorPrimaryText,
526
563
  borderRadius: '999px',
527
564
  fontSize: '13px',
528
565
  fontWeight: 500,
@@ -530,11 +567,11 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
530
567
  boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)',
531
568
  zIndex: 5,
532
569
  '&:hover': {
533
- background: '#1a2a5c',
570
+ background: colorPrimaryHover,
534
571
  },
535
572
  svg: {
536
- fill: '#fff',
537
- stroke: '#fff',
573
+ fill: colorPrimaryText,
574
+ stroke: colorPrimaryText,
538
575
  },
539
576
  },
540
577
  '.ss__chat__message': {
@@ -563,7 +600,7 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
563
600
  '.ss__chat__welcome': {
564
601
  display: 'flex',
565
602
  flexDirection: 'column',
566
- height: '100%',
603
+ minHeight: '100%',
567
604
  '.ss__chat__welcome__message': {
568
605
  fontSize: '15px',
569
606
  lineHeight: 1.5,
@@ -586,8 +623,8 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
586
623
  flexDirection: 'column',
587
624
  gap: '6px',
588
625
  padding: '8px 10px',
589
- background: '#f4f4ff',
590
- border: '1px solid #e3e3f5',
626
+ background: new Colour(colorPrimary).lightenHex(0.95),
627
+ border: `1px solid ${new Colour(colorPrimary).lightenHex(0.85)}`,
591
628
  borderRadius: '8px',
592
629
  },
593
630
  '.ss__chat__actions--title': {
@@ -607,7 +644,7 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
607
644
  fontSize: '14px',
608
645
  color: '#333',
609
646
  '&:hover': {
610
- background: '#f9f9ff',
647
+ background: new Colour(colorPrimary).lightenHex(0.97),
611
648
  },
612
649
  },
613
650
  },
@@ -707,8 +744,8 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
707
744
  visibility: 'visible',
708
745
  },
709
746
  '.ss__button': {
710
- background: '#253B80',
711
- color: '#fff',
747
+ background: colorPrimary,
748
+ color: colorPrimaryText,
712
749
  border: 'none',
713
750
  borderRadius: '999px',
714
751
  padding: '2px 10px',
@@ -716,7 +753,7 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
716
753
  lineHeight: '1.4',
717
754
  cursor: 'pointer',
718
755
  '&:not(.ss__button--disabled):hover': {
719
- background: '#1a2a5c',
756
+ background: colorPrimaryHover,
720
757
  },
721
758
  '&.ss__button--disabled': {
722
759
  opacity: 0.5,
@@ -739,8 +776,8 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
739
776
  flexDirection: 'column',
740
777
  gap: '6px',
741
778
  padding: '8px 10px',
742
- background: '#f4f4ff',
743
- border: '1px solid #e3e3f5',
779
+ background: new Colour(colorPrimary).lightenHex(0.95),
780
+ border: `1px solid ${new Colour(colorPrimary).lightenHex(0.85)}`,
744
781
  borderRadius: '8px',
745
782
  '.ss__chat__attachment-context__label': {
746
783
  fontSize: '14px',
@@ -876,20 +913,39 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
876
913
  flexDirection: 'column',
877
914
  gap: '8px',
878
915
  flexShrink: 0,
916
+ position: 'relative',
879
917
  ...(mobile
880
918
  ? {
881
- position: 'relative',
882
919
  zIndex: 11,
883
920
  background: '#fff',
884
921
  }
885
922
  : {}),
923
+ '.ss__chat__content__footer__drop-overlay': {
924
+ position: 'absolute',
925
+ inset: 0,
926
+ display: 'flex',
927
+ alignItems: 'center',
928
+ justifyContent: 'center',
929
+ gap: '8px',
930
+ borderRadius: '6px',
931
+ border: `2px dashed ${colorPrimary}`,
932
+ backgroundColor: new Colour(colorPrimary).lightenHex(0.9),
933
+ color: colorPrimary,
934
+ fontWeight: 'bold',
935
+ pointerEvents: 'none',
936
+ zIndex: 12,
937
+ svg: {
938
+ fill: colorPrimary,
939
+ stroke: colorPrimary,
940
+ },
941
+ },
886
942
  },
887
943
  '.ss__chat__topic-drift': {
888
944
  display: 'flex',
889
945
  alignItems: 'center',
890
946
  gap: '12px',
891
947
  padding: '12px 16px',
892
- border: '1px solid #93C5FD',
948
+ border: `1px solid ${new Colour(colorPrimary).lightenHex(0.6)}`,
893
949
  borderRadius: '8px',
894
950
  backgroundColor: '#fff',
895
951
  '.ss__chat__topic-drift__icon--info': {
@@ -919,7 +975,7 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
919
975
  '.ss__chat__topic-drift__button': {
920
976
  flex: '0 0 auto',
921
977
  backgroundColor: colorPrimary,
922
- color: '#fff',
978
+ color: colorPrimaryText,
923
979
  borderRadius: '6px',
924
980
  padding: '8px 14px',
925
981
  fontSize: '13px',
@@ -927,7 +983,7 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
927
983
  cursor: 'pointer',
928
984
  whiteSpace: 'nowrap',
929
985
  '&:not(.ss__button--disabled):hover': {
930
- background: '#1a2a5c',
986
+ background: colorPrimaryHover,
931
987
  },
932
988
  },
933
989
  '.ss__chat__topic-drift__icon--close': {
@@ -965,7 +1021,7 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
965
1021
  },
966
1022
  '&:focus': {
967
1023
  outline: 'none',
968
- borderColor: '#0066cc',
1024
+ borderColor: colorPrimary,
969
1025
  },
970
1026
  },
971
1027
  '.ss__button': {
@@ -981,18 +1037,20 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
981
1037
  '.ss__chat__input__actions': {
982
1038
  '.ss__button': {
983
1039
  backgroundColor: colorPrimary,
1040
+ color: colorPrimaryText,
984
1041
  borderRadius: '50%',
985
1042
  height: '3em',
986
1043
  width: '3em',
987
1044
  cursor: 'pointer',
988
1045
  justifyContent: 'center',
1046
+ boxSizing: 'border-box',
989
1047
  '&:disabled': {
990
1048
  opacity: 0.5,
991
1049
  cursor: 'not-allowed',
992
1050
  },
993
1051
  svg: {
994
- fill: '#fff',
995
- stroke: '#fff',
1052
+ fill: colorPrimaryText,
1053
+ stroke: colorPrimaryText,
996
1054
  },
997
1055
  },
998
1056
  },
@@ -1003,6 +1061,55 @@ const defaultStyles = ({ mobile, offset, hasSideChat, footerHeight }) => {
1003
1061
  textAlign: 'center',
1004
1062
  flexShrink: 0,
1005
1063
  },
1064
+ '.ss__chat__result__image__icons__icon--cart': {
1065
+ backgroundColor: `${colorPrimaryAccent} !important`,
1066
+ svg: {
1067
+ fill: `${colorPrimaryAccentText} !important`,
1068
+ stroke: `${colorPrimaryAccentText} !important`,
1069
+ },
1070
+ },
1071
+ '.ss__chat-product-query-message__header__product__actions__add-to-cart .ss__button': {
1072
+ background: `${colorPrimaryAccent} !important`,
1073
+ color: `${colorPrimaryAccentText} !important`,
1074
+ '&:not(.ss__button--disabled):hover': {
1075
+ background: `${colorPrimaryAccentHover} !important`,
1076
+ },
1077
+ },
1078
+ // Secondary accent — applied to the productQuery button (ChatResult inquire icon)
1079
+ // and the "Show similar" button (ChatProductQueryMessage). Both elements live in
1080
+ // child components but their styling overrides are intentionally scoped here so
1081
+ // the Chat-level theme can drive them.
1082
+ '.ss__chat__result__image__icons__icon--inquire': {
1083
+ backgroundColor: `${colorSecondaryAccent} !important`,
1084
+ svg: {
1085
+ fill: `${colorSecondaryAccentText} !important`,
1086
+ stroke: `${colorSecondaryAccentText} !important`,
1087
+ },
1088
+ },
1089
+ '.ss__chat-product-query-message__header__product__actions__show-similar .ss__button, .ss__chat-product-query-message__header__product__actions__discuss-product .ss__button': {
1090
+ background: `${colorSecondaryAccent} !important`,
1091
+ color: `${colorSecondaryAccentText} !important`,
1092
+ border: `1px solid ${colorSecondaryAccentText} !important`,
1093
+ '&:not(.ss__button--disabled):hover': {
1094
+ background: `${colorSecondaryAccentHover} !important`,
1095
+ },
1096
+ },
1097
+ '.ss__chat__expired': {
1098
+ display: 'flex',
1099
+ flexDirection: 'column',
1100
+ alignItems: 'center',
1101
+ justifyContent: 'center',
1102
+ gap: '12px',
1103
+ padding: '16px',
1104
+ textAlign: 'center',
1105
+ color: '#6A7282',
1106
+ '.ss__chat__expired__message': {
1107
+ fontSize: '14px',
1108
+ },
1109
+ '.ss__chat__expired__button': {
1110
+ alignSelf: 'center',
1111
+ },
1112
+ },
1006
1113
  },
1007
1114
  });
1008
1115
  };
@@ -1014,11 +1121,26 @@ export const Chat = observer((properties) => {
1014
1121
  treePath: globalTreePath,
1015
1122
  logo: 'https://cdn.athoscommerce.net/snap/images/Athos%20Commerce_Icon_white.svg',
1016
1123
  title: 'Athos Conversational Assistant',
1017
- subtitle: 'Ready to assist you',
1124
+ subtitle: 'Your Guided Discovery Expert',
1018
1125
  multiselectFacets: true,
1126
+ disableBubbleSuggestedQuestions: false,
1127
+ primaryColorBg: '#253B80',
1128
+ primaryColorFg: '#fff',
1129
+ primaryAccentColorBg: '#feeeae',
1130
+ primaryAccentColorFg: '#000000',
1131
+ secondaryAccentColorBg: '#000000',
1132
+ secondaryAccentColorFg: '#ffffff',
1133
+ position: 'right',
1019
1134
  };
1020
1135
  let props = mergeProps('facets', globalTheme, defaultProps, properties);
1021
- const { className, internalClassName, controller, logo, title, subtitle, offset, multiselectFacets } = props;
1136
+ const { className, internalClassName, controller, logo, title, subtitle, offset, multiselectFacets, disableBubbleSuggestedQuestions, primaryColorBg, primaryColorFg, primaryAccentColorBg, primaryAccentColorFg, secondaryAccentColorBg, secondaryAccentColorFg, position, } = props;
1137
+ const colorPrimary = primaryColorBg;
1138
+ const colorPrimaryText = primaryColorFg;
1139
+ const colorPrimaryAccent = primaryAccentColorBg;
1140
+ const colorPrimaryAccentText = primaryAccentColorFg;
1141
+ const colorSecondaryAccent = secondaryAccentColorBg;
1142
+ const colorSecondaryAccentText = secondaryAccentColorFg;
1143
+ const chatPosition = position === 'right' ? 'right' : 'left';
1022
1144
  const { store } = controller;
1023
1145
  const themeDefaults = {
1024
1146
  components: {},
@@ -1032,7 +1154,9 @@ export const Chat = observer((properties) => {
1032
1154
  const fileInputRef = useRef(null);
1033
1155
  const messagesEndRef = useRef(null);
1034
1156
  const messagesContainerRef = useRef(null);
1157
+ const headerRef = useRef(null);
1035
1158
  const footerRef = useRef(null);
1159
+ const chatRef = useRef(null);
1036
1160
  // Swipe-to-dismiss state for mobile secondary panel
1037
1161
  const secondaryRef = useRef(null);
1038
1162
  const swipeStartY = useRef(0);
@@ -1044,13 +1168,25 @@ export const Chat = observer((properties) => {
1044
1168
  const [mobileProductInfoOpen, setMobileProductInfoOpen] = useState(false);
1045
1169
  const [footerHeight, setFooterHeight] = useState(0);
1046
1170
  const [showNewMessages, setShowNewMessages] = useState(false);
1171
+ const [messagesOverflow, setMessagesOverflow] = useState(false);
1047
1172
  const isNearBottomRef = useRef(true);
1173
+ // Drag-and-drop image upload onto the footer. `dragenter`/`dragleave` fire on every
1174
+ // child crossing, so use a counter to know when the pointer has truly left the footer.
1175
+ const [isDraggingFile, setIsDraggingFile] = useState(false);
1176
+ const dragCounterRef = useRef(0);
1048
1177
  const updateSwipeOffset = (value) => {
1049
1178
  swipeOffsetRef.current = value;
1050
1179
  setSwipeOffsetRaw(value);
1051
1180
  };
1052
1181
  const scrollToBottom = () => {
1053
- messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
1182
+ // Scroll the messages container directly (instead of scrollIntoView on the
1183
+ // bottom anchor) so the browser never walks up the ancestor chain. On small
1184
+ // viewports a smooth scrollIntoView could otherwise animate the document/
1185
+ // visual viewport and visually push the fixed-position chat off-screen.
1186
+ const container = messagesContainerRef.current;
1187
+ if (container) {
1188
+ container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' });
1189
+ }
1054
1190
  };
1055
1191
  // Conditional scroll: only scrolls if the user is already near the bottom.
1056
1192
  // Passed to child components (e.g. image onLoad) so they don't yank the user
@@ -1116,6 +1252,35 @@ export const Chat = observer((properties) => {
1116
1252
  container.addEventListener('scroll', handleScroll);
1117
1253
  return () => container.removeEventListener('scroll', handleScroll);
1118
1254
  }, [store.open]);
1255
+ // Track whether the messages container is overflowing so the comparisons header
1256
+ // can switch between in-flow (short conversations — top content stays reachable)
1257
+ // and overlay (overflowing — avoid layout shift when toggling the tray).
1258
+ // Hysteresis: when already in overlay, only return to in-flow if the content
1259
+ // would still fit *with* the header re-inserted — otherwise the two states
1260
+ // fight each other and the layout flickers.
1261
+ useEffect(() => {
1262
+ const container = messagesContainerRef.current;
1263
+ if (!container || typeof ResizeObserver === 'undefined')
1264
+ return;
1265
+ const update = () => {
1266
+ setMessagesOverflow((prev) => {
1267
+ const headerHeight = headerRef.current?.offsetHeight || 0;
1268
+ if (prev) {
1269
+ // Currently overlay: header isn't taking flow space. Only switch
1270
+ // back to in-flow if the content would fit with the header added.
1271
+ return container.scrollHeight > container.clientHeight - headerHeight + 1;
1272
+ }
1273
+ // Currently in-flow: header already takes space — straightforward check.
1274
+ return container.scrollHeight > container.clientHeight + 1;
1275
+ });
1276
+ };
1277
+ update();
1278
+ const observer = new ResizeObserver(update);
1279
+ observer.observe(container);
1280
+ if (headerRef.current)
1281
+ observer.observe(headerRef.current);
1282
+ return () => observer.disconnect();
1283
+ }, [store.open, visibleChatLength]);
1119
1284
  // Re-focus the chat input on desktop after a search completes.
1120
1285
  // The input is disabled while loading, which causes the browser to drop focus.
1121
1286
  // On mobile we intentionally skip this so the virtual keyboard dismisses.
@@ -1143,7 +1308,19 @@ export const Chat = observer((properties) => {
1143
1308
  ? ['inspirationResult', ...(isFirstMobileComparison ? ['productComparison'] : []), ...(mobileProductInfoOpen ? ['productQuery'] : [])]
1144
1309
  : ['inspirationResult', 'productComparison', 'productQuery'];
1145
1310
  const shouldShowSideChat = activeMessage && sideChatTypes.includes(activeMessage?.messageType) && store.currentChat?.dismissedSideChatMessageId !== activeMessage.id;
1146
- const styling = mergeStyles({ mobile: isMobile, offset, hasSideChat: !!shouldShowSideChat, footerHeight }, defaultStyles);
1311
+ const styling = mergeStyles({
1312
+ mobile: isMobile,
1313
+ offset,
1314
+ hasSideChat: !!shouldShowSideChat,
1315
+ footerHeight,
1316
+ primaryColorBg: colorPrimary,
1317
+ primaryColorFg: colorPrimaryText,
1318
+ primaryAccentColorBg: colorPrimaryAccent,
1319
+ primaryAccentColorFg: colorPrimaryAccentText,
1320
+ secondaryAccentColorBg: colorSecondaryAccent,
1321
+ secondaryAccentColorFg: colorSecondaryAccentText,
1322
+ position: chatPosition,
1323
+ }, defaultStyles);
1147
1324
  // Lock body scroll while chat is open so touch/wheel scrolls don't leak to the page behind
1148
1325
  useEffect(() => {
1149
1326
  if (!store.open)
@@ -1188,6 +1365,70 @@ export const Chat = observer((properties) => {
1188
1365
  return text.length > 50 ? `${text.slice(0, 50)}...` : text;
1189
1366
  })() }), _jsx("div", { className: "ss__chat__history__chat__button__date", children: chat.createdAt.toLocaleString() })] }) }, chat.id))) })) : null] }));
1190
1367
  const FacetButton = (props) => _jsx(Button, { icon: props.open ? 'angle-down' : 'angle-up', children: props.label });
1368
+ const getFacetButtonLabel = (facet) => {
1369
+ const base = facet.label || facet.field;
1370
+ // Slider facets show the active low-high range in the button when narrowed.
1371
+ if (facet.type === 'range') {
1372
+ const rangeFacet = facet;
1373
+ const active = rangeFacet.active;
1374
+ const range = rangeFacet.range;
1375
+ if (active && range && (active.low !== range.low || active.high !== range.high)) {
1376
+ return `${base} (${active.low ?? '*'}-${active.high ?? '*'})`;
1377
+ }
1378
+ return base;
1379
+ }
1380
+ const applied = new Set();
1381
+ // Count values marked as filtered (already applied on the server)
1382
+ (facet.values || []).forEach((value) => {
1383
+ if (value?.filtered) {
1384
+ if ('low' in value && 'high' in value) {
1385
+ applied.add(`${value.low ?? '*'}:${value.high ?? '*'}`);
1386
+ }
1387
+ else {
1388
+ applied.add(value.value);
1389
+ }
1390
+ }
1391
+ });
1392
+ // Count pending facet attachments (newly selected, not yet sent)
1393
+ (controller.store.currentChat?.attachments.items || [])
1394
+ .filter((item) => item.type === 'facet' && item.key === facet.field && (item.state === 'active' || item.state === 'attached'))
1395
+ .forEach((item) => applied.add(item.value));
1396
+ return applied.size > 0 ? `${base} (${applied.size})` : base;
1397
+ };
1398
+ // Slider dropdown grows to fill the remaining chat width from its anchor,
1399
+ // so the price range slider isn't cramped inside a 150px-wide dropdown.
1400
+ const SliderFacetDropdown = (props) => {
1401
+ const anchorRef = useRef(null);
1402
+ const [width, setWidth] = useState(null);
1403
+ useEffect(() => {
1404
+ const compute = () => {
1405
+ if (!anchorRef.current || !chatRef.current)
1406
+ return;
1407
+ const anchor = anchorRef.current.getBoundingClientRect();
1408
+ const chat = chatRef.current.getBoundingClientRect();
1409
+ const remaining = chat.right - anchor.left - 16;
1410
+ setWidth(Math.max(220, remaining));
1411
+ };
1412
+ compute();
1413
+ const ro = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(compute) : null;
1414
+ if (ro && chatRef.current)
1415
+ ro.observe(chatRef.current);
1416
+ window.addEventListener('resize', compute);
1417
+ return () => {
1418
+ if (ro)
1419
+ ro.disconnect();
1420
+ window.removeEventListener('resize', compute);
1421
+ };
1422
+ }, []);
1423
+ return (_jsx("div", { ref: anchorRef, className: "ss__chat__actions__facet ss__chat__actions__facet--range", children: _jsx(Dropdown, { usePortal: true, dropUp: true, button: _jsx(FacetButton, { label: props.label }), style: {
1424
+ '.ss__dropdown__content': {
1425
+ width: width ? `${width}px` : 'auto',
1426
+ background: 'white',
1427
+ boxShadow: '0px 3px 6px 0px rgba(0, 0, 0, 0.2)',
1428
+ borderRadius: '6px',
1429
+ },
1430
+ }, children: _jsx("div", { className: "ss__chat__actions__facet__options", children: _jsx("div", { className: "ss__chat__actions__facet__slider", style: { padding: '12px 16px' }, children: _jsx(FacetSlider, { facet: props.facet }) }) }) }) }));
1431
+ };
1191
1432
  // TODO: if starting a new chat and it's expired, this button would then disappear
1192
1433
  if (!controller.store.chatEnabled) {
1193
1434
  return _jsx(_Fragment, {});
@@ -1272,11 +1513,11 @@ export const Chat = observer((properties) => {
1272
1513
  return undefined;
1273
1514
  }
1274
1515
  }, [requestType]);
1275
- return (_jsx(CacheProvider, { children: _jsxs(_Fragment, { children: [_jsxs("div", { className: classnames('ss__chat', {
1516
+ return (_jsx(CacheProvider, { children: _jsxs(_Fragment, { children: [_jsxs("div", { ref: chatRef, className: classnames('ss__chat', {
1276
1517
  'ss__chat--open': store.open,
1277
1518
  'ss__chat--minimized': !store.open,
1278
1519
  'ss__chat--mobile': isMobile,
1279
- }, 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: () => {
1520
+ }, 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: () => {
1280
1521
  controller.openChat(question);
1281
1522
  }, 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
1282
1523
  ? {
@@ -1303,9 +1544,9 @@ export const Chat = observer((properties) => {
1303
1544
  setMobileProductInfoOpen(false);
1304
1545
  store.currentChat?.dismissSideChat();
1305
1546
  } }) })] }), _jsx("div", { className: "ss__chat__content", children: _jsx("div", { className: 'ss__chat__messages', children: {
1306
- inspirationResult: (_jsx(ChatInspirationResultMessage, { chatItem: activeMessage, controller: controller, onViewProduct: isMobile ? () => setMobileProductInfoOpen(true) : undefined })),
1547
+ inspirationResult: (_jsx(ChatInspirationResultMessage, { chatItem: activeMessage, controller: controller, onProductQuickView: isMobile ? () => setMobileProductInfoOpen(true) : undefined })),
1307
1548
  productComparison: (_jsx(ChatProductComparisonMessage, { chatItem: activeMessage, controller: controller })),
1308
- productQuery: (_jsx(ChatProductQueryMessage, { chatItem: activeMessage, controller: controller, displayFields: controller.config.settings?.displayFields })),
1549
+ productQuery: (_jsx(ChatProductQueryMessage, { chatItem: activeMessage, controller: controller, displayFields: controller.config.settings?.quickview?.displayFields, primaryColor: colorPrimary, primaryColorText: colorPrimaryText })),
1309
1550
  }[activeMessage.messageType] || null }) })] })) : null, store.open ? (_jsxs("div", { className: 'ss__chat__primary', children: [_jsxs("div", { className: 'ss__chat__header', children: [_jsxs("div", { className: "ss__chat__header__title", children: [logo ? _jsx("img", { className: "ss__chat__header__title__logo", src: logo }) : null, _jsxs("div", { className: "ss__chat__header__title__text", children: [title ? _jsx("div", { className: "ss__chat__header__title__text__primary", children: title }) : null, subtitle ? _jsx("div", { className: "ss__chat__header__title__text__secondary", children: subtitle }) : null] })] }), _jsxs("div", { className: "ss__chat__header__buttons", children: [_jsx(Button
1310
1551
  // disabled={store.currentChat?.chat && store.currentChat.chat.length <= 1}
1311
1552
  , {
@@ -1319,345 +1560,381 @@ export const Chat = observer((properties) => {
1319
1560
  systemMessages.length >= feedbackAfterMessages &&
1320
1561
  !store.currentChat?.feedbackDismissed &&
1321
1562
  (!hasRated || store.currentChat?.feedbackJustGiven);
1322
- return shouldShowFeedback ? (_jsxs("div", { className: "ss__chat__session-feedback", children: [_jsx("div", { className: "ss__chat__session-feedback__icon", children: _jsx(Icon, { icon: "chat", size: "16px" }) }), _jsx("span", { className: "ss__chat__session-feedback__label", children: hasRated ? 'Thank you for your feedback' : "How's your experience so far?" }), _jsxs("div", { className: `ss__chat__session-feedback__actions${hasRated ? ' ss__chat__session-feedback__actions--rated' : ''}`, children: [(!hasRated || currentRating === 'UP') && (_jsx("span", { onClick: () => !hasRated && controller.handleFeedback('UP'), children: _jsx(Icon, { icon: 'thumbs-up', title: 'Thumbs Up' }) })), (!hasRated || currentRating === 'DOWN') && (_jsx("span", { onClick: () => !hasRated && controller.handleFeedback('DOWN'), children: _jsx(Icon, { icon: 'thumbs-down', title: 'Thumbs Down' }) }))] }), !hasRated && (_jsx("span", { className: "ss__chat__session-feedback__close", onClick: () => {
1563
+ return shouldShowFeedback ? (_jsxs("div", { className: "ss__chat__session-feedback", children: [_jsx("div", { className: "ss__chat__session-feedback__icon", children: _jsx(Icon, { icon: "chat", size: "16px" }) }), _jsx("span", { className: "ss__chat__session-feedback__label", children: hasRated ? 'Thank you for your feedback' : "How's your experience so far?" }), _jsxs("div", { className: `ss__chat__session-feedback__actions${hasRated ? ' ss__chat__session-feedback__actions--rated' : ''}`, children: [(!hasRated || currentRating === 'UP') && (_jsx("span", { onClick: () => !hasRated && controller.handleFeedback('UP'), children: _jsx(Icon, { icon: 'thumbs-up', size: "16px", title: 'Thumbs Up' }) })), (!hasRated || currentRating === 'DOWN') && (_jsx("span", { onClick: () => !hasRated && controller.handleFeedback('DOWN'), children: _jsx(Icon, { icon: 'thumbs-down', size: "16px", title: 'Thumbs Down' }) }))] }), !hasRated && (_jsx("span", { className: "ss__chat__session-feedback__close", onClick: () => {
1323
1564
  if (store.currentChat) {
1324
1565
  store.currentChat.feedbackDismissed = true;
1325
1566
  store.currentChat.save();
1326
1567
  }
1327
1568
  }, children: _jsx(Icon, { icon: "close-thin", size: "14px" }) }))] })) : null;
1328
- })(), _jsxs("div", { className: "ss__chat__content", children: [_jsx("div", { className: "ss__chat__content__header", 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) => {
1569
+ })(), _jsxs("div", { className: "ss__chat__content", children: [_jsx("div", { ref: headerRef, className: classnames('ss__chat__content__header', {
1570
+ 'ss__chat__content__header--overlay': messagesOverflow,
1571
+ }), 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) => {
1329
1572
  const comparisonItem = store.currentChat?.comparisons.compared[index];
1330
1573
  return (_jsx("div", { className: classnames('ss__chat__content__header__comparisons__content__comparison', {
1331
1574
  'ss__chat__content__header__comparisons__content__comparison--placeholder': !comparisonItem,
1332
1575
  }), children: comparisonItem ? (_jsxs(_Fragment, { children: [_jsx(Image
1333
1576
  // onClick={() => {
1334
- // controller?.viewProduct(product as any);
1577
+ // controller?.productQuickView(product as any);
1335
1578
  // }}
1336
1579
  , {
1337
1580
  // onClick={() => {
1338
- // controller?.viewProduct(product as any);
1581
+ // controller?.productQuickView(product as any);
1339
1582
  // }}
1340
1583
  alt: (comparisonItem?.result?.display || comparisonItem?.result)?.mappings?.core?.name || '', src: (comparisonItem?.result?.display || comparisonItem?.result)?.mappings?.core?.imageUrl || '' }), _jsx("div", { className: "ss__chat__content__header__comparisons__content__comparison__remove", onClick: () => {
1341
1584
  store.currentChat?.comparisons.remove(comparisonItem.result.id);
1342
1585
  }, 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));
1343
1586
  }) }), _jsx("div", { className: classnames('ss__chat__content__header__comparisons__action', {
1344
1587
  'ss__chat__content__header__comparisons__action--hidden': (store.currentChat?.comparisons.compared.length || 0) < 2,
1345
- }), children: _jsx(Button, { onClick: () => controller.search(), icon: { icon: 'compare', title: 'Compare' }, children: "Compare" }) })] })] })) }), _jsxs("div", { className: 'ss__chat__messages', ref: messagesContainerRef, style: visibleChatLength ? { overflowY: 'auto', scrollbarGutter: 'stable' } : undefined, 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
1588
+ }), 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
1346
1589
  .filter((chatItem) => chatItem.messageType !== 'productQuery')
1347
1590
  .map((chatItem, index) => (_jsx("div", { className: `ss__chat__message ss__chat__message--${chatItem.messageType}`, children: {
1348
- user: (_jsx(MessageUser, { chatItem: chatItem, controller: controller, onViewProduct: isMobile ? () => setMobileProductInfoOpen(true) : undefined })),
1349
- text: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, onViewProduct: isMobile ? () => setMobileProductInfoOpen(true) : undefined, sideChatOpen: !!shouldShowSideChat })),
1350
- content: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, onViewProduct: isMobile ? () => setMobileProductInfoOpen(true) : undefined, sideChatOpen: !!shouldShowSideChat })),
1351
- productSearchResult: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, onViewProduct: isMobile ? () => setMobileProductInfoOpen(true) : undefined, sideChatOpen: !!shouldShowSideChat })),
1352
- inspirationResult: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, onViewProduct: isMobile ? () => setMobileProductInfoOpen(true) : undefined, sideChatOpen: !!shouldShowSideChat })),
1353
- productAnswer: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, onViewProduct: isMobile ? () => setMobileProductInfoOpen(true) : undefined, sideChatOpen: !!shouldShowSideChat })),
1591
+ user: (_jsx(MessageUser, { chatItem: chatItem, controller: controller, onProductQuickView: isMobile ? () => setMobileProductInfoOpen(true) : undefined, primaryColor: colorPrimary, primaryColorText: colorPrimaryText })),
1592
+ text: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, onProductQuickView: isMobile ? () => setMobileProductInfoOpen(true) : undefined, sideChatOpen: !!shouldShowSideChat, primaryColor: colorPrimary, primaryColorText: colorPrimaryText })),
1593
+ content: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, onProductQuickView: isMobile ? () => setMobileProductInfoOpen(true) : undefined, sideChatOpen: !!shouldShowSideChat, primaryColor: colorPrimary, primaryColorText: colorPrimaryText })),
1594
+ productSearchResult: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, onProductQuickView: isMobile ? () => setMobileProductInfoOpen(true) : undefined, sideChatOpen: !!shouldShowSideChat, primaryColor: colorPrimary, primaryColorText: colorPrimaryText })),
1595
+ inspirationResult: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, onProductQuickView: isMobile ? () => setMobileProductInfoOpen(true) : undefined, sideChatOpen: !!shouldShowSideChat, primaryColor: colorPrimary, primaryColorText: colorPrimaryText })),
1596
+ productAnswer: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, onProductQuickView: isMobile ? () => setMobileProductInfoOpen(true) : undefined, sideChatOpen: !!shouldShowSideChat, primaryColor: colorPrimary, primaryColorText: colorPrimaryText })),
1354
1597
  productComparison: (() => {
1355
1598
  // On mobile, only the first productComparison in the session
1356
1599
  // auto-opens the secondary panel. Subsequent ones get a
1357
1600
  // prominent "Show Details" button instead.
1358
1601
  const comparisonMessages = (store.currentChat?.chat || []).filter((m) => m.messageType === 'productComparison');
1359
1602
  const isFirst = comparisonMessages.length > 0 && comparisonMessages[0].id === chatItem.id;
1360
- return (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, onViewProduct: isMobile ? () => setMobileProductInfoOpen(true) : undefined, showDetailsButton: isMobile && !isFirst, sideChatOpen: !!shouldShowSideChat }));
1603
+ return (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, onProductQuickView: isMobile ? () => setMobileProductInfoOpen(true) : undefined, showDetailsButton: isMobile && !isFirst, sideChatOpen: !!shouldShowSideChat, primaryColor: colorPrimary, primaryColorText: colorPrimaryText }));
1361
1604
  })(),
1362
- productRecommendation: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, onViewProduct: isMobile ? () => setMobileProductInfoOpen(true) : undefined, sideChatOpen: !!shouldShowSideChat })),
1605
+ productRecommendation: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, onProductQuickView: isMobile ? () => setMobileProductInfoOpen(true) : undefined, sideChatOpen: !!shouldShowSideChat, primaryColor: colorPrimary, primaryColorText: colorPrimaryText })),
1363
1606
  actions: _jsx(SuggestedQuestions, { actions: chatItem.actions, controller: controller }),
1364
- errorResponse: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, sideChatOpen: !!shouldShowSideChat })),
1365
- topicDrift: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, sideChatOpen: !!shouldShowSideChat })),
1607
+ errorResponse: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, sideChatOpen: !!shouldShowSideChat, primaryColor: colorPrimary, primaryColorText: colorPrimaryText })),
1608
+ topicDrift: (_jsx(MessageText, { chatItem: chatItem, controller: controller, scrollToBottom: scrollToBottomIfNear, sideChatOpen: !!shouldShowSideChat, primaryColor: colorPrimary, primaryColorText: colorPrimaryText })),
1366
1609
  productQuery: null,
1367
- }[chatItem.messageType] || null }, index))), _jsx("div", { className: "ss__chat__messages__end", ref: messagesEndRef }), showNewMessages && (_jsxs("div", { className: "ss__chat__new-messages", onClick: () => {
1610
+ }[chatItem.messageType] || null }, index))), _jsx("div", { className: "ss__chat__messages__end", ref: messagesEndRef }), showNewMessages && messagesOverflow && (_jsxs("div", { className: "ss__chat__new-messages", onClick: () => {
1368
1611
  scrollToBottom();
1369
1612
  setShowNewMessages(false);
1370
- }, 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: () => {
1371
- controller.store.createChat();
1372
- }, 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: {
1373
- 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')
1374
- ? ' ss__chat__actions__facets-apply--active'
1375
- : ''}`, children: _jsx(Button, { onClick: () => {
1376
- controller.search();
1377
- }, children: "Apply" }) }))] }), _jsx("div", { className: "ss__chat__actions--facets", children: action.data.slice(0, 10).map((facet, idx) => {
1378
- if (!facet.values?.length)
1379
- return null;
1380
- 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.label || facet.field }), style: {
1381
- '.ss__dropdown__content': {
1382
- width: '150px',
1383
- maxHeight: '200px',
1384
- overflowY: 'auto',
1385
- background: 'white',
1386
- boxShadow: '0px 3px 6px 0px rgba(0, 0, 0, 0.2)',
1387
- borderRadius: '6px',
1388
- ...(multiselectFacets
1389
- ? {
1390
- '.ss__chat__actions__facet__option--checkbox': {
1613
+ }, children: [_jsx(Icon, { icon: "angle-down", size: "12px" }), _jsx("span", { children: "New messages" })] }))] }), _jsx(ChatLoadingIndicator, { loading: store.loading, verbs: loadingVerbs }), !store.currentChat?.isExpired ? ((() => {
1614
+ const dropEnabled = store.features.imageSearch.enabled && !store.loading && !store.blocked && !store.currentChat?.sessionLimitReached;
1615
+ const hasFiles = (e) => Array.from(e.dataTransfer?.types || []).includes('Files');
1616
+ return (_jsxs("div", { className: "ss__chat__content__footer", ref: footerRef, onDragEnter: (e) => {
1617
+ if (!dropEnabled || !hasFiles(e))
1618
+ return;
1619
+ e.preventDefault();
1620
+ dragCounterRef.current += 1;
1621
+ setIsDraggingFile(true);
1622
+ }, onDragOver: (e) => {
1623
+ if (!dropEnabled || !hasFiles(e))
1624
+ return;
1625
+ // Required to allow the drop event to fire
1626
+ e.preventDefault();
1627
+ if (e.dataTransfer)
1628
+ e.dataTransfer.dropEffect = 'copy';
1629
+ }, onDragLeave: (e) => {
1630
+ if (!dropEnabled || !hasFiles(e))
1631
+ return;
1632
+ dragCounterRef.current = Math.max(0, dragCounterRef.current - 1);
1633
+ if (dragCounterRef.current === 0)
1634
+ setIsDraggingFile(false);
1635
+ }, onDrop: async (e) => {
1636
+ if (!dropEnabled || !hasFiles(e))
1637
+ return;
1638
+ e.preventDefault();
1639
+ dragCounterRef.current = 0;
1640
+ setIsDraggingFile(false);
1641
+ const dropped = Array.from(e.dataTransfer?.files || []);
1642
+ const images = dropped.filter((f) => f.type.startsWith('image/'));
1643
+ if (images.length === 0)
1644
+ return;
1645
+ // Reconstruct a FileList — controller.upload expects FileList | null
1646
+ const dt = new DataTransfer();
1647
+ images.forEach((f) => dt.items.add(f));
1648
+ await controller.upload(dt.files);
1649
+ }, 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: () => {
1650
+ controller.store.createChat();
1651
+ }, children: "New Session" })] })), store.facets && store.facets.length > 0 && (_jsx("div", { className: 'ss__chat__actions', children: _jsxs("div", { className: "ss__chat__actions__wrap", children: [_jsxs("div", { className: "ss__chat__actions__facets-header", children: [_jsx("div", { className: "ss__chat__actions--title", children: "Filter by:" }), multiselectFacets && store.hasPendingFacetChanges && (_jsx("div", { className: 'ss__chat__actions__facets-apply ss__chat__actions__facets-apply--active', children: _jsx(Button, { onClick: () => {
1652
+ controller.search();
1653
+ }, children: "Apply" }) }))] }), _jsx("div", { className: "ss__chat__actions--facets", children: store.facets.slice(0, 10).map((facet, idx) => {
1654
+ // range/slider facets render via FacetSlider; everything else needs at least one value to be useful
1655
+ if (facet.type !== 'range' && !facet.values?.length)
1656
+ return null;
1657
+ if (facet.type === 'range') {
1658
+ return _jsx(SliderFacetDropdown, { facet: facet, label: getFacetButtonLabel(facet) }, idx);
1659
+ }
1660
+ 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: {
1661
+ '.ss__dropdown__content': {
1662
+ width: '150px',
1663
+ maxHeight: '200px',
1664
+ overflowY: 'auto',
1665
+ background: 'white',
1666
+ boxShadow: '0px 3px 6px 0px rgba(0, 0, 0, 0.2)',
1667
+ borderRadius: '6px',
1668
+ ...(multiselectFacets
1669
+ ? {
1670
+ '.ss__chat__actions__facet__option--checkbox': {
1671
+ display: 'flex',
1672
+ alignItems: 'center',
1673
+ gap: '8px',
1674
+ padding: '6px 10px',
1675
+ cursor: 'pointer',
1676
+ width: '100%',
1677
+ boxSizing: 'border-box',
1678
+ background: '#fff',
1679
+ border: 'none',
1680
+ borderBottom: '1px solid #f0f0f0',
1681
+ '&:hover': {
1682
+ background: new Colour(colorPrimary).lightenHex(0.95),
1683
+ },
1684
+ '&:last-child': {
1685
+ borderBottom: 'none',
1686
+ },
1687
+ '.ss__chat__actions__facet__option__checkbox': {
1688
+ width: '16px',
1689
+ height: '16px',
1690
+ borderRadius: '3px',
1691
+ border: '2px solid #ccc',
1391
1692
  display: 'flex',
1392
1693
  alignItems: 'center',
1393
- gap: '8px',
1394
- padding: '6px 10px',
1395
- cursor: 'pointer',
1396
- width: '100%',
1397
- boxSizing: 'border-box',
1398
- background: '#fff',
1399
- border: 'none',
1400
- borderBottom: '1px solid #f0f0f0',
1401
- '&:hover': {
1402
- background: '#f4f4ff',
1403
- },
1404
- '&:last-child': {
1405
- borderBottom: 'none',
1406
- },
1407
- '.ss__chat__actions__facet__option__checkbox': {
1408
- width: '16px',
1409
- height: '16px',
1410
- borderRadius: '3px',
1411
- border: '2px solid #ccc',
1412
- display: 'flex',
1413
- alignItems: 'center',
1414
- justifyContent: 'center',
1415
- flexShrink: 0,
1416
- '&.ss__chat__actions__facet__option__checkbox--checked': {
1417
- background: '#253B80',
1418
- borderColor: '#253B80',
1694
+ justifyContent: 'center',
1695
+ flexShrink: 0,
1696
+ '&.ss__chat__actions__facet__option__checkbox--checked': {
1697
+ background: colorPrimary,
1698
+ borderColor: colorPrimary,
1699
+ color: colorPrimaryText,
1700
+ svg: {
1701
+ fill: colorPrimaryText,
1702
+ stroke: colorPrimaryText,
1419
1703
  },
1420
1704
  },
1421
- '.ss__chat__actions__facet__option__label': {
1422
- fontSize: '14px',
1423
- color: '#333',
1424
- flex: '1 1 auto',
1425
- textAlign: 'left',
1426
- },
1427
1705
  },
1428
- }
1429
- : {
1430
- '.ss__button': {
1431
- width: '100%',
1432
- border: 'none',
1433
- borderRadius: 0,
1434
- boxSizing: 'border-box',
1435
- background: '#fff',
1436
- '&:hover': {
1437
- background: '#f4f4ff',
1438
- },
1706
+ '.ss__chat__actions__facet__option__label': {
1707
+ fontSize: '14px',
1708
+ color: '#333',
1709
+ flex: '1 1 auto',
1710
+ textAlign: 'left',
1439
1711
  },
1440
- }),
1441
- },
1442
- }, children: _jsx("div", { className: "ss__chat__actions__facet__options", children: facet.type === 'range-buckets'
1443
- ? facet.values.map((option) => {
1444
- const optionValue = `${option.low ?? '*'}:${option.high ?? '*'}`;
1445
- if (multiselectFacets) {
1446
- const isSelected = controller.store.isFacetSelected(facet.field, optionValue);
1447
- return (_jsxs("div", { className: "ss__chat__actions__facet__option--checkbox", onClick: () => {
1448
- if (isSelected) {
1449
- controller.store.removeFacet(facet.field, optionValue);
1450
- }
1451
- else {
1452
- controller.store.addFacet({
1453
- key: facet.field,
1454
- facetLabel: facet.label,
1455
- value: optionValue,
1456
- label: option.label,
1457
- count: option.count,
1458
- });
1459
- }
1460
- }, children: [_jsx("div", { className: classnames('ss__chat__actions__facet__option__checkbox', {
1461
- 'ss__chat__actions__facet__option__checkbox--checked': isSelected,
1462
- }), children: isSelected && _jsx(Icon, { icon: "check-thin", size: "10px" }) }), _jsx("span", { className: "ss__chat__actions__facet__option__label", children: option.label })] }, optionValue));
1463
- }
1464
- return (_jsx(Button, { onClick: () => {
1465
- controller.store.addFacet({
1466
- key: facet.field,
1467
- facetLabel: facet.label,
1468
- value: optionValue,
1469
- label: option.label,
1470
- count: option.count,
1471
- });
1472
- controller.search();
1473
- }, children: option.label }, optionValue));
1474
- })
1475
- : facet.values.map((option) => {
1476
- const optionValue = option.value || option.label;
1477
- if (multiselectFacets) {
1478
- const isSelected = controller.store.isFacetSelected(facet.field, optionValue);
1479
- return (_jsxs("div", { className: "ss__chat__actions__facet__option--checkbox", onClick: () => {
1480
- if (isSelected) {
1481
- controller.store.removeFacet(facet.field, optionValue);
1482
- }
1483
- else {
1484
- controller.store.addFacet({
1485
- key: facet.field,
1486
- facetLabel: facet.label,
1487
- value: optionValue,
1488
- label: option.label,
1489
- count: option.count,
1490
- });
1491
- }
1492
- }, children: [_jsx("div", { className: classnames('ss__chat__actions__facet__option__checkbox', {
1493
- 'ss__chat__actions__facet__option__checkbox--checked': isSelected,
1494
- }), children: isSelected && _jsx(Icon, { icon: "check-thin", size: "10px" }) }), _jsx("span", { className: "ss__chat__actions__facet__option__label", children: option.label })] }, optionValue));
1495
- }
1496
- return (_jsx(Button, { onClick: () => {
1497
- controller.store.addFacet({
1498
- key: facet.field,
1499
- facetLabel: facet.label,
1500
- value: optionValue,
1501
- label: option.label,
1502
- count: option.count,
1503
- });
1504
- controller.search();
1505
- }, children: option.label }, optionValue));
1506
- }) }) }, facet.field) }, idx));
1507
- }) })] })) : null,
1508
- actions: (_jsx("div", { className: "ss__chat__actions--suggested" })),
1509
- }[action.type] || null }, index))) })), (() => {
1510
- const activeComparisonSearchResults = shouldShowSideChat && activeMessage?.messageType === 'productComparison'
1511
- ? activeMessage.searchResults || []
1512
- : null;
1513
- const showCommittedComparisons = !activeComparisonSearchResults &&
1514
- store.currentChat?.comparisons.committed &&
1515
- store.currentChat.comparisons.committed.length > 0 &&
1516
- (activeMessage?.messageType === 'user' ||
1517
- activeMessage?.messageType === 'productComparison' ||
1518
- activeMessage?.messageType === 'productQuery');
1519
- const visibleAttachments = store.currentChat?.attachments.attached.filter((item) => item.state === 'attached' || item.state === 'active' || item.state === 'loading' || item.state === 'error') || [];
1520
- const productAttachments = visibleAttachments.filter((item) => item.type === 'product' &&
1521
- item.requestType !== 'productSimilar' &&
1522
- item.requestType !== 'productComparison');
1523
- const imageAttachments = visibleAttachments.filter((item) => item.type === 'image');
1524
- const comparisonItems = activeComparisonSearchResults
1525
- ? activeComparisonSearchResults.map((result) => {
1526
- const d = result?.display || result;
1527
- return {
1528
- id: result?.id,
1529
- name: d?.mappings?.core?.name || '',
1530
- imageUrl: d?.mappings?.core?.thumbnailImageUrl || d?.mappings?.core?.imageUrl || '',
1531
- onClick: isMobile
1532
- ? () => {
1533
- controller.viewProduct(result);
1534
- setMobileProductInfoOpen(true);
1535
- }
1536
- : undefined,
1537
- };
1538
- })
1539
- : showCommittedComparisons
1540
- ? (store.currentChat?.comparisons.committed || []).map((comparisonItem) => {
1541
- const d = comparisonItem?.result?.display || comparisonItem?.result;
1712
+ },
1713
+ }
1714
+ : {
1715
+ '.ss__button': {
1716
+ width: '100%',
1717
+ border: 'none',
1718
+ borderRadius: 0,
1719
+ boxSizing: 'border-box',
1720
+ background: '#fff',
1721
+ '&:hover': {
1722
+ background: new Colour(colorPrimary).lightenHex(0.95),
1723
+ },
1724
+ },
1725
+ }),
1726
+ },
1727
+ }, children: _jsx("div", { className: "ss__chat__actions__facet__options", children: facet.type === 'range-buckets'
1728
+ ? facet.values.map((option) => {
1729
+ const rangeOption = option;
1730
+ const optionValue = `${rangeOption.low ?? '*'}:${rangeOption.high ?? '*'}`;
1731
+ if (multiselectFacets) {
1732
+ const isSelected = controller.store.isFacetSelected(facet.field, optionValue);
1733
+ return (_jsxs("div", { className: "ss__chat__actions__facet__option--checkbox", onClick: () => {
1734
+ if (isSelected) {
1735
+ controller.store.removeFacet(facet.field, optionValue);
1736
+ }
1737
+ else {
1738
+ controller.store.addFacet({
1739
+ key: facet.field,
1740
+ value: optionValue,
1741
+ });
1742
+ }
1743
+ }, children: [_jsx("div", { className: classnames('ss__chat__actions__facet__option__checkbox', {
1744
+ 'ss__chat__actions__facet__option__checkbox--checked': isSelected,
1745
+ }), children: isSelected && _jsx(Icon, { icon: "check-thin", size: "10px" }) }), _jsx("span", { className: "ss__chat__actions__facet__option__label", children: rangeOption.label })] }, optionValue));
1746
+ }
1747
+ return (_jsx(Button, { onClick: () => {
1748
+ controller.store.addFacet({
1749
+ key: facet.field,
1750
+ value: optionValue,
1751
+ });
1752
+ controller.search();
1753
+ }, children: rangeOption.label }, optionValue));
1754
+ })
1755
+ : facet.values.map((option) => {
1756
+ const valueOption = option;
1757
+ const optionValue = valueOption.value || valueOption.label;
1758
+ if (multiselectFacets) {
1759
+ const isSelected = controller.store.isFacetSelected(facet.field, optionValue);
1760
+ return (_jsxs("div", { className: "ss__chat__actions__facet__option--checkbox", onClick: () => {
1761
+ if (isSelected) {
1762
+ controller.store.removeFacet(facet.field, optionValue);
1763
+ }
1764
+ else {
1765
+ controller.store.addFacet({
1766
+ key: facet.field,
1767
+ value: optionValue,
1768
+ });
1769
+ }
1770
+ }, children: [_jsx("div", { className: classnames('ss__chat__actions__facet__option__checkbox', {
1771
+ 'ss__chat__actions__facet__option__checkbox--checked': isSelected,
1772
+ }), children: isSelected && _jsx(Icon, { icon: "check-thin", size: "10px" }) }), _jsx("span", { className: "ss__chat__actions__facet__option__label", children: valueOption.label })] }, optionValue));
1773
+ }
1774
+ return (_jsx(Button, { onClick: () => {
1775
+ controller.store.addFacet({
1776
+ key: facet.field,
1777
+ value: optionValue,
1778
+ });
1779
+ controller.search();
1780
+ }, children: valueOption.label }, optionValue));
1781
+ }) }) }, facet.field) }, idx));
1782
+ }) })] }) })), (() => {
1783
+ const activeComparisonSearchResults = shouldShowSideChat && activeMessage?.messageType === 'productComparison'
1784
+ ? activeMessage.searchResults || []
1785
+ : null;
1786
+ const showCommittedComparisons = !activeComparisonSearchResults &&
1787
+ store.currentChat?.comparisons.committed &&
1788
+ store.currentChat.comparisons.committed.length > 0 &&
1789
+ (activeMessage?.messageType === 'user' ||
1790
+ activeMessage?.messageType === 'productComparison' ||
1791
+ activeMessage?.messageType === 'productQuery');
1792
+ const visibleAttachments = store.currentChat?.attachments.attached.filter((item) => item.state === 'attached' || item.state === 'active' || item.state === 'loading' || item.state === 'error') || [];
1793
+ const productAttachments = visibleAttachments.filter((item) => item.type === 'product' &&
1794
+ item.requestType !== 'productSimilar' &&
1795
+ item.requestType !== 'productComparison');
1796
+ const imageAttachments = visibleAttachments.filter((item) => item.type === 'image');
1797
+ const comparisonItems = activeComparisonSearchResults
1798
+ ? activeComparisonSearchResults.map((result) => {
1799
+ const d = result?.display || result;
1542
1800
  return {
1543
- id: comparisonItem.result?.id,
1801
+ id: result?.id,
1544
1802
  name: d?.mappings?.core?.name || '',
1545
1803
  imageUrl: d?.mappings?.core?.thumbnailImageUrl || d?.mappings?.core?.imageUrl || '',
1546
1804
  onClick: isMobile
1547
1805
  ? () => {
1548
- controller.viewProduct(comparisonItem.result);
1806
+ controller.productQuickView(result);
1549
1807
  setMobileProductInfoOpen(true);
1550
1808
  }
1551
1809
  : undefined,
1552
1810
  };
1553
1811
  })
1554
- : [];
1555
- const productItems = productAttachments.map((item) => ({
1556
- id: item.id,
1557
- name: item.name || '',
1558
- imageUrl: item.thumbnailUrl || '',
1559
- onClick: isMobile
1560
- ? () => {
1561
- setMobileProductInfoOpen(true);
1562
- }
1563
- : () => {
1564
- store.currentChat?.setActiveMessage(store.currentChat?.activeMessage?.id || '');
1812
+ : showCommittedComparisons
1813
+ ? (store.currentChat?.comparisons.committed || []).map((comparisonItem) => {
1814
+ const d = comparisonItem?.result?.display || comparisonItem?.result;
1815
+ return {
1816
+ id: comparisonItem.result?.id,
1817
+ name: d?.mappings?.core?.name || '',
1818
+ imageUrl: d?.mappings?.core?.thumbnailImageUrl || d?.mappings?.core?.imageUrl || '',
1819
+ onClick: isMobile
1820
+ ? () => {
1821
+ controller.productQuickView(comparisonItem.result);
1822
+ setMobileProductInfoOpen(true);
1823
+ }
1824
+ : undefined,
1825
+ };
1826
+ })
1827
+ : [];
1828
+ const productItems = productAttachments.map((item) => ({
1829
+ id: item.id,
1830
+ name: item.name || '',
1831
+ imageUrl: item.thumbnailUrl || '',
1832
+ onClick: isMobile
1833
+ ? () => {
1834
+ setMobileProductInfoOpen(true);
1835
+ }
1836
+ : () => {
1837
+ store.currentChat?.setActiveMessage(store.currentChat?.activeMessage?.id || '');
1838
+ },
1839
+ onRemove: () => {
1840
+ store.currentChat?.attachments.remove(item.id);
1841
+ setMobileProductInfoOpen(false);
1842
+ store.currentChat?.dismissSideChat();
1565
1843
  },
1566
- onRemove: () => {
1567
- store.currentChat?.attachments.remove(item.id);
1568
- setMobileProductInfoOpen(false);
1569
- store.currentChat?.dismissSideChat();
1570
- },
1571
- }));
1572
- const imageItems = imageAttachments.map((item) => ({
1573
- id: item.id,
1574
- name: item.fileName || 'Image',
1575
- imageUrl: item.base64 || item.thumbnailUrl || '',
1576
- isLoading: item.state === 'loading',
1577
- hasError: !!item.error,
1578
- errorMessage: item.error?.message,
1579
- onRemove: () => store.currentChat?.attachments.remove(item.id),
1580
- }));
1581
- const productTitle = productItems.length > 1 ? 'Compare these products' : 'Ask about this product' + (isMobile ? '' : ' (click for details)');
1582
- const hasImageError = imageItems.some((item) => item.hasError);
1583
- const imageTitle = imageItems.length > 0 && !hasImageError ? 'Find products similar to this image:' : '';
1584
- return (_jsxs(_Fragment, { children: [comparisonItems.length > 0 && (_jsx(ChatAttachmentContext, { title: 'Compare these products', items: comparisonItems, onClose: () => {
1585
- store.currentChat?.comparisons.resetCommitted();
1586
- store.currentChat?.dismissSideChat();
1587
- } })), productItems.length > 0 && (_jsx(ChatAttachmentContext, { title: productTitle, items: productItems, onClose: () => {
1588
- productAttachments.forEach((item) => store.currentChat?.attachments.remove(item.id));
1589
- setMobileProductInfoOpen(false);
1590
- store.currentChat?.dismissSideChat();
1591
- } })), imageItems.length > 0 && _jsx(ChatAttachmentContext, { title: imageTitle, items: imageItems })] }));
1592
- })(), (() => {
1593
- const drift = store.currentChat?.topicDrift;
1594
- if (!drift)
1595
- return null;
1596
- // recommendedAction is the prescription; driftType is the diagnosis.
1597
- // Hide the banner when the backend says to keep flowing, or when no drift was detected.
1598
- if (drift.recommendedAction === 'CONTINUE' || drift.driftType === 'NO_DRIFT')
1599
- return null;
1600
- const isScopeRedirect = drift.recommendedAction === 'SCOPE_REDIRECT';
1601
- const primaryText = isScopeRedirect ? "I'm here to help with shopping" : 'Looking for something new?';
1602
- const secondaryText = isScopeRedirect
1603
- ? 'Try asking about products, comparisons, or recommendations'
1604
- : 'Start a fresh session for better assistance, or keep going in this one?';
1605
- const buttonText = isScopeRedirect ? '' : 'New Session';
1606
- return (_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: primaryText }), _jsx("span", { children: secondaryText })] }), buttonText && (_jsx(Button, { className: 'ss__chat__topic-drift__button', onClick: () => {
1607
- if (isScopeRedirect) {
1608
- store.currentChat?.dismissTopicDrift();
1609
- return;
1610
- }
1611
- const messageText = store.currentChat?.handleTopicDrift();
1612
- if (messageText) {
1613
- controller.store.createChat();
1614
- controller.search({ data: { message: messageText } });
1615
- }
1616
- }, children: buttonText })), _jsx("span", { onClick: () => {
1617
- store.currentChat?.dismissTopicDrift();
1618
- }, children: _jsx(Icon, { icon: "close-thin", size: "14px", className: 'ss__chat__topic-drift__icon--close' }) })] }));
1619
- })(), _jsxs("div", { className: 'ss__chat__input', children: [_jsxs("div", { className: 'ss__chat__input__input', children: [_jsx("input", { type: "text", name: "ss-chat-input", disabled: store.loading || store.blocked || store.currentChat?.sessionLimitReached, placeholder: (() => {
1620
- const comparedCount = store.currentChat?.comparisons.compared.length || 0;
1621
- const committedCount = store.currentChat?.comparisons.committed.length || 0;
1622
- const attached = store.currentChat?.attachments.attached.filter((item) => item.state === 'attached' || item.state === 'active') || [];
1623
- const attachedProducts = attached.filter((item) => item.type === 'product');
1624
- const attachedImages = attached.filter((item) => item.type === 'image' && !item.error);
1625
- if (attachedProducts.length === 1) {
1626
- return 'Ask about this product...';
1627
- }
1628
- if (comparedCount > 1 || attachedProducts.length > 1) {
1629
- return 'What would you like to compare?';
1630
- }
1631
- if (committedCount > 0) {
1632
- return 'Ask about the compared products...';
1633
- }
1634
- if (attachedImages.length > 0) {
1635
- return 'Ask about this image...';
1636
- }
1637
- if (comparedCount === 1) {
1638
- return 'Add another product to compare...';
1639
- }
1640
- return 'Type your message...';
1641
- })(), onInput: (e) => controller.handlers.input.input(e), onKeyDown: (e) => {
1642
- controller.handlers.input.enterKey(e);
1643
- if (e.keyCode === 13 && isMobile && mobileProductInfoOpen) {
1644
- setMobileProductInfoOpen(false);
1645
- }
1646
- }, value: controller.store.inputValue }), store.features.imageSearch.enabled && (_jsxs(_Fragment, { children: [_jsx(Button, { className: 'ss__chat__upload-button', disabled: store.loading || store.blocked || store.currentChat?.sessionLimitReached, onClick: () => fileInputRef.current?.click(), icon: { icon: 'image', title: 'Upload Image' } }), _jsx("input", { ref: fileInputRef, onChange: async (e) => {
1647
- await controller.upload(e.target.files);
1648
- // reset value
1649
- e.target.value = '';
1650
- }, multiple: true, type: "file", accept: "image/*", className: "ss__chat__input__input__file" })] }))] }), _jsx("div", { className: 'ss__chat__input__actions', children: (() => {
1651
- const hasImageAttachment = (store.currentChat?.attachments.attached || []).some((item) => item.type === 'image' && !item.error);
1652
- const messageEmpty = !controller.store.inputValue.trim();
1653
- const sendDisabled = store.blocked || store.currentChat?.sessionLimitReached || (hasImageAttachment && messageEmpty) || messageEmpty;
1654
- return (_jsx(Button, { className: "ss__chat__send-button", icon: { icon: 'send', title: 'Send Message' }, disabled: sendDisabled, onClick: () => {
1655
- if (sendDisabled)
1844
+ }));
1845
+ const imageItems = imageAttachments.map((item) => ({
1846
+ id: item.id,
1847
+ name: item.fileName || 'Image',
1848
+ imageUrl: item.base64 || item.thumbnailUrl || '',
1849
+ isLoading: item.state === 'loading',
1850
+ hasError: !!item.error,
1851
+ errorMessage: item.error?.message,
1852
+ onRemove: () => store.currentChat?.attachments.remove(item.id),
1853
+ }));
1854
+ const productTitle = productItems.length > 1 ? 'Compare these products' : 'Ask about this product' + (isMobile ? '' : ' (click for details)');
1855
+ const hasImageError = imageItems.some((item) => item.hasError);
1856
+ const imageTitle = imageItems.length > 0 && !hasImageError ? 'Find products similar to this image:' : '';
1857
+ return (_jsxs(_Fragment, { children: [comparisonItems.length > 0 && (_jsx(ChatAttachmentContext, { title: 'Compare these products', items: comparisonItems, onClose: () => {
1858
+ store.currentChat?.comparisons.resetCommitted();
1859
+ store.currentChat?.dismissSideChat();
1860
+ } })), productItems.length > 0 && (_jsx(ChatAttachmentContext, { title: productTitle, items: productItems, onClose: () => {
1861
+ productAttachments.forEach((item) => store.currentChat?.attachments.remove(item.id));
1862
+ setMobileProductInfoOpen(false);
1863
+ store.currentChat?.dismissSideChat();
1864
+ } })), imageItems.length > 0 && _jsx(ChatAttachmentContext, { title: imageTitle, items: imageItems })] }));
1865
+ })(), (() => {
1866
+ const drift = store.currentChat?.topicDrift;
1867
+ if (!drift)
1868
+ return null;
1869
+ // recommendedAction is the prescription; driftType is the diagnosis.
1870
+ // Hide the banner when the backend says to keep flowing, or when no drift was detected.
1871
+ if (drift.recommendedAction === 'CONTINUE' || drift.driftType === 'NO_DRIFT')
1872
+ return null;
1873
+ const isScopeRedirect = drift.recommendedAction === 'SCOPE_REDIRECT';
1874
+ const primaryText = isScopeRedirect ? "I'm here to help with shopping" : 'Looking for something new?';
1875
+ const secondaryText = isScopeRedirect
1876
+ ? 'Try asking about products, comparisons, or recommendations'
1877
+ : 'Start a fresh session for better assistance, or keep going in this one?';
1878
+ const buttonText = isScopeRedirect ? '' : 'New Session';
1879
+ return (_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: primaryText }), _jsx("span", { children: secondaryText })] }), buttonText && (_jsx(Button, { className: 'ss__chat__topic-drift__button', onClick: () => {
1880
+ if (isScopeRedirect) {
1881
+ store.currentChat?.dismissTopicDrift();
1656
1882
  return;
1657
- controller.search();
1658
- if (isMobile && mobileProductInfoOpen) {
1659
- setMobileProductInfoOpen(false);
1660
1883
  }
1661
- } }));
1662
- })() })] }), _jsx("div", { className: 'ss__chat__disclaimer', children: _jsxs("i", { children: ["Powered by Athos Commerce. AI-powered assistant. It sometimes make mistakes. Avoid sharing personal data.", ' ', _jsx("a", { href: "https://athoscommerce.com/legal/privacy-policy/", target: "_blank", rel: "noopener noreferrer", children: "Privacy Policy" })] }) })] })) : (_jsx("div", { children: "This chat is expired. Please start a new chat." }))] })] })) : null] }), _jsx(Overlay, { style: { zIndex: 1001 }, color: "transparent", active: store.open, onClick: () => controller.handlers.button.click() })] }) }));
1884
+ const messageText = store.currentChat?.handleTopicDrift();
1885
+ if (messageText) {
1886
+ controller.store.createChat();
1887
+ controller.search({ data: { message: messageText } });
1888
+ }
1889
+ }, children: buttonText })), _jsx("span", { onClick: () => {
1890
+ store.currentChat?.dismissTopicDrift();
1891
+ }, children: _jsx(Icon, { icon: "close-thin", size: "14px", className: 'ss__chat__topic-drift__icon--close' }) })] }));
1892
+ })(), _jsxs("div", { className: 'ss__chat__input', children: [_jsxs("div", { className: 'ss__chat__input__input', children: [_jsx("input", { type: "text", name: "ss-chat-input", disabled: store.loading || store.blocked || store.currentChat?.sessionLimitReached, placeholder: (() => {
1893
+ const comparedCount = store.currentChat?.comparisons.compared.length || 0;
1894
+ const committedCount = store.currentChat?.comparisons.committed.length || 0;
1895
+ const attached = store.currentChat?.attachments.attached.filter((item) => item.state === 'attached' || item.state === 'active') ||
1896
+ [];
1897
+ const attachedProducts = attached.filter((item) => item.type === 'product');
1898
+ const attachedImages = attached.filter((item) => item.type === 'image' && !item.error);
1899
+ if (attachedProducts.length === 1) {
1900
+ return 'Ask about this product...';
1901
+ }
1902
+ if (comparedCount > 1 || attachedProducts.length > 1) {
1903
+ return 'What would you like to compare?';
1904
+ }
1905
+ if (committedCount > 0) {
1906
+ return 'Ask about the compared products...';
1907
+ }
1908
+ if (attachedImages.length > 0) {
1909
+ return 'Ask about this image...';
1910
+ }
1911
+ if (comparedCount === 1) {
1912
+ return 'Add another product to compare...';
1913
+ }
1914
+ return 'Type your message...';
1915
+ })(), onInput: (e) => controller.handlers.input.input(e), onKeyDown: (e) => {
1916
+ controller.handlers.input.enterKey(e);
1917
+ if (e.keyCode === 13 && isMobile && mobileProductInfoOpen) {
1918
+ setMobileProductInfoOpen(false);
1919
+ }
1920
+ }, value: controller.store.inputValue }), store.features.imageSearch.enabled && (_jsxs(_Fragment, { children: [_jsx(Button, { className: 'ss__chat__upload-button', disabled: store.loading || store.blocked || store.currentChat?.sessionLimitReached, onClick: () => fileInputRef.current?.click(), icon: { icon: 'image', title: 'Upload Image' } }), _jsx("input", { ref: fileInputRef, onChange: async (e) => {
1921
+ await controller.upload(e.target.files);
1922
+ // reset value
1923
+ e.target.value = '';
1924
+ }, multiple: true, type: "file", accept: "image/*", className: "ss__chat__input__input__file" })] }))] }), _jsx("div", { className: 'ss__chat__input__actions', children: (() => {
1925
+ const hasImageAttachment = (store.currentChat?.attachments.attached || []).some((item) => item.type === 'image' && !item.error);
1926
+ const messageEmpty = !controller.store.inputValue.trim();
1927
+ const sendDisabled = store.blocked || store.currentChat?.sessionLimitReached || (hasImageAttachment && messageEmpty) || messageEmpty;
1928
+ return (_jsx(Button, { className: "ss__chat__send-button", icon: { icon: 'send', title: 'Send Message' }, disabled: sendDisabled, onClick: () => {
1929
+ if (sendDisabled)
1930
+ return;
1931
+ controller.search();
1932
+ if (isMobile && mobileProductInfoOpen) {
1933
+ setMobileProductInfoOpen(false);
1934
+ }
1935
+ } }));
1936
+ })() })] }), _jsx("div", { className: 'ss__chat__disclaimer', children: _jsxs("i", { children: ["Powered by Athos Commerce. AI-powered assistant. It sometimes make mistakes. Avoid sharing personal data.", ' ', _jsx("a", { href: "https://athoscommerce.com/legal/privacy-policy/", target: "_blank", rel: "noopener noreferrer", children: "Privacy Policy" })] }) }), isDraggingFile && (_jsxs("div", { className: "ss__chat__content__footer__drop-overlay", children: [_jsx(Icon, { icon: "image", size: "24px" }), _jsx("span", { children: "Drop image to upload" })] }))] }));
1937
+ })()) : (_jsxs("div", { className: 'ss__chat__expired', children: [_jsx("div", { className: 'ss__chat__expired__message', children: "This chat is expired. Please start a new chat." }), _jsx(Button, { className: 'ss__chat__expired__button', onClick: () => {
1938
+ controller.store.createChat();
1939
+ }, children: "New Session" })] }))] })] })) : null] }), _jsx(Overlay, { style: { zIndex: 1001 }, color: "transparent", active: store.open, onClick: () => controller.handlers.button.click() })] }) }));
1663
1940
  });