@envive-ai/react-hooks 0.3.0 → 0.3.2

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 (264) hide show
  1. package/dist/application/commerce-api.cjs +4 -4
  2. package/dist/application/commerce-api.js +4 -4
  3. package/dist/application/models/api/response.d.cts +1 -1
  4. package/dist/application/models/api/response.d.ts +1 -1
  5. package/dist/application/models/frontendConfig.d.cts +1 -1
  6. package/dist/application/models/frontendConfig.d.ts +1 -1
  7. package/dist/application/models/guards/api/isApiFormResponse.cjs +1 -1
  8. package/dist/application/models/guards/api/isApiFormResponse.js +1 -1
  9. package/dist/application/models/guards/api/isApiFormSubmittedResponseAttributes.cjs +1 -1
  10. package/dist/application/models/guards/api/isApiFormSubmittedResponseAttributes.js +1 -1
  11. package/dist/application/models/guards/api/isApiOrderResponseAttributes.cjs +2 -2
  12. package/dist/application/models/guards/api/isApiOrderResponseAttributes.js +2 -2
  13. package/dist/application/models/guards/api/isApiProductResponseAttributes.cjs +1 -1
  14. package/dist/application/models/guards/api/isApiProductResponseAttributes.js +1 -1
  15. package/dist/application/models/guards/api/isApiResponse.cjs +1 -1
  16. package/dist/application/models/guards/api/isApiResponse.js +1 -1
  17. package/dist/application/models/guards/api/isApiSearchEventAttributes.cjs +2 -2
  18. package/dist/application/models/guards/api/isApiSearchEventAttributes.js +2 -2
  19. package/dist/application/models/message.cjs +1 -1
  20. package/dist/application/models/message.d.cts +1 -1
  21. package/dist/application/models/message.d.ts +1 -1
  22. package/dist/application/models/message.js +1 -1
  23. package/dist/application/models/validators/validateGraphQLColorsConfig.cjs +2 -1
  24. package/dist/application/models/validators/validateGraphQLColorsConfig.js +2 -1
  25. package/dist/application/models/validators/validateGraphQLFrontendConfig.cjs +1 -1
  26. package/dist/application/models/validators/validateGraphQLFrontendConfig.d.cts +1 -1
  27. package/dist/application/models/validators/validateGraphQLFrontendConfig.d.ts +1 -1
  28. package/dist/application/models/validators/validateGraphQLFrontendConfig.js +1 -1
  29. package/dist/application/utils/analyticsUtils.cjs +2 -2
  30. package/dist/application/utils/analyticsUtils.js +2 -2
  31. package/dist/application/utils/domObserver.cjs +1 -1
  32. package/dist/application/utils/domObserver.js +1 -1
  33. package/dist/application/utils/elementObserver.cjs +1 -1
  34. package/dist/application/utils/elementObserver.d.cts +2 -2
  35. package/dist/application/utils/elementObserver.js +1 -1
  36. package/dist/application/utils/mutationHelper.cjs +2 -2
  37. package/dist/application/utils/mutationHelper.js +2 -2
  38. package/dist/application/utils/nodeSelector.cjs +5 -2
  39. package/dist/application/utils/nodeSelector.js +5 -2
  40. package/dist/application/utils/stringUtils.cjs +1 -1
  41. package/dist/application/utils/stringUtils.js +1 -1
  42. package/dist/atoms/app/index.cjs +1 -1
  43. package/dist/atoms/app/index.d.cts +1 -1
  44. package/dist/atoms/app/index.js +1 -1
  45. package/dist/atoms/app/variant.cjs +1 -1
  46. package/dist/atoms/app/variant.d.cts +3 -3
  47. package/dist/atoms/app/variant.d.ts +3 -3
  48. package/dist/atoms/app/variant.js +1 -1
  49. package/dist/atoms/chat/chatState.d.cts +15 -15
  50. package/dist/atoms/chat/chatState.d.ts +15 -15
  51. package/dist/atoms/chat/form.d.cts +2 -2
  52. package/dist/atoms/chat/form.d.ts +2 -2
  53. package/dist/atoms/chat/index.cjs +1 -1
  54. package/dist/atoms/chat/index.d.cts +3 -3
  55. package/dist/atoms/chat/index.d.ts +2 -2
  56. package/dist/atoms/chat/index.js +1 -1
  57. package/dist/atoms/chat/lastMessage.d.cts +2 -2
  58. package/dist/atoms/chat/lastMessage.d.ts +2 -2
  59. package/dist/atoms/chat/messageQueue.cjs +1 -1
  60. package/dist/atoms/chat/messageQueue.js +1 -1
  61. package/dist/atoms/chat/performanceMetrics.d.cts +6 -6
  62. package/dist/atoms/chat/performanceMetrics.d.ts +6 -6
  63. package/dist/atoms/chat/renderedWidgetRefs.d.cts +2 -2
  64. package/dist/atoms/chat/renderedWidgetRefs.d.ts +2 -2
  65. package/dist/atoms/chat/replies.cjs +2 -2
  66. package/dist/atoms/chat/replies.d.cts +2 -2
  67. package/dist/atoms/chat/replies.d.ts +2 -2
  68. package/dist/atoms/chat/replies.js +2 -2
  69. package/dist/atoms/chat/suggestions.d.cts +2 -2
  70. package/dist/atoms/chat/suggestions.d.ts +2 -2
  71. package/dist/atoms/globalSearch/globalSearch.cjs +3 -1
  72. package/dist/atoms/globalSearch/globalSearch.d.cts +10 -7
  73. package/dist/atoms/globalSearch/globalSearch.d.ts +10 -7
  74. package/dist/atoms/globalSearch/globalSearch.js +3 -2
  75. package/dist/atoms/globalSearch/index.cjs +1 -0
  76. package/dist/atoms/globalSearch/index.d.cts +2 -2
  77. package/dist/atoms/globalSearch/index.d.ts +2 -2
  78. package/dist/atoms/globalSearch/index.js +2 -2
  79. package/dist/atoms/org/customerService.d.cts +7 -7
  80. package/dist/atoms/org/graphqlConfig.cjs +1 -1
  81. package/dist/atoms/org/graphqlConfig.d.cts +6 -6
  82. package/dist/atoms/org/graphqlConfig.d.ts +5 -5
  83. package/dist/atoms/org/graphqlConfig.js +1 -1
  84. package/dist/atoms/org/newOrgConfigAtom.d.cts +2 -2
  85. package/dist/atoms/org/newOrgConfigAtom.d.ts +2 -2
  86. package/dist/atoms/org/orgAnalyticsConfig.cjs +1 -1
  87. package/dist/atoms/org/orgAnalyticsConfig.d.cts +6 -6
  88. package/dist/atoms/org/orgAnalyticsConfig.d.ts +6 -6
  89. package/dist/atoms/org/orgAnalyticsConfig.js +1 -1
  90. package/dist/atoms/search/chatSearch.d.cts +17 -17
  91. package/dist/atoms/search/chatSearch.d.ts +17 -17
  92. package/dist/atoms/search/searchAPI.cjs +1 -1
  93. package/dist/atoms/search/searchAPI.d.cts +14 -14
  94. package/dist/atoms/search/searchAPI.d.ts +14 -14
  95. package/dist/atoms/search/searchAPI.js +1 -1
  96. package/dist/atoms/search/searchServiceAdapter.cjs +1 -1
  97. package/dist/atoms/search/searchServiceAdapter.js +1 -1
  98. package/dist/atoms/search/types.d.cts +1 -1
  99. package/dist/contexts/amplitudeContext/amplitudeContext.cjs +6 -6
  100. package/dist/contexts/amplitudeContext/amplitudeContext.js +6 -6
  101. package/dist/contexts/cdnContext/cdnContext.cjs +1 -1
  102. package/dist/contexts/cdnContext/cdnContext.js +1 -1
  103. package/dist/contexts/chatContext/chatContext.cjs +4 -4
  104. package/dist/contexts/chatContext/chatContext.d.cts +2 -2
  105. package/dist/contexts/chatContext/chatContext.js +4 -4
  106. package/dist/contexts/enviveConfigContext/enviveConfigContext.cjs +2 -3
  107. package/dist/contexts/enviveConfigContext/enviveConfigContext.js +2 -3
  108. package/dist/contexts/enviveContext/enviveContext.cjs +52 -0
  109. package/dist/contexts/enviveContext/enviveContext.d.cts +31 -0
  110. package/dist/contexts/enviveContext/enviveContext.d.ts +31 -0
  111. package/dist/contexts/enviveContext/enviveContext.js +51 -0
  112. package/dist/contexts/enviveContext/index.cjs +5 -0
  113. package/dist/contexts/enviveContext/index.d.cts +3 -0
  114. package/dist/contexts/enviveContext/index.d.ts +3 -0
  115. package/dist/contexts/enviveContext/index.js +4 -0
  116. package/dist/contexts/enviveContext/types.cjs +11 -0
  117. package/dist/contexts/enviveContext/types.d.cts +8 -0
  118. package/dist/contexts/enviveContext/types.d.ts +8 -0
  119. package/dist/contexts/enviveContext/types.js +10 -0
  120. package/dist/contexts/featureFlagContext/featureFlagContext.cjs +2 -2
  121. package/dist/contexts/featureFlagContext/featureFlagContext.js +2 -2
  122. package/dist/contexts/featureFlagServiceContext/featureFlagServiceContext.cjs +2 -2
  123. package/dist/contexts/featureFlagServiceContext/featureFlagServiceContext.js +2 -2
  124. package/dist/contexts/graphqlContext/graphqlContext.cjs +3 -3
  125. package/dist/contexts/graphqlContext/graphqlContext.js +3 -3
  126. package/dist/contexts/localStorageContext/localStorageContext.cjs +1 -1
  127. package/dist/contexts/localStorageContext/localStorageContext.js +1 -1
  128. package/dist/contexts/newOrgConfigContext/newOrgConfigContext.cjs +1 -1
  129. package/dist/contexts/newOrgConfigContext/newOrgConfigContext.js +1 -1
  130. package/dist/contexts/searchContext/searchContext.cjs +1 -1
  131. package/dist/contexts/searchContext/searchContext.js +1 -1
  132. package/dist/contexts/sessionStorageContext/sessionStorageContext.cjs +1 -1
  133. package/dist/contexts/sessionStorageContext/sessionStorageContext.js +1 -1
  134. package/dist/contexts/systemSettingsContext/systemSettingsContext.d.cts +2 -2
  135. package/dist/contexts/systemSettingsContext/systemSettingsContext.d.ts +2 -2
  136. package/dist/contexts/types.cjs +1 -1
  137. package/dist/contexts/types.d.cts +1 -1
  138. package/dist/contexts/types.d.ts +1 -1
  139. package/dist/contexts/types.js +1 -1
  140. package/dist/contexts/userIdentityContext/userIdentityContext.cjs +1 -1
  141. package/dist/contexts/userIdentityContext/userIdentityContext.js +1 -1
  142. package/dist/events/index.cjs +2 -4
  143. package/dist/events/index.js +2 -4
  144. package/dist/hooks/AmplitudeOperations/useAmplitudeOperations.cjs +1 -1
  145. package/dist/hooks/AmplitudeOperations/useAmplitudeOperations.js +1 -1
  146. package/dist/hooks/Debounce/useDebounce.cjs +1 -1
  147. package/dist/hooks/Debounce/useDebounce.js +1 -1
  148. package/dist/hooks/ElementObserver/useElementObserver.cjs +2 -2
  149. package/dist/hooks/ElementObserver/useElementObserver.js +2 -2
  150. package/dist/hooks/GrabAndScroll/useGrabAndScroll.d.cts +2 -2
  151. package/dist/hooks/GrabAndScroll/useGrabAndScroll.d.ts +2 -2
  152. package/dist/hooks/IdentifyUser/useIdentifyUser.cjs +1 -1
  153. package/dist/hooks/IdentifyUser/useIdentifyUser.js +1 -1
  154. package/dist/hooks/ImageResolver/useImageResolver.cjs +2 -2
  155. package/dist/hooks/ImageResolver/useImageResolver.js +2 -2
  156. package/dist/hooks/IsSmallScreen/useIsSmallScreen.cjs +1 -1
  157. package/dist/hooks/IsSmallScreen/useIsSmallScreen.js +1 -1
  158. package/dist/hooks/LocalStorageOperations/useLocalStorageOperations.cjs +11 -3
  159. package/dist/hooks/LocalStorageOperations/useLocalStorageOperations.js +11 -3
  160. package/dist/hooks/Search/useRecommendedProducts.cjs +1 -1
  161. package/dist/hooks/Search/useRecommendedProducts.js +1 -1
  162. package/dist/hooks/Search/useSearch.cjs +6 -3
  163. package/dist/hooks/Search/useSearch.d.cts +3 -1
  164. package/dist/hooks/Search/useSearch.d.ts +3 -1
  165. package/dist/hooks/Search/useSearch.js +7 -4
  166. package/dist/hooks/Search/useSearchInput.cjs +1 -1
  167. package/dist/hooks/Search/useSearchInput.js +1 -1
  168. package/dist/hooks/SearchOperations/useSearchOperations.cjs +1 -1
  169. package/dist/hooks/SearchOperations/useSearchOperations.d.cts +1 -1
  170. package/dist/hooks/SearchOperations/useSearchOperations.d.ts +1 -1
  171. package/dist/hooks/SearchOperations/useSearchOperations.js +1 -1
  172. package/dist/hooks/SessionStorageOperations/useSessionStorageOperations.cjs +1 -1
  173. package/dist/hooks/SessionStorageOperations/useSessionStorageOperations.js +1 -1
  174. package/dist/hooks/SystemSettingsContext/useSystemSettingsContext.d.cts +2 -2
  175. package/dist/hooks/SystemSettingsContext/useSystemSettingsContext.d.ts +2 -2
  176. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.cjs +1 -1
  177. package/dist/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.js +1 -1
  178. package/dist/hooks/utils.cjs +3 -3
  179. package/dist/hooks/utils.d.cts +1 -1
  180. package/dist/hooks/utils.d.ts +1 -1
  181. package/dist/hooks/utils.js +3 -3
  182. package/package.json +25 -6
  183. package/src/application/commerce-api.ts +12 -10
  184. package/src/application/models/api/nextMessageRequest.ts +2 -0
  185. package/src/application/models/api/response.ts +4 -4
  186. package/src/application/models/api/search.ts +1 -2
  187. package/src/application/models/api/userEvent.ts +1 -2
  188. package/src/application/models/frontendConfig.ts +2 -2
  189. package/src/application/models/guards/api/isApiOrderResponseAttributes.ts +8 -4
  190. package/src/application/models/guards/api/isApiSearchEventAttributes.ts +1 -1
  191. package/src/application/models/index.ts +1 -0
  192. package/src/application/models/message.ts +5 -5
  193. package/src/application/models/utilityTypes/delimiterCase.ts +1 -0
  194. package/src/application/models/validators/validateGraphQLColorsConfig.ts +2 -1
  195. package/src/application/models/validators/validateGraphQLFrontendConfig.ts +1 -1
  196. package/src/application/models/variantInfo/variantInfo.ts +3 -0
  197. package/src/application/utils/analyticsUtils.ts +3 -3
  198. package/src/application/utils/domObserver.ts +1 -0
  199. package/src/application/utils/elementObserver.ts +1 -0
  200. package/src/application/utils/mutationHelper.ts +3 -1
  201. package/src/application/utils/nodeSelector.ts +7 -1
  202. package/src/application/utils/stringUtils.ts +1 -0
  203. package/src/atoms/app/index.ts +17 -6
  204. package/src/atoms/app/variant.ts +11 -13
  205. package/src/atoms/chat/__tests__/messageQueue.test.spec.ts +61 -0
  206. package/src/atoms/chat/index.ts +8 -8
  207. package/src/atoms/chat/messageQueue.ts +2 -48
  208. package/src/atoms/chat/replies.ts +3 -3
  209. package/src/atoms/org/graphqlConfig.ts +0 -1
  210. package/src/atoms/org/orgAnalyticsConfig.ts +1 -1
  211. package/src/atoms/search/searchAPI.ts +1 -1
  212. package/src/atoms/search/searchServiceAdapter.ts +1 -1
  213. package/src/contexts/amplitudeContext/__tests__/amplitudeContext.test.tsx +4 -3
  214. package/src/contexts/amplitudeContext/amplitudeContext.tsx +5 -5
  215. package/src/contexts/cdnContext/cdnContext.tsx +1 -1
  216. package/src/contexts/enviveConfigContext/__tests__/enviveConfigContext.test.tsx +2 -3
  217. package/src/contexts/enviveConfigContext/enviveConfigContext.tsx +3 -3
  218. package/src/contexts/enviveContext/enviveContext.tsx +89 -0
  219. package/src/contexts/enviveContext/index.ts +2 -0
  220. package/src/contexts/enviveContext/types.ts +4 -0
  221. package/src/contexts/featureFlagContext/featureFlagContext.tsx +1 -1
  222. package/src/contexts/featureFlagServiceContext/featureFlagServiceContext.tsx +1 -1
  223. package/src/contexts/graphqlContext/__tests__/graphqlContext.test.tsx +7 -5
  224. package/src/contexts/graphqlContext/graphqlContext.tsx +1 -5
  225. package/src/contexts/hardcopyContext/hardcopyContext.tsx +70 -0
  226. package/src/contexts/hardcopyContext/index.ts +1 -0
  227. package/src/contexts/localStorageContext/__tests__/localStorageContext.test.tsx +9 -3
  228. package/src/contexts/localStorageContext/localStorageContext.tsx +1 -1
  229. package/src/contexts/newOrgConfigContext/__tests__/newOrgConfigContext.test.tsx +2 -3
  230. package/src/contexts/newOrgConfigContext/newOrgConfigContext.tsx +1 -1
  231. package/src/contexts/pageContext/__tests__/pageContext.test.tsx +666 -0
  232. package/src/contexts/pageContext/index.ts +2 -0
  233. package/src/contexts/pageContext/mapping.ts +38 -0
  234. package/src/contexts/pageContext/pageContext.tsx +78 -0
  235. package/src/contexts/pageContext/types.ts +27 -0
  236. package/src/contexts/salesAgentContext/chatAPI.ts +75 -0
  237. package/src/contexts/salesAgentContext/index.ts +3 -0
  238. package/src/contexts/salesAgentContext/salesAgentContext.tsx +128 -0
  239. package/src/contexts/salesAgentContext/salesAgentService.ts +197 -0
  240. package/src/contexts/searchContext/__tests__/searchContext.test.tsx +14 -15
  241. package/src/contexts/searchContext/searchContext.tsx +1 -1
  242. package/src/contexts/sessionStorageContext/sessionStorageContext.tsx +1 -1
  243. package/src/contexts/systemSettingsContext/__tests__/systemSettingsContext.test.tsx +7 -6
  244. package/src/contexts/types.ts +20 -22
  245. package/src/contexts/userIdentityContext/__tests__/userIdentityContext.test.tsx +3 -8
  246. package/src/contexts/userIdentityContext/userIdentityContext.tsx +1 -1
  247. package/src/events/index.ts +5 -7
  248. package/src/hooks/AmplitudeOperations/useAmplitudeOperations.ts +1 -1
  249. package/src/hooks/Debounce/useDebounce.ts +1 -1
  250. package/src/hooks/ElementObserver/useElementObserver.ts +2 -1
  251. package/src/hooks/GraphQLConfig/useGraphQLConfig.ts +4 -2
  252. package/src/hooks/IdentifyUser/useIdentifyUser.ts +1 -1
  253. package/src/hooks/ImageResolver/useImageResolver.ts +2 -0
  254. package/src/hooks/IsSmallScreen/useIsSmallScreen.ts +1 -1
  255. package/src/hooks/LocalStorageOperations/useLocalStorageOperations.ts +20 -4
  256. package/src/hooks/Search/__tests__/useSearch.test.tsx +15 -10
  257. package/src/hooks/Search/useRecommendedProducts.ts +2 -2
  258. package/src/hooks/Search/useSearchInput.ts +1 -1
  259. package/src/hooks/SearchOperations/useSearchOperations.ts +2 -2
  260. package/src/hooks/SessionStorageOperations/useSessionStorageOperations.ts +1 -1
  261. package/src/hooks/TrackComponentVisibleEvent/useTrackComponentVisibleEvent.ts +2 -2
  262. package/src/hooks/utils.ts +3 -2
  263. package/src/contexts/chatContext/chatContext.tsx +0 -463
  264. package/src/contexts/chatContext/index.ts +0 -1
@@ -0,0 +1,78 @@
1
+ import {
2
+ ReactNode,
3
+ createContext,
4
+ useCallback,
5
+ useContext,
6
+ useEffect,
7
+ useMemo,
8
+ useState,
9
+ } from 'react';
10
+ import { UserEvent } from '@spiffy-ai/commerce-api-client';
11
+ import { UrlResolverResponse, urlResolverAtom } from 'src/atoms/app/variant';
12
+ import { useAtom } from 'jotai';
13
+
14
+ import CommerceApiClient from 'src/application/commerce-api';
15
+ import Logger from 'src/application/logging/logger';
16
+ import { mapUrlResolverResponseToVariantInfo } from './mapping';
17
+ import { PageDetails, PageVariantInfo } from './types';
18
+
19
+ const PageContext = createContext<PageDetails | undefined>(undefined);
20
+
21
+ export const PageProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
22
+ const [pageUrl, setPageUrl] = useState<string | undefined>(undefined);
23
+ const [variantInfo, setVariantInfo] = useState<PageVariantInfo | undefined>(undefined);
24
+ const [urlResolverResponse, setUrlResolverResponse] = useAtom(urlResolverAtom);
25
+ const [userEvent, setUserEvent] = useState<UserEvent | undefined>(undefined);
26
+
27
+ useEffect(() => {
28
+ // TODO: Add support for SPA's and other changes to the URL to ensure that the pageUrl is set correctly
29
+ setPageUrl(window.location.href);
30
+ }, []);
31
+
32
+ const setVariantFromUrlResolver = useCallback(
33
+ (url: string, response: UrlResolverResponse) => {
34
+ const newVariantInfo = mapUrlResolverResponseToVariantInfo(url, response);
35
+ const newUserEvent = response.user_event ?? undefined;
36
+ setVariantInfo(newVariantInfo);
37
+ setUserEvent(newUserEvent);
38
+ },
39
+ [setVariantInfo],
40
+ );
41
+
42
+ useEffect(() => {
43
+ const resolvePageUrl = async () => {
44
+ try {
45
+ if (!pageUrl) {
46
+ return;
47
+ }
48
+ const cleansedUrl = pageUrl.toLowerCase().trim();
49
+ const response =
50
+ urlResolverResponse[cleansedUrl] ??
51
+ ((await CommerceApiClient.resolveUrl(cleansedUrl)) as UrlResolverResponse);
52
+ setVariantFromUrlResolver(cleansedUrl, response);
53
+ } catch (e) {
54
+ Logger.logError('Failed to resolve page URL', e, { pageUrl });
55
+ }
56
+ };
57
+ resolvePageUrl();
58
+ }, [pageUrl, urlResolverResponse, setUrlResolverResponse, setVariantFromUrlResolver]);
59
+
60
+ const value = useMemo(
61
+ () => ({
62
+ pageUrl,
63
+ variantInfo,
64
+ userEvent,
65
+ }),
66
+ [pageUrl, userEvent, variantInfo],
67
+ );
68
+
69
+ return <PageContext.Provider value={value}>{children}</PageContext.Provider>;
70
+ };
71
+
72
+ export const usePage = () => {
73
+ const context = useContext(PageContext);
74
+ if (!context) {
75
+ throw new Error('usePage must be used within a PageProvider');
76
+ }
77
+ return context;
78
+ };
@@ -0,0 +1,27 @@
1
+ import { UserEvent } from '@spiffy-ai/commerce-api-client';
2
+ import { VariantTypeEnum } from 'src/application/models';
3
+
4
+ export type PDPPageVariantInfo = {
5
+ variantType: VariantTypeEnum.Pdp;
6
+ productId: string;
7
+ url: string;
8
+ };
9
+
10
+ export type PLPPageVariantInfo = {
11
+ variantType: VariantTypeEnum.Plp;
12
+ plpId: string;
13
+ url: string;
14
+ };
15
+
16
+ export type VisitPageVariantInfo = {
17
+ variantType: VariantTypeEnum.PageVisit;
18
+ url: string;
19
+ };
20
+
21
+ export type PageVariantInfo = PDPPageVariantInfo | PLPPageVariantInfo | VisitPageVariantInfo;
22
+
23
+ export interface PageDetails {
24
+ pageUrl: string | undefined;
25
+ variantInfo: PageVariantInfo | undefined;
26
+ userEvent: UserEvent | undefined;
27
+ }
@@ -0,0 +1,75 @@
1
+ // This component will interact with the backend API to get the responses from the sales agent.
2
+
3
+ import { PageVisitCategory, UserEventCategory } from '@spiffy-ai/commerce-api-client';
4
+ import { Suggestion, UserEvent } from 'src/application/models';
5
+ import { useSetAtom } from 'jotai';
6
+ import { useCallback } from 'react';
7
+ import { v4 as uuid } from 'uuid';
8
+ import { queueUserEventAtom } from 'src/atoms/chat/messageQueue';
9
+
10
+ export interface SalesAgentChatAPI {
11
+ logPageVisit: ({ pageVisitCategory }: { pageVisitCategory: PageVisitCategory }) => void;
12
+ onSuggestionClicked: (suggestion: Suggestion) => void;
13
+ onTypedMessageSubmitted: ({ query }: { query: string }) => void;
14
+ onFormResponseSubmitted: (formResponse: any) => void; // TODO: Figure out the right type
15
+ }
16
+
17
+ export const useSalesAgentChatAPI = () => {
18
+ // TODO: Each of these functions will trigger both the necessary amplitude events and initiate the
19
+ // necessary actions to trigger the backend API
20
+ const queueUserEvent = useSetAtom(queueUserEventAtom);
21
+
22
+ const logPageVisit = useCallback(
23
+ ({ pageVisitCategory }: { pageVisitCategory: PageVisitCategory }) => {
24
+ const event: UserEvent = {
25
+ eventId: uuid(),
26
+ category: UserEventCategory.PageVisit,
27
+ createdAt: new Date().toISOString(),
28
+ attributes: {
29
+ url: window.location.href,
30
+ pageVisitCategory,
31
+ },
32
+ };
33
+ queueUserEvent(event);
34
+ },
35
+ [queueUserEvent],
36
+ );
37
+ const onSuggestionClicked = useCallback(
38
+ (suggestion: Suggestion) => {
39
+ const event: UserEvent = {
40
+ eventId: uuid(),
41
+ category: UserEventCategory.SuggestionClicked,
42
+ createdAt: new Date().toISOString(),
43
+ attributes: {
44
+ suggestionId: suggestion.id,
45
+ },
46
+ };
47
+ queueUserEvent(event);
48
+ },
49
+ [queueUserEvent],
50
+ );
51
+ const onTypedMessageSubmitted = useCallback(
52
+ ({ query }: { query: string }) => {
53
+ const event: UserEvent = {
54
+ eventId: uuid(),
55
+ category: UserEventCategory.QueryTyped,
56
+ createdAt: new Date().toISOString(),
57
+ attributes: {
58
+ query,
59
+ },
60
+ };
61
+ queueUserEvent(event);
62
+ },
63
+ [queueUserEvent],
64
+ );
65
+ const onFormResponseSubmitted = useCallback(() => {
66
+ // TODO: Implement the form response submitted
67
+ }, []);
68
+
69
+ return {
70
+ logPageVisit,
71
+ onSuggestionClicked,
72
+ onTypedMessageSubmitted,
73
+ onFormResponseSubmitted,
74
+ };
75
+ };
@@ -0,0 +1,3 @@
1
+ export * from './salesAgentContext';
2
+ export * from './chatAPI';
3
+ export { PageVisitCategory, SuggestionCategory } from '@spiffy-ai/commerce-api-client';
@@ -0,0 +1,128 @@
1
+ import { useAtomValue, useSetAtom } from 'jotai';
2
+ import { ReactNode, createContext, useCallback, useEffect, useMemo } from 'react';
3
+ import { Message } from 'src/application/models/message';
4
+ import { Suggestion, UserEvent } from 'src/application/models';
5
+ import { messagesAtom, suggestionsAtom } from 'src/atoms/chat/chatState';
6
+ import {
7
+ processUserEventAtom,
8
+ userEventQueueAtom,
9
+ userQueueEventCountAtom,
10
+ } from 'src/atoms/chat/messageQueue';
11
+ import { useSystemSettingsContext } from 'src/hooks/SystemSettingsContext';
12
+ import { SalesAgentChatAPI, useSalesAgentChatAPI } from './chatAPI';
13
+ import { useSalesAgentService } from './salesAgentService';
14
+
15
+ interface SalesAgent extends SalesAgentChatAPI {
16
+ // Messages are the full list of all turns
17
+ messages: Message[][];
18
+ // Pending messages are messages that the user has clicked/typed but have not yet been sent to the backend.
19
+ pendingMessages: UserEvent[];
20
+ // Suggestions are the list of suggestions that the user can click on in response to the most recent message from the backend
21
+ suggestions: Suggestion[];
22
+ // True while the response is streaming from the backend
23
+ isResponseStreaming: boolean;
24
+ // True while the response is pending from the backend
25
+ isPendingResponse: boolean;
26
+ // True when the sales agent is initialized
27
+ isInitialized: boolean;
28
+ }
29
+
30
+ const SalesAgentContext = createContext<SalesAgent | undefined>(undefined);
31
+
32
+ export const SalesAgentProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
33
+ const userEvents = useAtomValue(userEventQueueAtom);
34
+ const userQueueEventCount = useAtomValue(userQueueEventCountAtom);
35
+ const markUserEventsProcessed = useSetAtom(processUserEventAtom);
36
+ const settingsContext = useSystemSettingsContext();
37
+
38
+ const { getStreamingResponses, createResponsePayload } = useSalesAgentService();
39
+
40
+ // TODO: Ensure that amplitude events are being emitted
41
+
42
+ // TODO: Add support for the streaming responses to function
43
+
44
+ // TODO: Add support for the hydration of the messages from the backend on initialization
45
+ const sendMessagesToBackend = useCallback(async () => {
46
+ // Fast exit if there are no pending messages
47
+ if (userEvents.length === 0) {
48
+ return;
49
+ }
50
+ // TODO: Support sending multiple messages to the backend in one request
51
+ const [pendingMessage] = userEvents.slice(0, userQueueEventCount);
52
+ try {
53
+ const requestPayload = createResponsePayload({
54
+ userEvents: [pendingMessage],
55
+ generationParams: settingsContext.generationParams,
56
+ });
57
+ const response = await getStreamingResponses(requestPayload);
58
+
59
+ console.log('response', response);
60
+ // Remove the pending message from the user events
61
+ markUserEventsProcessed([pendingMessage.eventId]);
62
+ } catch (error) {
63
+ console.error('error sending message');
64
+ }
65
+ }, [
66
+ userEvents,
67
+ userQueueEventCount,
68
+ createResponsePayload,
69
+ settingsContext.generationParams,
70
+ getStreamingResponses,
71
+ markUserEventsProcessed,
72
+ ]);
73
+
74
+ console.log('userQueueEventCount', userQueueEventCount);
75
+ console.log('userEvents', userEvents);
76
+
77
+ // This is the primary event loop for communicating with the backend API
78
+ // It will be triggered when there are pending messages to be sent to the backend
79
+ // It will be responsible for sending the messages to the backend and receiving the responses
80
+ useEffect(() => {
81
+ if (userQueueEventCount > 0) {
82
+ sendMessagesToBackend();
83
+ }
84
+ // TODO: Add a suggestion retrieval here (don't get suggestions unless the event queue is empty)
85
+ console.log('SalesAgentProvider useEffect');
86
+ }, [sendMessagesToBackend, userQueueEventCount]);
87
+
88
+ return <SalesAgentContext.Provider value={undefined}>{children}</SalesAgentContext.Provider>;
89
+ };
90
+
91
+ export const useSalesAgent = (): SalesAgent => {
92
+ const messages = useAtomValue(messagesAtom);
93
+ const suggestions = useAtomValue(suggestionsAtom);
94
+ const pendingMessages = useAtomValue(userEventQueueAtom);
95
+ const isResponseStreaming = false; // useAtomValue(isResponseStreamingAtom);
96
+ const isPendingResponse = false; // useAtomValue(isPendingResponseAtom);
97
+ const isInitialized = false; // useAtomValue(isInitializedAtom);
98
+
99
+ const { logPageVisit, onSuggestionClicked, onTypedMessageSubmitted, onFormResponseSubmitted } =
100
+ useSalesAgentChatAPI();
101
+
102
+ const salesAgent: SalesAgent = useMemo(() => {
103
+ return {
104
+ messages,
105
+ suggestions,
106
+ pendingMessages,
107
+ isResponseStreaming,
108
+ isPendingResponse,
109
+ isInitialized,
110
+ logPageVisit,
111
+ onSuggestionClicked,
112
+ onTypedMessageSubmitted,
113
+ onFormResponseSubmitted,
114
+ };
115
+ }, [
116
+ messages,
117
+ suggestions,
118
+ pendingMessages,
119
+ isResponseStreaming,
120
+ isPendingResponse,
121
+ isInitialized,
122
+ logPageVisit,
123
+ onSuggestionClicked,
124
+ onTypedMessageSubmitted,
125
+ onFormResponseSubmitted,
126
+ ]);
127
+ return salesAgent;
128
+ };
@@ -0,0 +1,197 @@
1
+ import { useAtomValue, useSetAtom } from 'jotai';
2
+ import { useCallback } from 'react';
3
+ import { v4 as uuid } from 'uuid';
4
+ import CommerceApiClient from 'src/application/commerce-api';
5
+ import { appDetailsAtom } from 'src/atoms/app';
6
+ import Logger from 'src/application/logging/logger';
7
+ import {
8
+ GenerationParams,
9
+ Message,
10
+ MessageType,
11
+ NextMessageRequest,
12
+ Response,
13
+ UserEvent,
14
+ } from 'src/application/models';
15
+ import { messageFromResponse } from 'src/application/utils';
16
+ import { messagesAtom, requestFailureAtom } from 'src/atoms/chat/chatState';
17
+ import { useMessageInterceptor } from 'src/interceptors/useMessageInterceptor';
18
+ import { useFeatureFlagService } from '../featureFlagServiceContext';
19
+
20
+ interface SalesAgentService {
21
+ createResponsePayload: (payload: {
22
+ userEvents: UserEvent[];
23
+ generationParams?: GenerationParams;
24
+ }) => NextMessageRequest;
25
+ getStreamingResponses: (payload: NextMessageRequest) => Promise<void>;
26
+ }
27
+
28
+ export const getQueryParam = (key: string): string | null => {
29
+ const urlObj = new URL(window.location.href);
30
+ return urlObj.searchParams.get(key);
31
+ };
32
+
33
+ const updateMessageState = (
34
+ message: Message,
35
+ lastMessage: Message,
36
+ setMessages: (updater: (prev: Message[][]) => Message[][]) => void,
37
+ ): Message => {
38
+ if (lastMessage == null) {
39
+ setMessages(prev => [...prev, [message]]);
40
+ return message;
41
+ }
42
+ if (lastMessage.type === MessageType.Text && message.type === MessageType.Text) {
43
+ const newMessage = {
44
+ ...lastMessage,
45
+ metadata: {
46
+ ...lastMessage.metadata,
47
+ content: lastMessage.metadata.content + message.metadata.content,
48
+ },
49
+ };
50
+ setMessages(prev => {
51
+ const lastTurn = prev[prev.length - 1];
52
+ return [
53
+ ...prev.slice(0, prev.length - 1),
54
+ [...lastTurn.slice(0, lastTurn.length - 1), newMessage],
55
+ ];
56
+ });
57
+ return newMessage;
58
+ }
59
+ setMessages(prev => [...prev.slice(0, prev.length - 1), [...prev[prev.length - 1], message]]);
60
+ return message;
61
+ };
62
+
63
+ const processStreamingResponse = async (
64
+ stream: AsyncIterable<Response>,
65
+ messageInterceptor: { intercept: (response?: Response) => boolean | undefined },
66
+ setMessages: (updater: (prev: Message[][]) => Message[][]) => void,
67
+ ): Promise<void> => {
68
+ let lastMessage: Message | undefined;
69
+
70
+ for await (const response of stream) {
71
+ try {
72
+ if (messageInterceptor.intercept(response)) {
73
+ return;
74
+ }
75
+
76
+ if (!response) {
77
+ throw new Error('No response from stream');
78
+ }
79
+ const message = messageFromResponse(response);
80
+ if (!message) {
81
+ throw new Error('Failed to transform API response to client message');
82
+ }
83
+
84
+ // No support for ChatSearch messages at the current time.
85
+ // Perhaps we add support back in the future
86
+ // if (message.type === MessageType.ProductSearch) {
87
+ // handleSearchResults(message);
88
+ // hasSearchResults = true;
89
+ // setSearchIsLoading(false); // Update search loading immediately when results are detected
90
+ // }
91
+
92
+ lastMessage = updateMessageState(message, lastMessage!, setMessages);
93
+ } catch (error: unknown) {
94
+ Logger.logWarn(`[spiffy-ai] Failed to generate responses from stream`, error, {
95
+ lastResponse: lastMessage,
96
+ response,
97
+ });
98
+ }
99
+ }
100
+ };
101
+
102
+ export const useSalesAgentService: () => SalesAgentService = () => {
103
+ const setRequestFailure = useSetAtom(requestFailureAtom);
104
+ const setMessages = useSetAtom(messagesAtom);
105
+ const messageInterceptor = useMessageInterceptor();
106
+
107
+ const featureFlagService = useFeatureFlagService();
108
+ const context = useAtomValue(appDetailsAtom);
109
+
110
+ const createResponsePayload = useCallback(
111
+ ({
112
+ userEvents,
113
+ generationParams,
114
+ }: {
115
+ userEvents: UserEvent[];
116
+ generationParams?: GenerationParams;
117
+ }): NextMessageRequest => {
118
+ const featureFlags = featureFlagService?.featureFlagService?.getFeatureFlags() || {};
119
+
120
+ const overrideConfigVersion =
121
+ getQueryParam('spiffy_config_version') ||
122
+ getQueryParam('envive_config_version') ||
123
+ undefined;
124
+ const overrideModelDatetime = getQueryParam('override_model_datetime') || undefined;
125
+ return {
126
+ id: uuid(),
127
+ context,
128
+ userEvents,
129
+ featureFlags,
130
+ generationParams,
131
+ overrideConfigVersion,
132
+ overrideModelDatetime,
133
+ };
134
+ },
135
+ [context, featureFlagService?.featureFlagService],
136
+ );
137
+
138
+ const getStreamingResponses = useCallback(
139
+ async (payload: NextMessageRequest): Promise<void> => {
140
+ // logPerfMetric(PerfMetricsEvents.FirstResponseStarted);
141
+ // const startTime = Date.now();
142
+ console.log('getStreamingResponses payload', JSON.stringify(payload, null, 2));
143
+ const stream = CommerceApiClient.getNextResponseStreaming(payload);
144
+
145
+ try {
146
+ setRequestFailure(false);
147
+
148
+ await processStreamingResponse(stream, messageInterceptor, setMessages);
149
+
150
+ // TODO: Add support for the Chrome Extension communication
151
+ // Log successful next_responses call
152
+ // const responseTime = Date.now() - startTime;
153
+ // await logBundleEvent({
154
+ // level: 'info',
155
+ // event: 'NEXT_RESPONSE_SUCCESS',
156
+ // context: {
157
+ // type: 'next_responses',
158
+ // endpoint: '/v1/next_responses',
159
+ // statusCode: 200,
160
+ // responseTime,
161
+ // },
162
+ // });
163
+ } catch (e) {
164
+ console.error('error getting streaming responses', e);
165
+ // Log failed next_responses call
166
+ // const responseTime = Date.now() - startTime;
167
+ // await logBundleEvent({
168
+ // level: 'error',
169
+ // event: 'NEXT_RESPONSE_FAILED',
170
+ // message: `Failed to get streaming response: ${e}`,
171
+ // context: {
172
+ // type: 'next_responses',
173
+ // endpoint: '/v1/next_responses',
174
+ // responseTime,
175
+ // error: e instanceof Error ? e.message : String(e),
176
+ // },
177
+ // });
178
+
179
+ // handleStreamingError(e, setRequestFailure, setMessages);
180
+ throw e;
181
+ // } finally {
182
+ // logPerfMetric(PerfMetricsEvents.FirstResponseCompleted);
183
+ }
184
+ },
185
+ [
186
+ // logPerfMetric,
187
+ setRequestFailure,
188
+ messageInterceptor,
189
+ setMessages,
190
+ ],
191
+ );
192
+
193
+ return {
194
+ createResponsePayload,
195
+ getStreamingResponses,
196
+ };
197
+ };
@@ -1,23 +1,24 @@
1
1
  import React from 'react';
2
- import { render, screen, waitFor, act } from '@testing-library/react';
3
- import { Provider } from 'jotai';
4
- import { useAtomValue } from 'jotai';
5
- import { SearchProvider, useSearchService } from '../searchContext';
6
- import { EnviveConfigProvider } from 'src/contexts/enviveConfigContext/enviveConfigContext';
2
+ import { act, render, screen, waitFor } from '@testing-library/react';
3
+ import { Provider, useAtomValue } from 'jotai';
4
+ import {
5
+ EnviveConfigProvider,
6
+ useEnviveConfig,
7
+ } from 'src/contexts/enviveConfigContext/enviveConfigContext';
7
8
  import { LocalStorageProvider } from 'src/contexts/localStorageContext/localStorageContext';
8
9
  import { GraphQLProvider } from 'src/contexts/graphqlContext/graphqlContext';
9
10
  import { UserIdentityProvider } from 'src/contexts/userIdentityContext/userIdentityContext';
10
11
  import { FeatureFlagServiceProvider } from 'src/contexts/featureFlagServiceContext/featureFlagServiceContext';
11
12
  import { baseUrlAtom } from 'src/atoms/envive/enviveConfig';
12
13
  import {
13
- setSearchServiceFunction,
14
14
  clearSearchServiceFunction,
15
- getSearchServiceFunction,
15
+ setSearchServiceFunction,
16
16
  } from 'src/atoms/search/searchServiceAdapter';
17
- import { SearchParams, SearchResult } from 'src/application/models/api/search';
18
- import { SessionRestartRequired } from 'src/types/exceptions/sessionExceptions';
19
- import { UnsupportedProductException } from 'src/types/exceptions/unsupportedProductExceptions';
20
- import { ResponseCategory } from '@spiffy-ai/commerce-api-client';
17
+ import { SearchResult } from 'src/application/models/api/search';
18
+
19
+ import { useAppDetails } from 'src/hooks/AppDetails/useAppDetails';
20
+ import * as commerceApiClient from '@spiffy-ai/commerce-api-client';
21
+ import { SearchProvider, useSearchService } from '../searchContext';
21
22
 
22
23
  // Mock the commerce-api-client
23
24
  const mockV1SearchQueryGet = vi.fn();
@@ -41,6 +42,7 @@ vi.mock('@spiffy-ai/commerce-api-client', () => {
41
42
  },
42
43
  ResponseError: class ResponseError extends Error {
43
44
  response: any;
45
+
44
46
  constructor(message: string, response?: any) {
45
47
  super(message);
46
48
  this.response = response;
@@ -85,10 +87,6 @@ vi.mock('src/atoms/search/searchServiceAdapter', () => ({
85
87
  getSearchServiceFunction: vi.fn(),
86
88
  }));
87
89
 
88
- import { useAppDetails } from 'src/hooks/AppDetails/useAppDetails';
89
- import { useEnviveConfig } from 'src/contexts/enviveConfigContext/enviveConfigContext';
90
- import * as commerceApiClient from '@spiffy-ai/commerce-api-client';
91
-
92
90
  // Get mocked functions
93
91
  const getMocks = () => {
94
92
  return (commerceApiClient as any).__getMocks();
@@ -129,6 +127,7 @@ const MockSearchComponent: React.FC = () => {
129
127
  <button
130
128
  data-testid="search-btn"
131
129
  onClick={handleSearch}
130
+ type="button"
132
131
  >
133
132
  Search
134
133
  </button>
@@ -1,4 +1,4 @@
1
- import React, { createContext, useContext, useCallback, useMemo, useEffect } from 'react';
1
+ import React, { createContext, useCallback, useContext, useEffect, useMemo } from 'react';
2
2
  import { useAtomValue } from 'jotai';
3
3
  import {
4
4
  Configuration,
@@ -1,4 +1,4 @@
1
- import React, { createContext, useContext, useCallback, useMemo, useEffect } from 'react';
1
+ import React, { createContext, useCallback, useContext, useEffect, useMemo } from 'react';
2
2
  import Logger from '../../application/logging/logger';
3
3
 
4
4
  interface SessionStorageContextType {
@@ -1,12 +1,11 @@
1
1
  import React from 'react';
2
- import { render, screen, waitFor, act } from '@testing-library/react';
3
- import { Provider } from 'jotai';
4
- import { useAtomValue } from 'jotai';
5
- import { SystemSettingsContextProvider, defaultGenerationParams } from '../systemSettingsContext';
2
+ import { act, render, screen, waitFor } from '@testing-library/react';
3
+ import { Provider, useAtomValue } from 'jotai';
6
4
  import { useSystemSettingsContext } from 'src/hooks/SystemSettingsContext/useSystemSettingsContext';
7
5
  import { EnviveConfigProvider } from 'src/contexts/enviveConfigContext/enviveConfigContext';
8
6
  import { baseUrlAtom } from 'src/atoms/envive/enviveConfig';
9
7
  import { GenerationParams } from 'src/application/models';
8
+ import { SystemSettingsContextProvider, defaultGenerationParams } from '../systemSettingsContext';
10
9
 
11
10
  // Component that uses the useSystemSettingsContext hook
12
11
  const MockSystemSettingsComponent: React.FC = () => {
@@ -38,6 +37,7 @@ const MockSystemSettingsComponent: React.FC = () => {
38
37
  <button
39
38
  data-testid="update-params-btn"
40
39
  onClick={handleUpdate}
40
+ type="button"
41
41
  >
42
42
  Update Params
43
43
  </button>
@@ -212,7 +212,7 @@ describe('SystemSettingsContextProvider', () => {
212
212
  describe('showDebugBar Prop', () => {
213
213
  it('should pass showDebugBar prop to context', async () => {
214
214
  render(
215
- <TestWrapper showDebugBar={true}>
215
+ <TestWrapper showDebugBar>
216
216
  <MockSystemSettingsComponent />
217
217
  </TestWrapper>,
218
218
  );
@@ -346,6 +346,7 @@ describe('SystemSettingsContextProvider', () => {
346
346
  <button
347
347
  data-testid="partial-update-btn"
348
348
  onClick={handlePartialUpdate}
349
+ type="button"
349
350
  >
350
351
  Partial Update
351
352
  </button>
@@ -449,7 +450,7 @@ describe('SystemSettingsContextProvider', () => {
449
450
  const firstShowDebugBar = contextValue1.showDebugBar;
450
451
 
451
452
  rerender(
452
- <TestWrapper showDebugBar={true}>
453
+ <TestWrapper showDebugBar>
453
454
  <Component1 />
454
455
  <Component2 />
455
456
  </TestWrapper>,