@envive-ai/react-toolkit-v3 0.3.18 → 0.3.19

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 (166) hide show
  1. package/dist/AnimatedText/AnimatedText.d.cts +3 -3
  2. package/dist/AnimatedText/AnimatedText.d.ts +3 -3
  3. package/dist/CSSVariablesEditor/CssVariablesEditorComponent.d.cts +2 -2
  4. package/dist/CSSVariablesEditor/CssVariablesEditorComponent.d.ts +2 -2
  5. package/dist/Carousel/Carousel.cjs +1 -1
  6. package/dist/Carousel/Carousel.d.cts +2 -2
  7. package/dist/Carousel/Carousel.d.ts +2 -2
  8. package/dist/Carousel/Carousel.js +1 -1
  9. package/dist/Carousel/components/Container.cjs +2 -2
  10. package/dist/Carousel/components/Container.js +2 -2
  11. package/dist/ChatFooter/ChatFooter.cjs +1 -1
  12. package/dist/ChatFooter/ChatFooter.d.cts +2 -2
  13. package/dist/ChatFooter/ChatFooter.d.ts +2 -2
  14. package/dist/ChatFooter/ChatFooter.js +1 -1
  15. package/dist/ChatFooter/components/Layout.cjs +2 -2
  16. package/dist/ChatFooter/components/Layout.js +2 -2
  17. package/dist/ChatFooter/components/index.d.cts +5 -5
  18. package/dist/ChatFooter/components/index.d.ts +5 -5
  19. package/dist/ChatHeader/components/Handle.cjs +2 -2
  20. package/dist/ChatHeader/components/Handle.js +2 -2
  21. package/dist/ChatHeader/components/Toggle.cjs +3 -3
  22. package/dist/ChatHeader/components/Toggle.js +3 -3
  23. package/dist/ChatPreview/ChatPreview.cjs +1 -1
  24. package/dist/ChatPreview/ChatPreview.d.cts +2 -2
  25. package/dist/ChatPreview/ChatPreview.d.ts +2 -2
  26. package/dist/ChatPreview/ChatPreview.js +1 -1
  27. package/dist/ChatPreviewComparison/ChatPreviewComparison.cjs +1 -1
  28. package/dist/ChatPreviewComparison/ChatPreviewComparison.d.cts +2 -2
  29. package/dist/ChatPreviewComparison/ChatPreviewComparison.js +1 -1
  30. package/dist/ChatPreviewComparison/components/Headline.cjs +2 -2
  31. package/dist/ChatPreviewComparison/components/Headline.js +2 -2
  32. package/dist/ChatPreviewComparison/components/Layout.cjs +4 -4
  33. package/dist/ChatPreviewComparison/components/Layout.js +4 -4
  34. package/dist/ChatPreviewComparison/components/Message.cjs +2 -2
  35. package/dist/ChatPreviewComparison/components/Message.js +2 -2
  36. package/dist/ChatPreviewLoading/ChatPreviewLoading.d.cts +2 -2
  37. package/dist/ChatPreviewLoading/ChatPreviewLoading.d.ts +2 -2
  38. package/dist/Container/Container.d.cts +175 -175
  39. package/dist/Container/Container.d.ts +175 -175
  40. package/dist/DesignTokens/DesignTokensComponent.d.cts +2 -2
  41. package/dist/DesignTokens/DesignTokensComponent.d.ts +2 -2
  42. package/dist/Disclaimer/components/Container.cjs +2 -2
  43. package/dist/Disclaimer/components/Container.js +2 -2
  44. package/dist/DocumentRetrievalCard/DocumentRetrievalCard.d.cts +2 -2
  45. package/dist/DocumentRetrievalCard/components/Layout.cjs +2 -2
  46. package/dist/DocumentRetrievalCard/components/Layout.js +2 -2
  47. package/dist/DocumentRetrievalCard/components/ViewArticleButton/components/Icon.cjs +1 -1
  48. package/dist/DocumentRetrievalCard/components/ViewArticleButton/components/Icon.js +1 -1
  49. package/dist/FloatingButton/FloatingButton.d.cts +2 -2
  50. package/dist/FloatingButton/FloatingButton.d.ts +2 -2
  51. package/dist/FloatingChat/FloatingChat.cjs +56 -20
  52. package/dist/FloatingChat/FloatingChat.d.cts +2 -2
  53. package/dist/FloatingChat/FloatingChat.d.ts +2 -2
  54. package/dist/FloatingChat/FloatingChat.js +57 -21
  55. package/dist/FloatingChat/components/AgentMessage.cjs +1 -1
  56. package/dist/FloatingChat/components/AgentMessage.js +1 -1
  57. package/dist/FloatingChat/components/ChatMessages.cjs +2 -2
  58. package/dist/FloatingChat/components/ChatMessages.js +2 -2
  59. package/dist/FloatingChat/components/Layout.cjs +5 -4
  60. package/dist/FloatingChat/components/Layout.js +5 -4
  61. package/dist/FloatingChat/components/ResultsGridView.cjs +72 -0
  62. package/dist/FloatingChat/components/ResultsGridView.js +71 -0
  63. package/dist/FloatingChat/components/SalesAgentBadgeContent.cjs +1 -1
  64. package/dist/FloatingChat/components/SalesAgentBadgeContent.js +1 -1
  65. package/dist/FloatingChat/components/SlideChatContent.cjs +46 -0
  66. package/dist/FloatingChat/components/SlideChatContent.js +45 -0
  67. package/dist/FloatingChat/components/index.cjs +4 -0
  68. package/dist/FloatingChat/components/index.js +4 -0
  69. package/dist/FloatingChat/hooks/useProductResultsView.cjs +36 -0
  70. package/dist/FloatingChat/hooks/useProductResultsView.js +35 -0
  71. package/dist/FloatingChat/utils/functions.cjs +16 -1
  72. package/dist/FloatingChat/utils/functions.js +15 -1
  73. package/dist/FullPageSalesAgent/FullPageSalesAgent.cjs +2 -2
  74. package/dist/FullPageSalesAgent/FullPageSalesAgent.d.cts +2 -2
  75. package/dist/FullPageSalesAgent/FullPageSalesAgent.d.ts +2 -2
  76. package/dist/FullPageSalesAgent/FullPageSalesAgent.js +2 -2
  77. package/dist/Image/Image.d.cts +2 -2
  78. package/dist/Image/Image.d.ts +2 -2
  79. package/dist/ImageGallery/ImageGallery.d.cts +2 -2
  80. package/dist/ImageGallery/ImageGallery.d.ts +2 -2
  81. package/dist/ImageGallery/components/Layout.cjs +1 -1
  82. package/dist/ImageGallery/components/Layout.js +1 -1
  83. package/dist/MarkdownProcessor/MarkdownProcessor.d.cts +2 -2
  84. package/dist/MarkdownProcessor/MarkdownProcessor.d.ts +2 -2
  85. package/dist/Message/components/LinkButton.cjs +1 -1
  86. package/dist/Message/components/LinkButton.js +1 -1
  87. package/dist/OrderLookupCard/OrderLookupCard.cjs +1 -1
  88. package/dist/OrderLookupCard/OrderLookupCard.js +1 -1
  89. package/dist/ProductCard/ProductCard.cjs +2 -2
  90. package/dist/ProductCard/ProductCard.d.cts +2 -2
  91. package/dist/ProductCard/ProductCard.d.ts +2 -2
  92. package/dist/ProductCard/ProductCard.js +2 -2
  93. package/dist/PromptButton/PromptButton.d.cts +2 -2
  94. package/dist/PromptButton/PromptButton.d.ts +2 -2
  95. package/dist/PromptButtonCarouselWithImage/PromptButtonCarouselWithImage.d.cts +2 -2
  96. package/dist/PromptButtonCarouselWithImage/PromptButtonCarouselWithImage.d.ts +2 -2
  97. package/dist/PromptButtonCarouselWithImage/components/PromptButtonsCarousel.cjs +1 -1
  98. package/dist/PromptButtonCarouselWithImage/components/PromptButtonsCarousel.js +1 -1
  99. package/dist/PromptCarousel/PromptCarousel.cjs +3 -3
  100. package/dist/PromptCarousel/PromptCarousel.d.cts +2 -2
  101. package/dist/PromptCarousel/PromptCarousel.d.ts +2 -2
  102. package/dist/PromptCarousel/PromptCarousel.js +3 -3
  103. package/dist/ReviewCard/ReviewCard.d.cts +2 -2
  104. package/dist/ReviewCard/ReviewCard.d.ts +2 -2
  105. package/dist/ReviewCard/components/Container.cjs +2 -2
  106. package/dist/ReviewCard/components/Container.js +2 -2
  107. package/dist/ReviewCard/components/ReadMoreButton.cjs +1 -1
  108. package/dist/ReviewCard/components/ReadMoreButton.js +1 -1
  109. package/dist/ReviewCard/components/index.d.cts +6 -6
  110. package/dist/ReviewCard/components/index.d.ts +4 -4
  111. package/dist/SalesAgentProductCard/SalesAgentProductCard.d.cts +2 -2
  112. package/dist/SalesAgentProductCard/SalesAgentProductCard.d.ts +2 -2
  113. package/dist/SalesAgentProductCard/components/Container.cjs +2 -2
  114. package/dist/SalesAgentProductCard/components/Container.js +2 -2
  115. package/dist/SalesAgentProductCard/components/index.d.cts +6 -6
  116. package/dist/SalesAgentProductCard/components/index.d.ts +8 -8
  117. package/dist/SocialProof/SocialProof.cjs +1 -1
  118. package/dist/SocialProof/SocialProof.d.ts +2 -2
  119. package/dist/SocialProof/SocialProof.js +1 -1
  120. package/dist/SocialProof/components/Headline.cjs +3 -3
  121. package/dist/SocialProof/components/Headline.js +3 -3
  122. package/dist/SocialProof/components/LayoutFourHorizontal.cjs +1 -1
  123. package/dist/SocialProof/components/LayoutFourHorizontal.js +1 -1
  124. package/dist/SocialProof/components/Subheadline.cjs +1 -1
  125. package/dist/SocialProof/components/Subheadline.js +1 -1
  126. package/dist/SparkleAnimation/SparkleAnimation.d.cts +2 -2
  127. package/dist/SparkleAnimation/SparkleAnimation.d.ts +2 -2
  128. package/dist/Stack/Stack.d.cts +2 -2
  129. package/dist/TitledPromptCarousel/TitledPromptCarousel.cjs +1 -1
  130. package/dist/TitledPromptCarousel/TitledPromptCarousel.d.cts +2 -2
  131. package/dist/TitledPromptCarousel/TitledPromptCarousel.d.ts +2 -2
  132. package/dist/TitledPromptCarousel/TitledPromptCarousel.js +1 -1
  133. package/dist/Tokens/index.cjs +1 -1
  134. package/dist/Tokens/index.js +1 -1
  135. package/dist/TypingAnimation/TypingAnimation.cjs +1 -1
  136. package/dist/TypingAnimation/TypingAnimation.d.cts +2 -2
  137. package/dist/TypingAnimation/TypingAnimation.d.ts +2 -2
  138. package/dist/TypingAnimation/TypingAnimation.js +1 -1
  139. package/dist/TypingAnimation/hooks/useGetTypographyVariant.cjs +1 -1
  140. package/dist/TypingAnimation/hooks/useGetTypographyVariant.js +1 -1
  141. package/dist/Typography/Typography.d.cts +4 -4
  142. package/dist/Typography/Typography.d.ts +4 -4
  143. package/dist/WelcomeMessage/components/Container.cjs +2 -2
  144. package/dist/WelcomeMessage/components/Container.js +2 -2
  145. package/dist/WidgetTextField/WidgetTextField.cjs +1 -1
  146. package/dist/WidgetTextField/WidgetTextField.d.cts +2 -2
  147. package/dist/WidgetTextField/WidgetTextField.d.ts +2 -2
  148. package/dist/WidgetTextField/WidgetTextField.js +1 -1
  149. package/dist/WidgetTextField/components/Container.cjs +2 -2
  150. package/dist/WidgetTextField/components/Container.js +2 -2
  151. package/dist/WidgetTextField/components/Icon.cjs +1 -1
  152. package/dist/WidgetTextField/components/Icon.js +1 -1
  153. package/dist/WidgetWrapper/WidgetWrapper.d.cts +2 -2
  154. package/dist/WidgetWrapper/WidgetWrapper.d.ts +2 -2
  155. package/dist/WidgetWrapperWithTitle/WidgetWrapperWithTitle.d.cts +2 -2
  156. package/dist/WidgetWrapperWithTitle/WidgetWrapperWithTitle.d.ts +2 -2
  157. package/dist/styles.css +1 -1
  158. package/package.json +1 -1
  159. package/src/components/FloatingChat/FloatingChat.tsx +80 -25
  160. package/src/components/FloatingChat/components/Layout.tsx +7 -1
  161. package/src/components/FloatingChat/components/ResultsGridView.tsx +93 -0
  162. package/src/components/FloatingChat/components/SlideChatContent.tsx +72 -0
  163. package/src/components/FloatingChat/components/index.ts +4 -0
  164. package/src/components/FloatingChat/hooks/useProductResultsView.ts +49 -0
  165. package/src/components/FloatingChat/utils/functions.ts +22 -0
  166. package/src/logging/logger.ts +33 -8
@@ -1,4 +1,4 @@
1
- import { useEffect, useRef, useState } from 'react';
1
+ import { useEffect, useMemo, useRef, useState } from 'react';
2
2
 
3
3
  import {
4
4
  ChatElementDisplayLocationV3,
@@ -21,8 +21,8 @@ import { PromptButtonVariant } from '../PromptButton';
21
21
  import { PromptCarousel, PromptCarouselRows, usePromptCarouselAnalytics } from '../PromptCarousel';
22
22
  import { WelcomeMessage } from '../WelcomeMessage/WelcomeMessage';
23
23
  import { SparkleIconColor } from '../WelcomeMessage/types/types';
24
- import { FloatingChatComponents, ModalSheet } from './components';
25
24
  import { useChatSuggestions } from './hooks/useChatSuggestions';
25
+ import { useProductResultsView } from './hooks/useProductResultsView';
26
26
  import { useScrollToBottom } from './hooks/useScrollToBottom';
27
27
  import { useSnapSetup } from './hooks/useSnapSetup';
28
28
 
@@ -31,7 +31,9 @@ import { resolveTheme } from '../utils/resolveTheme';
31
31
  import { useFilteredChatMessages } from './hooks/useFilteredChatMessages';
32
32
  import { Unit } from './hooks/useSnapCalculator';
33
33
  import { FloatingChatProps } from './types/types';
34
- import { getCleanProducts } from './utils/functions';
34
+ import { getCleanProducts, getSearchQueryFromMessageBlock } from './utils/functions';
35
+ import { FloatingChatComponents, ModalSheet } from './components';
36
+ import { SalesAgentProductCardProps } from '../SalesAgentProductCard/types/types';
35
37
 
36
38
  export const FloatingChat = ({
37
39
  id,
@@ -80,6 +82,19 @@ export const FloatingChat = ({
80
82
  rightToggleLabel,
81
83
  } = hardcopyContent?.values ?? {};
82
84
 
85
+ const disclaimerTextString = useMemo(() => {
86
+ if (disclaimerText && typeof disclaimerText === 'string') {
87
+ return disclaimerText;
88
+ }
89
+ if (disclaimerText && Array.isArray(disclaimerText)) {
90
+ const textString = disclaimerText[0];
91
+ if (textString && typeof textString === 'string') {
92
+ return textString;
93
+ }
94
+ }
95
+ return undefined;
96
+ }, [disclaimerText]);
97
+
83
98
  const { agentName, chatHeaderLogoDarkSrc, chatHeaderLogoLightSrc } = lookAndFeelConfig;
84
99
 
85
100
  const {
@@ -141,6 +156,15 @@ export const FloatingChat = ({
141
156
  isOpen: isFloatingChatOpen,
142
157
  });
143
158
 
159
+ const {
160
+ resultsViewData,
161
+ setResultsViewData,
162
+ isResultsView,
163
+ scrollContainerRef,
164
+ isResultsViewRef,
165
+ handleBackToChat,
166
+ } = useProductResultsView({ scrollToBottom });
167
+
144
168
  useEffect(() => {
145
169
  if (isFloatingChatOpen) {
146
170
  trackWidgetInteraction({
@@ -182,6 +206,21 @@ export const FloatingChat = ({
182
206
  modalSheetControl,
183
207
  });
184
208
 
209
+ const handleProductCardClick = (product: SalesAgentProductCardProps) => {
210
+ trackWidgetInteraction({
211
+ eventName: EnviveMetricsEventName.WidgetInteraction,
212
+ trigger: {
213
+ widget: WidgetInteractionComponent.FLOATING_CHAT,
214
+ widget_interaction: WidgetInteractionType.PRODUCT_CARD_CLICKED,
215
+ widget_interaction_data: {
216
+ product_card_clicked: {
217
+ product_id: product.id,
218
+ },
219
+ },
220
+ },
221
+ });
222
+ };
223
+
185
224
  const header = (
186
225
  <ChatHeader
187
226
  logoDark={chatHeaderLogoDarkSrc}
@@ -254,6 +293,7 @@ export const FloatingChat = ({
254
293
  });
255
294
  setAnswerSuggestions([]);
256
295
  setGeneralSuggestions([]);
296
+ handleBackToChat();
257
297
  }}
258
298
  textFieldPlaceholderText={chatFooterTextFieldPlaceholderText as string}
259
299
  promptSuggestions={
@@ -269,6 +309,7 @@ export const FloatingChat = ({
269
309
 
270
310
  setAnswerSuggestions([]);
271
311
  setGeneralSuggestions([]);
312
+ handleBackToChat();
272
313
  }}
273
314
  disabled={isPendingResponse || isResponseStreaming}
274
315
  disabledInput={!userQueryInputEnabled}
@@ -291,15 +332,18 @@ export const FloatingChat = ({
291
332
  );
292
333
 
293
334
  const handleExploreAllResults = (firstProductMessageId: string) => {
294
- const messageBlock = messages.find(block =>
335
+ const blockIndex = messages.findIndex(block =>
295
336
  block.some(msg => msg.type === MessageType.Product && msg.id === firstProductMessageId),
296
337
  );
297
- const productMessages = messageBlock?.filter(msg => msg.type === MessageType.Product) ?? [];
338
+ if (blockIndex < 0) return;
339
+
340
+ const messageBlock = messages[blockIndex];
341
+ const productMessages = messageBlock.filter(msg => msg.type === MessageType.Product);
298
342
  const products = getCleanProducts(productMessages);
299
- console.log('[INFO] [spiffy-ai] Explore All Results clicked', {
300
- firstProductMessageId,
301
- products,
302
- });
343
+ if (products.length === 0) return;
344
+
345
+ const searchQuery = getSearchQueryFromMessageBlock(messages, blockIndex);
346
+ setResultsViewData({ products, searchQuery });
303
347
  };
304
348
 
305
349
  const chatMessages = (
@@ -321,6 +365,19 @@ export const FloatingChat = ({
321
365
  />
322
366
  );
323
367
 
368
+ const middleContent = (
369
+ <FloatingChatComponents.SlideChatContent
370
+ isResultsView={isResultsView}
371
+ isResultsViewRef={isResultsViewRef}
372
+ resultsViewData={resultsViewData}
373
+ onBackToChat={handleBackToChat}
374
+ onProductCardClick={handleProductCardClick}
375
+ theme={finalTheme}
376
+ chatMessages={chatMessages}
377
+ scrollToBottom={scrollToBottom}
378
+ />
379
+ );
380
+
324
381
  const answerSuggestionsComponent = (
325
382
  <PromptCarousel
326
383
  className="envive-tw-flex envive-tw-justify-end envive-tw-p-4 [&>div>div]:envive-tw-items-end"
@@ -354,20 +411,19 @@ export const FloatingChat = ({
354
411
  theme={finalTheme}
355
412
  header={header}
356
413
  footer={footer}
357
- welcomeMessage={welcomeMessage}
358
- chatMessages={chatMessages}
359
- answerSuggestions={showAnswerSuggestions ? answerSuggestionsComponent : undefined}
414
+ welcomeMessage={isResultsView ? null : welcomeMessage}
415
+ chatMessages={middleContent}
416
+ answerSuggestions={showAnswerSuggestions ? answerSuggestionsComponent : null}
360
417
  scrollToBottomButton={
361
- showScrollButton ? (
418
+ !isResultsView && showScrollButton ? (
362
419
  <FloatingChatComponents.ScrollToBottomButton onClick={handleScrollToBottom} />
363
- ) : undefined
420
+ ) : null
364
421
  }
365
422
  disclaimer={
366
- disclaimerText && typeof disclaimerText === 'string' ? (
367
- <Disclaimer disclaimerMarkdown={disclaimerText} />
368
- ) : undefined
423
+ disclaimerTextString ? <Disclaimer disclaimerMarkdown={disclaimerTextString} /> : null
369
424
  }
370
425
  debugBar={debugBar}
426
+ scrollContainerRef={scrollContainerRef}
371
427
  />
372
428
  );
373
429
 
@@ -380,20 +436,19 @@ export const FloatingChat = ({
380
436
  theme={finalTheme}
381
437
  header={mobileHeader}
382
438
  footer={footer}
383
- welcomeMessage={welcomeMessage}
384
- chatMessages={chatMessages}
385
- answerSuggestions={showAnswerSuggestions ? answerSuggestionsComponent : undefined}
439
+ welcomeMessage={isResultsView ? null : welcomeMessage}
440
+ chatMessages={middleContent}
441
+ answerSuggestions={showAnswerSuggestions ? answerSuggestionsComponent : null}
386
442
  scrollToBottomButton={
387
- showScrollButton ? (
443
+ !isResultsView && showScrollButton ? (
388
444
  <FloatingChatComponents.ScrollToBottomButton onClick={handleScrollToBottom} />
389
- ) : undefined
445
+ ) : null
390
446
  }
391
447
  disclaimer={
392
- disclaimerText && typeof disclaimerText === 'string' ? (
393
- <Disclaimer disclaimerMarkdown={disclaimerText} />
394
- ) : undefined
448
+ disclaimerTextString ? <Disclaimer disclaimerMarkdown={disclaimerTextString} /> : null
395
449
  }
396
450
  isFloatingFooterLayout={isFloatingLayout}
451
+ scrollContainerRef={scrollContainerRef}
397
452
  />
398
453
  );
399
454
 
@@ -20,6 +20,8 @@ type LayoutProps = {
20
20
  disclaimer?: React.ReactNode;
21
21
  isFloatingFooterLayout?: boolean;
22
22
  debugBar?: React.ReactNode;
23
+ /** Ref for the scrollable content container (used for programmatic scroll on view changes) */
24
+ scrollContainerRef?: React.RefObject<HTMLDivElement | null>;
23
25
  };
24
26
 
25
27
  export const Layout = ({
@@ -37,6 +39,7 @@ export const Layout = ({
37
39
  disclaimer,
38
40
  isFloatingFooterLayout = false,
39
41
  debugBar,
42
+ scrollContainerRef,
40
43
  }: LayoutProps) => {
41
44
  const finalTheme = resolveTheme(theme);
42
45
  const hasWelcomeMessage = isValidElement(welcomeMessage);
@@ -58,7 +61,10 @@ export const Layout = ({
58
61
  >
59
62
  {header && <div className="envive-tw-flex-shrink-0 envive-tw-leading-[0]">{header}</div>}
60
63
 
61
- <div className="envive-tw-flex-1 envive-tw-overflow-y-auto envive-tw-overflow-x-hidden envive-tw-transition-all envive-tw-duration-300 envive-tw-ease-in-out">
64
+ <div
65
+ ref={scrollContainerRef}
66
+ className="envive-tw-flex-1 envive-tw-overflow-y-auto envive-tw-overflow-x-hidden envive-tw-transition-all envive-tw-duration-300 envive-tw-ease-in-out"
67
+ >
62
68
  <Stack
63
69
  className="envive-tw-h-full"
64
70
  direction="column"
@@ -0,0 +1,93 @@
1
+ import { Theme } from '../../../../tokens/theme/theme';
2
+ import { SalesAgentProductCard } from '../../SalesAgentProductCard/SalesAgentProductCard';
3
+ import {
4
+ SalesAgentProductCardProps,
5
+ SalesAgentProductCardVariant,
6
+ } from '../../SalesAgentProductCard/types/types';
7
+ import { resolveTheme } from '../../utils/resolveTheme';
8
+ import { Stack } from '../../Stack';
9
+ import { Typography, TypographyColor, TypographyVariant } from '../../Typography';
10
+
11
+ export interface ResultsGridViewProps {
12
+ theme: Theme;
13
+ searchQuery: string;
14
+ products: SalesAgentProductCardProps[];
15
+ onBackToChat: () => void;
16
+ onProductCardClick?: (product: SalesAgentProductCardProps) => void;
17
+ }
18
+
19
+ export const ResultsGridView = ({
20
+ theme,
21
+ searchQuery,
22
+ products,
23
+ onBackToChat,
24
+ onProductCardClick,
25
+ }: ResultsGridViewProps) => {
26
+ const finalTheme = resolveTheme(theme);
27
+
28
+ const forceShowCurrentPriceSpace = products.some(
29
+ product => product.currentPrice && product.previousPrice !== product.currentPrice,
30
+ );
31
+
32
+ return (
33
+ <Stack
34
+ direction="column"
35
+ gap="4"
36
+ className="envive-tw-pb-4 envive-tw-pl-4 envive-tw-pr-4"
37
+ >
38
+ <div className="envive-tw-top-0 envive-tw-sticky envive-tw-z-10 envive-tw-mb-2 envive-tw-bg-background-light envive-tw-pb-2 envive-tw-pt-2">
39
+ <button
40
+ type="button"
41
+ onClick={onBackToChat}
42
+ className="envive-tw-flex envive-tw-items-center envive-tw-gap-1 envive-tw-rounded-global-custom envive-tw-border envive-tw-border-border-medium envive-tw-bg-background-light envive-tw-p-2 envive-tw-shadow-sm hover:envive-tw-border-border-dark"
43
+ aria-label="Back to Chat"
44
+ >
45
+ <span
46
+ className="envive-tw-text-[--envive-colors-text-primary]"
47
+ aria-hidden
48
+ >
49
+
50
+ </span>
51
+ <Typography
52
+ variant={TypographyVariant.B3_MD}
53
+ color={TypographyColor.TEXT_PRIMARY}
54
+ >
55
+ Back to Chat
56
+ </Typography>
57
+ </button>
58
+ </div>
59
+
60
+ {searchQuery && (
61
+ <Typography
62
+ variant={TypographyVariant.B3_MD}
63
+ color={TypographyColor.TEXT_PRIMARY}
64
+ className="envive-tw-mb-2"
65
+ >
66
+ Results for &quot;{searchQuery}&quot;
67
+ </Typography>
68
+ )}
69
+
70
+ {/* Product grid */}
71
+ <div className="envive-tw-grid envive-tw-grid-cols-2 envive-tw-gap-4">
72
+ {products.map((product, index) => (
73
+ <SalesAgentProductCard
74
+ key={product.id ?? `product-${index}`}
75
+ variant={SalesAgentProductCardVariant.LARGE}
76
+ productName={product.productName}
77
+ currentPrice={product.currentPrice}
78
+ previousPrice={product.previousPrice}
79
+ pricePrefix={product.pricePrefix ?? '$'}
80
+ forceShowCurrentPriceSpace={forceShowCurrentPriceSpace}
81
+ rate={product.rate}
82
+ numberOfReviews={product.numberOfReviews}
83
+ image={product.image}
84
+ url={product.url}
85
+ target="_blank"
86
+ theme={finalTheme}
87
+ onClick={() => onProductCardClick?.(product)}
88
+ />
89
+ ))}
90
+ </div>
91
+ </Stack>
92
+ );
93
+ };
@@ -0,0 +1,72 @@
1
+ import { AnimatePresence, motion } from 'framer-motion';
2
+ import { Theme } from '../../../../tokens/theme/theme';
3
+ import { SalesAgentProductCardProps } from '../../SalesAgentProductCard/types/types';
4
+ import { ResultsGridView } from './ResultsGridView';
5
+ import type { ResultsViewData } from '../hooks/useProductResultsView';
6
+
7
+ type SlideChatContentProps = {
8
+ isResultsView: boolean;
9
+ isResultsViewRef: React.MutableRefObject<boolean>;
10
+ resultsViewData: ResultsViewData | null;
11
+ onBackToChat: () => void;
12
+ onProductCardClick: (product: SalesAgentProductCardProps) => void;
13
+ theme: Theme;
14
+ chatMessages: React.ReactNode;
15
+ scrollToBottom: () => void;
16
+ };
17
+
18
+ const slideVariants = {
19
+ // Chat → Grid: chat exits left (-100%), grid enters from right (100%)
20
+ // Grid → Chat: grid exits right (100%), chat enters from left (-100%)
21
+ enter: (isResults: boolean) => ({ x: isResults ? '100%' : '-100%' }),
22
+ center: { x: 0 },
23
+ exit: (isResults: boolean) => ({ x: isResults ? '100%' : '-100%' }),
24
+ };
25
+
26
+ export const SlideChatContent = ({
27
+ isResultsView,
28
+ isResultsViewRef,
29
+ resultsViewData,
30
+ onBackToChat,
31
+ onProductCardClick,
32
+ theme,
33
+ chatMessages,
34
+ scrollToBottom,
35
+ }: SlideChatContentProps) => {
36
+ const resultsViewContent = resultsViewData && (
37
+ <ResultsGridView
38
+ theme={theme}
39
+ searchQuery={resultsViewData.searchQuery}
40
+ products={resultsViewData.products}
41
+ onBackToChat={onBackToChat}
42
+ onProductCardClick={onProductCardClick}
43
+ />
44
+ );
45
+
46
+ return (
47
+ <div className="envive-tw-relative envive-tw-min-h-[200px] envive-tw-w-full envive-tw-overflow-hidden">
48
+ <AnimatePresence
49
+ initial={false}
50
+ mode="wait"
51
+ >
52
+ <motion.div
53
+ key={isResultsView ? 'results' : 'chat'}
54
+ custom={isResultsView}
55
+ variants={slideVariants}
56
+ initial="enter"
57
+ animate="center"
58
+ exit="exit"
59
+ transition={{ duration: 0.3, ease: 'easeInOut' }}
60
+ onAnimationComplete={() => {
61
+ if (!isResultsViewRef.current) {
62
+ scrollToBottom();
63
+ }
64
+ }}
65
+ className="envive-tw-w-full"
66
+ >
67
+ {isResultsView ? resultsViewContent : chatMessages}
68
+ </motion.div>
69
+ </AnimatePresence>
70
+ </div>
71
+ );
72
+ };
@@ -1,4 +1,6 @@
1
1
  import { Layout } from './Layout';
2
+ import { ResultsGridView } from './ResultsGridView';
3
+ import { SlideChatContent } from './SlideChatContent';
2
4
  import { UserMessage } from './UserMessage';
3
5
  import { AgentMessage } from './AgentMessage';
4
6
  import { ChatMessages } from './ChatMessages';
@@ -11,6 +13,8 @@ import { MessageDivider } from './MessageDivider';
11
13
 
12
14
  export const FloatingChatComponents = {
13
15
  Layout,
16
+ ResultsGridView,
17
+ SlideChatContent,
14
18
  UserMessage,
15
19
  AgentMessage,
16
20
  ChatMessages,
@@ -0,0 +1,49 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { getCleanProducts } from '../utils/functions';
3
+
4
+ export type ResultsViewData = {
5
+ products: ReturnType<typeof getCleanProducts>;
6
+ searchQuery: string;
7
+ };
8
+
9
+ type UseResultsViewProps = {
10
+ scrollToBottom: () => void;
11
+ };
12
+
13
+ export const useProductResultsView = ({ scrollToBottom }: UseResultsViewProps) => {
14
+ const [resultsViewData, setResultsViewData] = useState<ResultsViewData | null>(null);
15
+ const isResultsView = resultsViewData !== null;
16
+
17
+ const scrollContainerRef = useRef<HTMLDivElement>(null);
18
+ const isResultsViewRef = useRef(isResultsView);
19
+ isResultsViewRef.current = isResultsView;
20
+
21
+ const handleBackToChat = () => {
22
+ setResultsViewData(null);
23
+ };
24
+
25
+ // Scroll to top when navigating to results view
26
+ useEffect(() => {
27
+ if (isResultsView && scrollContainerRef.current) {
28
+ scrollContainerRef.current.scrollTo({ top: 0, behavior: 'smooth' });
29
+ }
30
+ }, [isResultsView]);
31
+
32
+ // Scroll to bottom when navigating back to chat
33
+ const prevIsResultsViewRef = useRef(isResultsView);
34
+ useEffect(() => {
35
+ if (prevIsResultsViewRef.current && !isResultsView) {
36
+ scrollToBottom();
37
+ }
38
+ prevIsResultsViewRef.current = isResultsView;
39
+ }, [isResultsView, scrollToBottom]);
40
+
41
+ return {
42
+ resultsViewData,
43
+ setResultsViewData,
44
+ isResultsView,
45
+ scrollContainerRef,
46
+ isResultsViewRef,
47
+ handleBackToChat,
48
+ };
49
+ };
@@ -121,6 +121,28 @@ export const getCleanOrders = (messages: Message[]) => {
121
121
 
122
122
  export const MOST_RELEVANT_DISPLAY_LIMIT = 8;
123
123
 
124
+ /**
125
+ * Extracts the search query from a message block (or previous blocks) that led to product results.
126
+ * Looks for QueryTyped (metadata.content) or SuggestionClicked (metadata.suggestionContent).
127
+ */
128
+ export const getSearchQueryFromMessageBlock = (
129
+ messages: Message[][],
130
+ productBlockIndex: number,
131
+ ): string => {
132
+ for (let i = productBlockIndex; i >= 0; i -= 1) {
133
+ const block = messages[i];
134
+ const queryTyped = block.find(msg => msg.type === MessageType.QueryTyped);
135
+ if (queryTyped && 'content' in queryTyped.metadata) {
136
+ return (queryTyped.metadata as { content?: string }).content ?? '';
137
+ }
138
+ const suggestionClicked = block.find(msg => msg.type === MessageType.SuggestionClicked);
139
+ if (suggestionClicked && 'suggestionContent' in suggestionClicked.metadata) {
140
+ return (suggestionClicked.metadata as { suggestionContent?: string }).suggestionContent ?? '';
141
+ }
142
+ }
143
+ return '';
144
+ };
145
+
124
146
  export const getProductCarouselDisplayInfo = <T>(
125
147
  products: T[],
126
148
  numberOfProducts?: number,
@@ -1,20 +1,45 @@
1
1
  /* eslint-disable no-console */
2
2
 
3
3
  class Logger {
4
- static logInfo(message: string, ...args: unknown[]): void {
5
- console.info(`INFO: ${message}`, ...args);
4
+ private readonly caller: string;
5
+
6
+ /* Creates a new Logger instance.
7
+ * @param caller - The caller of the logger.
8
+ */
9
+ constructor(caller: string) {
10
+ this.caller = caller;
11
+ }
12
+
13
+ static getTimestamp(): string {
14
+ return new Date().toISOString();
15
+ }
16
+
17
+ logInfo(message: string, ...args: unknown[]): void {
18
+ console.info(
19
+ `INFO: [envive-ai] ${Logger.getTimestamp()} - ${this.caller} - ${message}`,
20
+ ...args,
21
+ );
6
22
  }
7
23
 
8
- static logDebug(message: string, ...args: unknown[]): void {
9
- console.debug(`DEBUG: ${message}`, ...args);
24
+ logDebug(message: string, ...args: unknown[]): void {
25
+ console.debug(
26
+ `DEBUG: [envive-ai] ${Logger.getTimestamp()} - ${this.caller} - ${message}`,
27
+ ...args,
28
+ );
10
29
  }
11
30
 
12
- static logError(message: string, error: unknown | undefined, ...args: unknown[]): void {
13
- console.error(`ERROR: ${message} error=${error}`, args);
31
+ logError(message: string, error: unknown | undefined, ...args: unknown[]): void {
32
+ console.error(
33
+ `ERROR: [envive-ai] ${Logger.getTimestamp()} - ${this.caller} - ${message} error=${error}`,
34
+ args,
35
+ );
14
36
  }
15
37
 
16
- static logWarn(message: string, error: unknown | undefined, ...args: unknown[]): void {
17
- console.warn(`WARN: ${message} error=${error}`, args);
38
+ logWarn(message: string, error: unknown | undefined, ...args: unknown[]): void {
39
+ console.warn(
40
+ `WARN: [envive-ai] ${Logger.getTimestamp()} - ${this.caller} - ${message} error=${error}`,
41
+ args,
42
+ );
18
43
  }
19
44
  }
20
45