@envive-ai/react-widgets-v3 0.3.21 → 0.3.23

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 (91) hide show
  1. package/dist/debug/chatEmbed.cjs +1 -1
  2. package/dist/debug/chatEmbed.js +1 -1
  3. package/dist/debug/reportIssue.cjs +1 -1
  4. package/dist/debug/reportIssue.js +1 -1
  5. package/dist/hocs/withBaseWidget/types.d.cts +5 -6
  6. package/dist/hocs/withBaseWidget/types.d.ts +5 -6
  7. package/dist/hocs/withBaseWidget/withBaseWidget.cjs +37 -43
  8. package/dist/hocs/withBaseWidget/withBaseWidget.d.cts +2 -2
  9. package/dist/hocs/withBaseWidget/withBaseWidget.d.ts +2 -2
  10. package/dist/hocs/withBaseWidget/withBaseWidget.js +39 -45
  11. package/dist/packages/widgets/dist/SearchResults/SearchResultsWidget.d.ts +2 -2
  12. package/dist/packages/widgets/dist/SearchZeroState/SearchZeroStateWidget.d.ts +2 -2
  13. package/dist/packages/widgets/dist/SuggestionBar/SuggestionBar.d.ts +2 -2
  14. package/dist/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.cjs +34 -8
  15. package/dist/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.d.cts +3 -3
  16. package/dist/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.d.ts +3 -3
  17. package/dist/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.js +35 -9
  18. package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.cjs +13 -5
  19. package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.d.cts +3 -3
  20. package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.d.ts +3 -3
  21. package/dist/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.js +13 -5
  22. package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.cjs +32 -15
  23. package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.d.cts +3 -3
  24. package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.d.ts +3 -3
  25. package/dist/widgets/ChatPreviewWidget/ChatPreviewWidget.js +35 -18
  26. package/dist/widgets/FloatingChatWidget/FloatingChatWidget.cjs +36 -32
  27. package/dist/widgets/FloatingChatWidget/FloatingChatWidget.d.cts +2 -2
  28. package/dist/widgets/FloatingChatWidget/FloatingChatWidget.js +37 -33
  29. package/dist/widgets/FloatingChatWidget/hooks/useAutoPopup.cjs +2 -2
  30. package/dist/widgets/FloatingChatWidget/hooks/useAutoPopup.js +2 -2
  31. package/dist/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.cjs +7 -1
  32. package/dist/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.js +7 -1
  33. package/dist/widgets/ProductCardWidget/ProductCardWidget.cjs +31 -15
  34. package/dist/widgets/ProductCardWidget/ProductCardWidget.d.cts +2 -2
  35. package/dist/widgets/ProductCardWidget/ProductCardWidget.d.ts +2 -2
  36. package/dist/widgets/ProductCardWidget/ProductCardWidget.js +33 -17
  37. package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.cjs +33 -16
  38. package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.d.cts +3 -3
  39. package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.d.ts +3 -3
  40. package/dist/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.js +36 -19
  41. package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.cjs +3 -12
  42. package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.d.cts +2 -2
  43. package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.d.ts +2 -2
  44. package/dist/widgets/PromptCarouselWidget/PromptCarouselWidget.js +5 -14
  45. package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.d.cts +2 -2
  46. package/dist/widgets/SocialProofFlowWidget/SocialProofFlowWidget.d.ts +2 -2
  47. package/dist/widgets/SocialProofWidget/SocialProofWidget.cjs +34 -17
  48. package/dist/widgets/SocialProofWidget/SocialProofWidget.d.cts +3 -3
  49. package/dist/widgets/SocialProofWidget/SocialProofWidget.d.ts +3 -3
  50. package/dist/widgets/SocialProofWidget/SocialProofWidget.js +37 -20
  51. package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.cjs +2 -11
  52. package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.d.cts +2 -2
  53. package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.d.ts +2 -2
  54. package/dist/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.js +4 -13
  55. package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.d.cts +2 -2
  56. package/dist/widgets/TypingAnimationFlowWidget/TypingAnimationFlowWidget.d.ts +2 -2
  57. package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.cjs +31 -14
  58. package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.d.cts +3 -3
  59. package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.d.ts +3 -3
  60. package/dist/widgets/TypingAnimationWidget/TypingAnimationWidget.js +34 -17
  61. package/dist/widgets/dist/SearchResults/SearchResultsWidget.d.cts +2 -2
  62. package/dist/widgets/dist/SearchZeroState/SearchZeroStateWidget.d.cts +2 -2
  63. package/dist/widgets/dist/SuggestionBar/SuggestionBar.d.cts +2 -2
  64. package/dist/widgets/hooks/useGetWidgetStatus.cjs +1 -1
  65. package/dist/widgets/hooks/useGetWidgetStatus.js +1 -1
  66. package/dist/widgets/utils/functions.cjs +1 -1
  67. package/dist/widgets/utils/functions.js +1 -1
  68. package/package.json +1 -1
  69. package/src/hocs/withBaseWidget/__tests__/withBaseWidget.test.tsx +68 -302
  70. package/src/hocs/withBaseWidget/types.ts +6 -6
  71. package/src/hocs/withBaseWidget/withBaseWidget.tsx +46 -61
  72. package/src/widgets/ChatPreviewComparisonWidget/ChatPreviewComparisonWidget.tsx +33 -0
  73. package/src/widgets/ChatPreviewLoadingWidget/ChatPreviewLoadingWidget.tsx +9 -1
  74. package/src/widgets/ChatPreviewWidget/ChatPreviewWidget.tsx +34 -13
  75. package/src/widgets/ChatPreviewWidget/__tests__/ChatPreviewWidget.test.tsx +11 -5
  76. package/src/widgets/FloatingChatWidget/FloatingChatWidget.tsx +56 -38
  77. package/src/widgets/FloatingChatWidget/__tests__/FloatingChatWidget.test.tsx +11 -5
  78. package/src/widgets/FullPageSalesAgentWidget/FullPageSalesAgentWidget.tsx +8 -0
  79. package/src/widgets/ProductCardWidget/ProductCardWidget.tsx +37 -15
  80. package/src/widgets/ProductCardWidget/__tests__/ProductCardWidget.test.tsx +12 -8
  81. package/src/widgets/PromptButtonCarouselWithImageWidget/PromptButtonCarouselWithImageWidget.tsx +35 -13
  82. package/src/widgets/PromptButtonCarouselWithImageWidget/__tests__/PromptButtonCarouselWithImageWidget.test.tsx +19 -8
  83. package/src/widgets/PromptCarouselWidget/PromptCarouselWidget.tsx +1 -12
  84. package/src/widgets/PromptCarouselWidget/__tests__/PromptCarouselWidget.test.tsx +21 -8
  85. package/src/widgets/SocialProofWidget/SocialProofWidget.tsx +34 -12
  86. package/src/widgets/SocialProofWidget/__tests__/SocialProofWidget.test.tsx +19 -8
  87. package/src/widgets/TitledPromptCarouselWidget/TitledPromptCarouselWidget.tsx +1 -12
  88. package/src/widgets/TitledPromptCarouselWidget/__tests__/TitledPromptCarouselWidget.test.tsx +19 -8
  89. package/src/widgets/TypingAnimationWidget/TypingAnimationWidget.tsx +37 -14
  90. package/src/widgets/TypingAnimationWidget/__tests__/TypingAnimationWidget.test.tsx +19 -8
  91. package/src/widgets/__tests__/trackEventCanary.test.ts +1 -1
@@ -1,5 +1,4 @@
1
1
  import { RefObject } from 'react';
2
- import { SpiffyMetricsEventName } from '@envive-ai/react-hooks/contexts/amplitudeContext';
3
2
  import { HardcopyResponse } from '@envive-ai/react-hooks/contexts/hardcopyContext';
4
3
  import {
5
4
  OrgUIConfigV3,
@@ -22,11 +21,6 @@ export interface BaseWidgetProps {
22
21
  }
23
22
 
24
23
  export interface WithBaseWidgetOptions {
25
- /**
26
- * The Amplitude event name to track when the widget becomes visible.
27
- * Defaults to a generic component visible event if not provided.
28
- */
29
- visibilityEventName?: SpiffyMetricsEventName;
30
24
  /**
31
25
  * Additional properties to include with the visibility event.
32
26
  */
@@ -36,4 +30,10 @@ export interface WithBaseWidgetOptions {
36
30
  * Defaults to '0px'.
37
31
  */
38
32
  rootMargin?: string;
33
+
34
+ /**
35
+ * Whether to defer loading of the widget until it is visible.
36
+ * Defaults to true
37
+ */
38
+ deferLoading?: boolean;
39
39
  }
@@ -1,15 +1,12 @@
1
- import { useEffect, useRef, useState } from 'react';
1
+ import { useEffect, useMemo, useRef, useState } from 'react';
2
2
 
3
- import {
4
- SpiffyMetricsEventName,
5
- useAmplitude,
6
- } from '@envive-ai/react-hooks/contexts/amplitudeContext';
3
+ import { useAmplitude } from '@envive-ai/react-hooks/contexts/amplitudeContext';
4
+ import { useTrackComponentVisibleEvent } from '@envive-ai/react-hooks/hooks/TrackComponentVisibleEvent';
7
5
  import { HardcopyResponse, useHardcopy } from '@envive-ai/react-hooks/contexts/hardcopyContext';
8
6
  import { usePage } from '@envive-ai/react-hooks/contexts/pageContext';
9
7
  import { useWidgetConfig } from '@envive-ai/react-hooks/contexts/widgetConfigContext';
10
8
  import { OrgUIConfigV3, WidgetConfigV3 } from '@envive-ai/react-hooks/contexts/typesV3';
11
9
  import { useUiConfig } from '@envive-ai/react-hooks/contexts/uiConfigContext';
12
-
13
10
  import { BaseWidgetProps, WithBaseWidgetOptions } from './types';
14
11
 
15
12
  export function withBaseWidget<P extends BaseWidgetProps>(
@@ -19,10 +16,10 @@ export function withBaseWidget<P extends BaseWidgetProps>(
19
16
  const WrappedWidget = (props: P) => {
20
17
  const { widgetConfigId, widgetType } = props;
21
18
  const widgetRef = useRef<HTMLDivElement>(null);
22
- const hasTrackedEvent = useRef(false);
23
- const { trackEvent, isReady } = useAmplitude();
19
+ const { isReady } = useAmplitude();
24
20
 
25
21
  const [hardcopyContent, setHardcopyContent] = useState<HardcopyResponse | undefined>(undefined);
22
+ const hardcopyFetchedRef = useRef(false);
26
23
  const { getHardcopy, isLoading: isHardcopyLoading } = useHardcopy();
27
24
 
28
25
  const { userEvent, isLoading: isPageLoading } = usePage();
@@ -36,87 +33,75 @@ export function withBaseWidget<P extends BaseWidgetProps>(
36
33
  const isLoading =
37
34
  isHardcopyLoading || isPageLoading || isWidgetConfigLoading || isUiConfigLoading;
38
35
 
39
- useEffect(() => {
40
- if (!isReady || hasTrackedEvent.current || !widgetRef.current) {
41
- return undefined;
36
+ const visibilityEventProps = {
37
+ widget_config_id: widgetConfigId,
38
+ widget_type: widgetType,
39
+ ...options?.visibilityEventProps,
40
+ };
41
+ const { isVisible } = useTrackComponentVisibleEvent(
42
+ widgetRef,
43
+ visibilityEventProps,
44
+ options?.rootMargin || '0px',
45
+ isReady,
46
+ );
47
+ const startLoading: boolean = useMemo(() => {
48
+ if (options?.deferLoading === false) {
49
+ return true;
42
50
  }
43
-
44
- const observer = new IntersectionObserver(
45
- ([entry]) => {
46
- if (entry.isIntersecting && !hasTrackedEvent.current) {
47
- const eventName =
48
- options?.visibilityEventName || SpiffyMetricsEventName.ChatComponentVisible;
49
-
50
- trackEvent({
51
- eventName,
52
- eventProps: {
53
- widget_config_id: widgetConfigId,
54
- widget_type: widgetType,
55
- ...options?.visibilityEventProps,
56
- },
57
- });
58
-
59
- hasTrackedEvent.current = true;
60
- observer.disconnect();
61
- }
62
- },
63
- {
64
- rootMargin: options?.rootMargin || '0px',
65
- },
66
- );
67
-
68
- observer.observe(widgetRef.current);
69
-
70
- return () => {
71
- observer.disconnect();
72
- };
73
- }, [isReady, widgetConfigId, widgetType, trackEvent]);
51
+ // Defer loading by default until the widget is visible
52
+ return isVisible && !!widgetConfigId && !!widgetType;
53
+ }, [isVisible, widgetConfigId, widgetType]);
74
54
 
75
55
  useEffect(() => {
76
- if (!widgetType || !userEvent) {
56
+ if (!startLoading || hardcopyFetchedRef.current) {
77
57
  return;
78
58
  }
59
+ hardcopyFetchedRef.current = true;
79
60
  getHardcopy({ widgetType, userEvent })
80
61
  .then(setHardcopyContent)
81
62
  .catch(() => {
82
63
  // Silently handle errors - widget will render without hardcopy content
83
64
  });
84
- }, [widgetConfigId, widgetType, getHardcopy, userEvent]);
65
+ }, [startLoading, widgetType, getHardcopy, userEvent, widgetConfigId]);
85
66
 
86
67
  useEffect(() => {
68
+ if (!startLoading) {
69
+ return;
70
+ }
87
71
  getUiConfig()
88
72
  .then(setUiConfig)
89
73
  .catch(() => {
90
74
  // Silently handle errors - widget will render without ui config
91
75
  });
92
- }, [getUiConfig, widgetConfigId, widgetType]);
76
+ }, [startLoading, getUiConfig, widgetConfigId, widgetType]);
93
77
 
94
78
  useEffect(() => {
95
- if (!widgetConfigId || !widgetType) {
79
+ if (!startLoading) {
96
80
  return;
97
81
  }
98
82
 
99
83
  getWidgetConfig({ widgetConfigId, widgetType })
100
84
  .then(setWidgetConfig)
101
- .catch(e => {
102
- console.error('Error getting widget config', widgetConfigId, widgetType, e);
85
+ .catch(() => {
103
86
  // Silently handle errors - widget will render without widget config
104
87
  });
105
- }, [widgetConfigId, widgetType, getWidgetConfig]);
88
+ }, [startLoading, widgetConfigId, widgetType, getWidgetConfig]);
106
89
 
107
90
  return (
108
- <Widget
109
- {...props}
110
- isLoading={isLoading}
111
- isHardcopyLoading={isHardcopyLoading}
112
- isPageLoading={isPageLoading}
113
- isWidgetConfigLoading={isWidgetConfigLoading}
114
- isUiConfigLoading={isUiConfigLoading}
115
- uiConfig={uiConfig}
116
- widgetConfig={widgetConfig}
117
- hardcopyContent={hardcopyContent}
118
- observedWidget={widgetRef}
119
- />
91
+ <span ref={widgetRef}>
92
+ <Widget
93
+ {...props}
94
+ isLoading={isLoading}
95
+ isHardcopyLoading={isHardcopyLoading}
96
+ isPageLoading={isPageLoading}
97
+ isWidgetConfigLoading={isWidgetConfigLoading}
98
+ isUiConfigLoading={isUiConfigLoading}
99
+ uiConfig={uiConfig}
100
+ widgetConfig={widgetConfig}
101
+ hardcopyContent={hardcopyContent}
102
+ observedWidget={widgetRef}
103
+ />
104
+ </span>
120
105
  );
121
106
  };
122
107
 
@@ -6,9 +6,11 @@ import {
6
6
  } from '@envive-ai/react-hooks/contexts/typesV3';
7
7
  import { useChatToggle } from '@envive-ai/react-hooks/hooks/ChatToggle';
8
8
  import { useAtomValue } from 'jotai';
9
+ import { useFeatureFlagService } from '@envive-ai/react-hooks/contexts/featureFlagServiceContext';
9
10
 
10
11
  import {
11
12
  ChatElementDisplayLocationV3,
13
+ FeatureGates,
12
14
  Suggestion,
13
15
  VariantTypeEnum,
14
16
  } from '@envive-ai/react-hooks/application/models';
@@ -89,6 +91,11 @@ const ChatPreviewComparisonWidgetHandler = (props: BaseWidgetProps) => {
89
91
 
90
92
  const hideLogo = uiConfig?.lookAndFeel?.hideWidgetLogo;
91
93
 
94
+ const voiceInputEnabled = uiConfig?.lookAndFeel?.voiceInputEnabled;
95
+ const { featureFlagService } = useFeatureFlagService();
96
+ const isVoiceInputEnabled =
97
+ featureFlagService?.isFeatureGateEnabled(FeatureGates.IsVoiceInputEnabled) || voiceInputEnabled;
98
+
92
99
  const handlePromptButtonClick = useCallback(
93
100
  (suggestion: Suggestion) => {
94
101
  onSuggestionClicked(
@@ -112,6 +119,29 @@ const ChatPreviewComparisonWidgetHandler = (props: BaseWidgetProps) => {
112
119
  });
113
120
  }, [openChat]);
114
121
 
122
+ const handleTranscriptionStarted = useCallback(() => {
123
+ trackWidgetInteraction({
124
+ eventName: EnviveMetricsEventName.WidgetInteraction,
125
+ trigger: {
126
+ widget: WidgetInteractionComponent.POST_INTERACTION_COMPARISON,
127
+ widget_interaction: WidgetInteractionType.VOICE_TRANSCRIPTION_STARTED,
128
+ },
129
+ });
130
+ }, []);
131
+
132
+ const handleTranscriptionCompleted = useCallback((transcript: string) => {
133
+ trackWidgetInteraction({
134
+ eventName: EnviveMetricsEventName.WidgetInteraction,
135
+ trigger: {
136
+ widget: WidgetInteractionComponent.POST_INTERACTION_COMPARISON,
137
+ widget_interaction: WidgetInteractionType.VOICE_TRANSCRIPTION_COMPLETED,
138
+ widget_interaction_data: {
139
+ transcription: transcript,
140
+ },
141
+ },
142
+ });
143
+ }, []);
144
+
115
145
  const handleLinkClick = (url: string) => {
116
146
  trackWidgetInteraction({
117
147
  eventName: EnviveMetricsEventName.WidgetInteraction,
@@ -156,6 +186,7 @@ const ChatPreviewComparisonWidgetHandler = (props: BaseWidgetProps) => {
156
186
  textFieldPlaceholderText: hardCopyContent?.textFieldPlaceholderText,
157
187
  images: images ?? [],
158
188
  logoSrc: logoSrc ?? undefined,
189
+ voiceInputEnabled: isVoiceInputEnabled,
159
190
  };
160
191
 
161
192
  const widgetEventProps: ChatPreviewComparisonProps['widgetEventProps'] = {
@@ -168,6 +199,8 @@ const ChatPreviewComparisonWidgetHandler = (props: BaseWidgetProps) => {
168
199
  handlePromptButtonTouchEnd: onTouchEnd,
169
200
  handleLinkClick,
170
201
  handleTextFieldClick,
202
+ onTranscriptionStarted: handleTranscriptionStarted,
203
+ onTranscriptionCompleted: handleTranscriptionCompleted,
171
204
  };
172
205
 
173
206
  return (
@@ -3,9 +3,11 @@ import {
3
3
  WidgetTypeV3,
4
4
  } from '@envive-ai/react-hooks/contexts/typesV3';
5
5
  import { useAtomValue } from 'jotai';
6
+ import { useFeatureFlagService } from '@envive-ai/react-hooks/contexts/featureFlagServiceContext';
7
+ import { FeatureGates } from '@envive-ai/react-hooks/application/models';
6
8
 
7
- import { ChatPreviewLoading } from '@envive-ai/react-toolkit-v3/ChatPreviewLoading';
8
9
  import { chatPreviewLoadingDataAtom } from '@envive-ai/react-hooks/atoms/widget';
10
+ import { ChatPreviewLoading } from '@envive-ai/react-toolkit-v3/ChatPreviewLoading';
9
11
  import { BaseWidgetProps } from '../../hocs/withBaseWidget/types';
10
12
  import { withBaseWidget } from '../../hocs/withBaseWidget/withBaseWidget';
11
13
  import { PromptCarouselWidget } from '../PromptCarouselWidget/PromptCarouselWidget';
@@ -19,6 +21,7 @@ const ChatPreviewLoadingWidgetHandler = (props: BaseWidgetProps) => {
19
21
 
20
22
  const chatPreviewLoadingDataMap = useAtomValue(chatPreviewLoadingDataAtom);
21
23
  const chatPreviewLoadingData = chatPreviewLoadingDataMap[widgetConfigId] ?? undefined;
24
+ const { featureFlagService } = useFeatureFlagService();
22
25
 
23
26
  const isCacheEmpty =
24
27
  !chatPreviewLoadingData ||
@@ -48,6 +51,10 @@ const ChatPreviewLoadingWidgetHandler = (props: BaseWidgetProps) => {
48
51
  const hideTextField =
49
52
  chatPreviewLoadingWidgetConfig?.hideTextField ?? chatPreviewLoadingData?.hideTextField;
50
53
 
54
+ const voiceInputEnabled = uiConfig?.lookAndFeel?.voiceInputEnabled;
55
+ const isVoiceInputEnabled =
56
+ featureFlagService?.isFeatureGateEnabled(FeatureGates.IsVoiceInputEnabled) || voiceInputEnabled;
57
+
51
58
  return (
52
59
  <ChatPreviewLoading
53
60
  id={id}
@@ -58,6 +65,7 @@ const ChatPreviewLoadingWidgetHandler = (props: BaseWidgetProps) => {
58
65
  hideLogo={hideLogo}
59
66
  logoSrc={logoSrc}
60
67
  titleLabel={titleLabel}
68
+ voiceInputEnabled={isVoiceInputEnabled}
61
69
  />
62
70
  );
63
71
  };
@@ -3,16 +3,17 @@ import { useSalesAgent } from '@envive-ai/react-hooks/contexts/salesAgentContext
3
3
  import { ChatPreviewWidgetV3Config, WidgetTypeV3 } from '@envive-ai/react-hooks/contexts/typesV3';
4
4
  import { useChatToggle } from '@envive-ai/react-hooks/hooks/ChatToggle';
5
5
  import { useAtomValue } from 'jotai';
6
+ import { useFeatureFlagService } from '@envive-ai/react-hooks/contexts/featureFlagServiceContext';
6
7
 
7
8
  import { Theme } from '@envive-ai/react-toolkit-v3/Tokens';
8
9
 
9
10
  import {
10
11
  ChatElementDisplayLocationV3,
12
+ FeatureGates,
11
13
  Suggestion,
12
14
  } from '@envive-ai/react-hooks/application/models';
13
15
  import {
14
16
  EnviveMetricsEventName,
15
- SpiffyMetricsEventName,
16
17
  useAmplitude,
17
18
  } from '@envive-ai/react-hooks/contexts/amplitudeContext';
18
19
  import { useWidgetInteraction } from '@envive-ai/react-hooks/hooks/WidgetInteraction';
@@ -23,7 +24,7 @@ import {
23
24
  import { ChatPreview } from '@envive-ai/react-toolkit-v3/ChatPreview';
24
25
  import { ChatPreviewProps } from '@envive-ai/react-toolkit-v3/ChatPreview/types/types';
25
26
  import { usePromptCarouselAnalytics } from '@envive-ai/react-toolkit-v3/PromptCarousel';
26
- import { useCallback, useEffect, useMemo } from 'react';
27
+ import { useCallback, useMemo } from 'react';
27
28
  import { BaseWidgetProps } from '../../hocs/withBaseWidget/types';
28
29
  import { withBaseWidget } from '../../hocs/withBaseWidget/withBaseWidget';
29
30
  import { ChatPreviewLoadingWidgetWithBaseWidget } from '../ChatPreviewLoadingWidget/ChatPreviewLoadingWidget';
@@ -67,21 +68,15 @@ const ChatPreviewWidgetHandler = (props: BaseWidgetProps) => {
67
68
  onTouchEnd,
68
69
  } = usePromptCarouselAnalytics(WidgetInteractionComponent.CHAT_PREVIEW, text => text);
69
70
 
70
- useEffect(() => {
71
- // TODO: -Add Widget Interaction here
72
- trackEvent({
73
- eventName: SpiffyMetricsEventName.ChatComponentVisible,
74
- eventProps: {
75
- widget_config_id: widgetConfigId,
76
- widget_type: WidgetTypeV3.ChatPreviewV3,
77
- },
78
- });
79
- }, [trackEvent, widgetConfigId]);
80
-
81
71
  const logoSrc = uiConfig?.lookAndFeel?.widgetLogoSrc;
82
72
 
83
73
  const hideLogo = uiConfig?.lookAndFeel?.hideWidgetLogo;
84
74
 
75
+ const voiceInputEnabled = uiConfig?.lookAndFeel?.voiceInputEnabled;
76
+ const { featureFlagService } = useFeatureFlagService();
77
+ const isVoiceInputEnabled =
78
+ featureFlagService?.isFeatureGateEnabled(FeatureGates.IsVoiceInputEnabled) || voiceInputEnabled;
79
+
85
80
  const handlePromptButtonClick = useCallback(
86
81
  (suggestion: Suggestion) => {
87
82
  onSuggestionClicked(suggestion, ChatElementDisplayLocationV3.CHAT_PREVIEW_PROMPT_BUTTON);
@@ -102,6 +97,29 @@ const ChatPreviewWidgetHandler = (props: BaseWidgetProps) => {
102
97
  });
103
98
  }, [openChat]);
104
99
 
100
+ const handleTranscriptionStarted = useCallback(() => {
101
+ trackWidgetInteraction({
102
+ eventName: EnviveMetricsEventName.WidgetInteraction,
103
+ trigger: {
104
+ widget: WidgetInteractionComponent.CHAT_PREVIEW,
105
+ widget_interaction: WidgetInteractionType.VOICE_TRANSCRIPTION_STARTED,
106
+ },
107
+ });
108
+ }, []);
109
+
110
+ const handleTranscriptionCompleted = useCallback((transcript: string) => {
111
+ trackWidgetInteraction({
112
+ eventName: EnviveMetricsEventName.WidgetInteraction,
113
+ trigger: {
114
+ widget: WidgetInteractionComponent.CHAT_PREVIEW,
115
+ widget_interaction: WidgetInteractionType.VOICE_TRANSCRIPTION_COMPLETED,
116
+ widget_interaction_data: {
117
+ transcription: transcript,
118
+ },
119
+ },
120
+ });
121
+ }, []);
122
+
105
123
  const handleLinkClick = (url: string) => {
106
124
  trackWidgetInteraction({
107
125
  eventName: EnviveMetricsEventName.WidgetInteraction,
@@ -140,6 +158,7 @@ const ChatPreviewWidgetHandler = (props: BaseWidgetProps) => {
140
158
  promptButtons: hardCopyContent?.promptButtons,
141
159
  textFieldPlaceholderText: hardCopyContent?.textFieldPlaceholderText,
142
160
  logoSrc: logoSrc ?? undefined,
161
+ voiceInputEnabled: isVoiceInputEnabled,
143
162
  };
144
163
 
145
164
  const widgetEventProps: ChatPreviewProps['widgetEventProps'] = {
@@ -152,6 +171,8 @@ const ChatPreviewWidgetHandler = (props: BaseWidgetProps) => {
152
171
  handlePromptButtonTouchEnd: onTouchEnd,
153
172
  handleLinkClick,
154
173
  handleTextFieldClick,
174
+ onTranscriptionStarted: handleTranscriptionStarted,
175
+ onTranscriptionCompleted: handleTranscriptionCompleted,
155
176
  };
156
177
 
157
178
  return (
@@ -1,11 +1,11 @@
1
1
  import { render, waitFor } from '@testing-library/react';
2
- import { SpiffyMetricsEventName } from '@envive-ai/react-hooks/contexts/amplitudeContext';
3
2
  import { WidgetTypeV3 } from '@envive-ai/react-hooks/contexts/typesV3';
4
3
  import { UserEvent, UserEventCategory } from '@spiffy-ai/commerce-api-client';
5
4
  import { ChatPreviewWidget } from '../ChatPreviewWidget';
6
5
 
7
6
  const mockTrackEvent = vi.fn();
8
7
  const mockGetHardcopy = vi.fn();
8
+ const mockUseTrackComponentVisibleEvent = vi.fn();
9
9
 
10
10
  vi.mock('@envive-ai/react-toolkit-v3/ChatPreview', () => ({
11
11
  ChatPreview: () => <div data-testid="chat-preview-mock">Chat Preview</div>,
@@ -22,6 +22,10 @@ vi.mock('@envive-ai/react-hooks/contexts/amplitudeContext', () => ({
22
22
  },
23
23
  }));
24
24
 
25
+ vi.mock('@envive-ai/react-hooks/hooks/TrackComponentVisibleEvent', () => ({
26
+ useTrackComponentVisibleEvent: (...args: unknown[]) => mockUseTrackComponentVisibleEvent(...args),
27
+ }));
28
+
25
29
  vi.mock('@envive-ai/react-hooks/contexts/hardcopyContext', () => ({
26
30
  useHardcopy: () => ({
27
31
  getHardcopy: mockGetHardcopy,
@@ -100,13 +104,15 @@ describe('ChatPreviewWidget analytics', () => {
100
104
 
101
105
  await waitFor(
102
106
  () => {
103
- expect(mockTrackEvent).toHaveBeenCalledWith({
104
- eventName: SpiffyMetricsEventName.ChatComponentVisible,
105
- eventProps: {
107
+ expect(mockUseTrackComponentVisibleEvent).toHaveBeenCalledWith(
108
+ expect.any(Object),
109
+ {
106
110
  widget_config_id: 'test-config-1',
107
111
  widget_type: WidgetTypeV3.ChatPreviewV3,
108
112
  },
109
- });
113
+ '0px',
114
+ true,
115
+ );
110
116
  },
111
117
  { timeout: 3000 },
112
118
  );
@@ -1,7 +1,9 @@
1
- import { ChatElementDisplayLocationV3 } from '@envive-ai/react-hooks/application/models';
1
+ import {
2
+ ChatElementDisplayLocationV3,
3
+ FeatureGates,
4
+ } from '@envive-ai/react-hooks/application/models';
2
5
  import {
3
6
  EnviveMetricsEventName,
4
- SpiffyMetricsEventName,
5
7
  useAmplitude,
6
8
  } from '@envive-ai/react-hooks/contexts/amplitudeContext';
7
9
  import { useSalesAgent } from '@envive-ai/react-hooks/contexts/salesAgentContext';
@@ -11,7 +13,9 @@ import {
11
13
  WidgetTypeV3,
12
14
  } from '@envive-ai/react-hooks/contexts/typesV3';
13
15
  import { useChatToggle } from '@envive-ai/react-hooks/hooks/ChatToggle';
16
+ import { useTrackComponentVisibleEvent } from '@envive-ai/react-hooks/hooks/TrackComponentVisibleEvent';
14
17
  import { useWidgetInteraction } from '@envive-ai/react-hooks/hooks/WidgetInteraction';
18
+ import { useFeatureFlagService } from '@envive-ai/react-hooks/contexts/featureFlagServiceContext';
15
19
  import {
16
20
  Collapse,
17
21
  WidgetInteractionComponent,
@@ -92,6 +96,20 @@ const FloatingChatWidgetHandler = (props: FloatingChatWidgetHandlerProps) => {
92
96
  (customerServiceIntegration?.provider as CustomerServiceType) ??
93
97
  CustomerServiceType.unsupported;
94
98
 
99
+ const { featureFlagService } = useFeatureFlagService();
100
+ const originalVoiceInputEnabled = uiConfig?.lookAndFeel?.voiceInputEnabled;
101
+ const isVoiceInputEnabled =
102
+ featureFlagService?.isFeatureGateEnabled(FeatureGates.IsVoiceInputEnabled) ||
103
+ originalVoiceInputEnabled;
104
+
105
+ const modifiedLookAndFeelConfig = useMemo(
106
+ () => ({
107
+ ...uiConfig?.lookAndFeel,
108
+ voiceInputEnabled: isVoiceInputEnabled,
109
+ }),
110
+ [uiConfig?.lookAndFeel, isVoiceInputEnabled],
111
+ );
112
+
95
113
  // TODO: Get hardcopy content
96
114
  const { isOpen, openChat, closeChat, onHover } = useChatToggle();
97
115
 
@@ -139,21 +157,17 @@ const FloatingChatWidgetHandler = (props: FloatingChatWidgetHandlerProps) => {
139
157
 
140
158
  useAutoPopup({ autoPopupConfig: floatingChat?.autoPopupConfig });
141
159
 
142
- const { trackEvent } = useAmplitude();
143
- const hasTrackedEvent = useRef(false);
144
- // When we add support for initially hidden floating buttons, we will need to update this to handle that case
145
- useEffect(() => {
146
- if (buttonShouldRender && !hasTrackedEvent.current) {
147
- trackEvent({
148
- eventName: SpiffyMetricsEventName.ChatComponentVisible,
149
- eventProps: {
150
- widget_config_id: 'floating-button',
151
- widget_type: WidgetTypeV3.FloatingButtonV3,
152
- },
153
- });
154
- hasTrackedEvent.current = true;
155
- }
156
- }, [trackEvent, buttonShouldRender]);
160
+ const { isReady } = useAmplitude();
161
+ const floatingButtonVisibilityRef = useRef<HTMLDivElement>(null);
162
+ useTrackComponentVisibleEvent(
163
+ floatingButtonVisibilityRef,
164
+ {
165
+ widget_config_id: 'floating-button',
166
+ widget_type: WidgetTypeV3.FloatingButtonV3,
167
+ },
168
+ '0px',
169
+ isReady && buttonShouldRender,
170
+ );
157
171
 
158
172
  return (
159
173
  <CXButtonProvider
@@ -190,7 +204,7 @@ const FloatingChatWidgetHandler = (props: FloatingChatWidgetHandlerProps) => {
190
204
  salesAgentData={salesAgentData}
191
205
  hardcopyContent={hardcopyContent}
192
206
  floatingChatConfig={uiConfig?.floatingChat ?? ({} as FloatingChatConfig)}
193
- lookAndFeelConfig={uiConfig?.lookAndFeel ?? ({} as LookAndFeelConfig)}
207
+ lookAndFeelConfig={modifiedLookAndFeelConfig ?? ({} as LookAndFeelConfig)}
194
208
  isCXButtonSwitchEnabled={!!isSwitchEnabled?.()}
195
209
  isFloatingChatOpen={effectiveIsOpen}
196
210
  onToggleCXButton={toggle}
@@ -222,24 +236,26 @@ const FloatingChatWidgetHandler = (props: FloatingChatWidgetHandlerProps) => {
222
236
 
223
237
  {/* Render button when preview button only OR when chat is closed */}
224
238
  {buttonShouldRender && (
225
- <FloatingButton
226
- id={FLOATING_BUTTON_ID}
227
- variant={floatingButton?.style as FloatingButtonVariant}
228
- mode={floatingButton?.mode as FloatingButtonMode}
229
- backgroundColor={floatingButton?.backgroundColor as FloatingButtonBackgroundColor}
230
- onClick={
231
- previewButtonOnly
232
- ? () => {}
233
- : () => openChat(ChatElementDisplayLocationV3.FLOATING_BUTTON)
234
- }
235
- onMouseOver={onHover}
236
- customIcon={floatingButton?.iconSVGSrc}
237
- show={floatingButton?.showOption as FloatingButtonShow}
238
- location={floatingButton?.position as FloatingButtonLocation}
239
- hasInteractionHappened={userHasInteractedValue}
240
- ariaLabel="Open chat"
241
- previewMode={!!previewButtonOnly}
242
- />
239
+ <div ref={floatingButtonVisibilityRef}>
240
+ <FloatingButton
241
+ id={FLOATING_BUTTON_ID}
242
+ variant={floatingButton?.style as FloatingButtonVariant}
243
+ mode={floatingButton?.mode as FloatingButtonMode}
244
+ backgroundColor={floatingButton?.backgroundColor as FloatingButtonBackgroundColor}
245
+ onClick={
246
+ previewButtonOnly
247
+ ? () => {}
248
+ : () => openChat(ChatElementDisplayLocationV3.FLOATING_BUTTON)
249
+ }
250
+ onMouseOver={onHover}
251
+ customIcon={floatingButton?.iconSVGSrc}
252
+ show={floatingButton?.showOption as FloatingButtonShow}
253
+ location={floatingButton?.position as FloatingButtonLocation}
254
+ hasInteractionHappened={userHasInteractedValue}
255
+ ariaLabel="Open chat"
256
+ previewMode={!!previewButtonOnly}
257
+ />
258
+ </div>
243
259
  )}
244
260
  </>
245
261
  )}
@@ -247,8 +263,10 @@ const FloatingChatWidgetHandler = (props: FloatingChatWidgetHandlerProps) => {
247
263
  );
248
264
  };
249
265
 
250
- const FloatingChatWidgetWithBaseWidget =
251
- withBaseWidget<FloatingChatWidgetHandlerProps>(FloatingChatWidgetHandler);
266
+ const FloatingChatWidgetWithBaseWidget = withBaseWidget<FloatingChatWidgetHandlerProps>(
267
+ FloatingChatWidgetHandler,
268
+ { deferLoading: false },
269
+ );
252
270
 
253
271
  export interface FloatingChatWidgetProps {
254
272
  previewButtonOnly?: boolean;
@@ -1,11 +1,11 @@
1
1
  import { render, waitFor } from '@testing-library/react';
2
- import { SpiffyMetricsEventName } from '@envive-ai/react-hooks/contexts/amplitudeContext';
3
2
  import { WidgetTypeV3 } from '@envive-ai/react-hooks/contexts/typesV3';
4
3
  import { UserEvent, UserEventCategory } from '@spiffy-ai/commerce-api-client';
5
4
  import { FloatingChatWidget } from '../FloatingChatWidget';
6
5
 
7
6
  const mockTrackEvent = vi.fn();
8
7
  const mockGetHardcopy = vi.fn();
8
+ const mockUseTrackComponentVisibleEvent = vi.fn();
9
9
 
10
10
  vi.mock('@envive-ai/react-toolkit-v3/FloatingButton', () => ({
11
11
  FloatingButton: () => <div data-testid="floating-button">Floating Button</div>,
@@ -22,6 +22,10 @@ vi.mock('@envive-ai/react-hooks/contexts/amplitudeContext', () => ({
22
22
  },
23
23
  }));
24
24
 
25
+ vi.mock('@envive-ai/react-hooks/hooks/TrackComponentVisibleEvent', () => ({
26
+ useTrackComponentVisibleEvent: (...args: unknown[]) => mockUseTrackComponentVisibleEvent(...args),
27
+ }));
28
+
25
29
  vi.mock('@envive-ai/react-hooks/contexts/hardcopyContext', () => ({
26
30
  useHardcopy: () => ({
27
31
  getHardcopy: mockGetHardcopy,
@@ -105,13 +109,15 @@ describe('FloatingChatWidget analytics', () => {
105
109
 
106
110
  await waitFor(
107
111
  () => {
108
- expect(mockTrackEvent).toHaveBeenCalledWith({
109
- eventName: SpiffyMetricsEventName.ChatComponentVisible,
110
- eventProps: {
112
+ expect(mockUseTrackComponentVisibleEvent).toHaveBeenCalledWith(
113
+ expect.any(Object),
114
+ {
111
115
  widget_config_id: 'floating-button',
112
116
  widget_type: WidgetTypeV3.FloatingButtonV3,
113
117
  },
114
- });
118
+ '0px',
119
+ true,
120
+ );
115
121
  },
116
122
  { timeout: 3000 },
117
123
  );
@@ -7,6 +7,8 @@ import {
7
7
  import { FullPageSalesAgent } from '@envive-ai/react-toolkit-v3/FullPageSalesAgent';
8
8
  import { Theme } from '@envive-ai/react-toolkit-v3/Tokens';
9
9
  import { BaseWidgetProps, withBaseWidget } from 'src/hocs/withBaseWidget';
10
+ import { useFeatureFlagService } from '@envive-ai/react-hooks/contexts/featureFlagServiceContext';
11
+ import { FeatureGates } from '@envive-ai/react-hooks/application/models';
10
12
 
11
13
  const FullPageSalesAgentHandler = ({
12
14
  uiConfig,
@@ -15,6 +17,11 @@ const FullPageSalesAgentHandler = ({
15
17
  }: BaseWidgetProps) => {
16
18
  const { headerContainer, autoHeight } = (widgetConfig as FullPageSalesAgentWidgetV3Config) || {};
17
19
 
20
+ const voiceInputEnabled = uiConfig?.lookAndFeel?.voiceInputEnabled;
21
+ const { featureFlagService } = useFeatureFlagService();
22
+ const isVoiceInputEnabled =
23
+ featureFlagService?.isFeatureGateEnabled(FeatureGates.IsVoiceInputEnabled) || voiceInputEnabled;
24
+
18
25
  return (
19
26
  <FullPageSalesAgent
20
27
  theme={Theme.STANDARD}
@@ -23,6 +30,7 @@ const FullPageSalesAgentHandler = ({
23
30
  hardcopyContent={hardcopyContent}
24
31
  headerContainer={headerContainer}
25
32
  autoHeight={autoHeight}
33
+ voiceInputEnabled={isVoiceInputEnabled}
26
34
  />
27
35
  );
28
36
  };