@envive-ai/react-hooks 0.3.5 → 0.3.7

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 (132) hide show
  1. package/dist/application/models/api/userEvent.d.cts +4 -2
  2. package/dist/application/models/api/userEvent.d.ts +4 -2
  3. package/dist/application/models/graphql/queries/getColorsAndFrontendQuery.cjs +18 -0
  4. package/dist/application/models/graphql/queries/getColorsAndFrontendQuery.js +18 -0
  5. package/dist/application/models/guards/api/isApiQueryTypedEventAttributes.cjs +2 -2
  6. package/dist/application/models/guards/api/isApiQueryTypedEventAttributes.js +2 -2
  7. package/dist/atoms/app/index.d.ts +1 -1
  8. package/dist/atoms/app/variant.d.cts +6 -6
  9. package/dist/atoms/app/variant.d.ts +6 -6
  10. package/dist/atoms/chat/chatState.d.cts +17 -17
  11. package/dist/atoms/chat/chatState.d.ts +18 -18
  12. package/dist/atoms/chat/form.d.cts +2 -2
  13. package/dist/atoms/chat/form.d.ts +2 -2
  14. package/dist/atoms/chat/index.cjs +1 -3
  15. package/dist/atoms/chat/index.d.cts +4 -5
  16. package/dist/atoms/chat/index.d.ts +4 -5
  17. package/dist/atoms/chat/index.js +2 -3
  18. package/dist/atoms/chat/lastMessage.d.cts +2 -2
  19. package/dist/atoms/chat/lastMessage.d.ts +2 -2
  20. package/dist/atoms/chat/performanceMetrics.d.cts +6 -6
  21. package/dist/atoms/chat/performanceMetrics.d.ts +6 -6
  22. package/dist/atoms/chat/renderedWidgetRefs.d.cts +2 -2
  23. package/dist/atoms/chat/renderedWidgetRefs.d.ts +2 -2
  24. package/dist/atoms/chat/suggestions.d.cts +2 -2
  25. package/dist/atoms/chat/suggestions.d.ts +2 -2
  26. package/dist/atoms/globalSearch/globalSearch.d.cts +5 -5
  27. package/dist/atoms/globalSearch/globalSearch.d.ts +5 -5
  28. package/dist/atoms/org/customerService.d.cts +6 -6
  29. package/dist/atoms/org/customerService.d.ts +6 -6
  30. package/dist/atoms/org/graphqlConfig.d.cts +4 -4
  31. package/dist/atoms/org/graphqlConfig.d.ts +4 -4
  32. package/dist/atoms/org/newOrgConfigAtom.d.cts +2 -2
  33. package/dist/atoms/org/newOrgConfigAtom.d.ts +2 -2
  34. package/dist/atoms/org/orgAnalyticsConfig.d.cts +5 -5
  35. package/dist/atoms/org/orgAnalyticsConfig.d.ts +5 -5
  36. package/dist/atoms/search/chatSearch.d.ts +17 -17
  37. package/dist/atoms/search/searchAPI.d.ts +13 -13
  38. package/dist/atoms/search/types.d.ts +1 -1
  39. package/dist/atoms/search/utils.d.ts +1 -1
  40. package/dist/atoms/widget/chatPreviewLoading.d.cts +2 -2
  41. package/dist/atoms/widget/chatPreviewLoading.d.ts +2 -2
  42. package/dist/contexts/enviveContext/enviveContext.cjs +20 -17
  43. package/dist/contexts/enviveContext/enviveContext.d.cts +3 -1
  44. package/dist/contexts/enviveContext/enviveContext.d.ts +3 -1
  45. package/dist/contexts/enviveContext/enviveContext.js +20 -17
  46. package/dist/contexts/enviveContext/types.d.ts +1 -1
  47. package/dist/contexts/graphqlContext/graphqlContext.cjs +40 -26
  48. package/dist/contexts/graphqlContext/graphqlContext.js +41 -26
  49. package/dist/contexts/graphqlContext/mockV3Config.cjs +47 -45
  50. package/dist/contexts/graphqlContext/mockV3Config.js +5 -3
  51. package/dist/contexts/newOrgConfigContext/newOrgConfigContext.cjs +10 -9
  52. package/dist/contexts/newOrgConfigContext/newOrgConfigContext.d.cts +2 -1
  53. package/dist/contexts/newOrgConfigContext/newOrgConfigContext.d.ts +2 -1
  54. package/dist/contexts/newOrgConfigContext/newOrgConfigContext.js +10 -9
  55. package/dist/contexts/salesAgentContext/chatAPI.cjs +26 -5
  56. package/dist/contexts/salesAgentContext/chatAPI.d.cts +7 -3
  57. package/dist/contexts/salesAgentContext/chatAPI.d.ts +7 -3
  58. package/dist/contexts/salesAgentContext/chatAPI.js +26 -5
  59. package/dist/contexts/salesAgentContext/salesAgentContext.cjs +4 -7
  60. package/dist/contexts/salesAgentContext/salesAgentContext.js +4 -7
  61. package/dist/contexts/salesAgentContext/salesAgentService.cjs +42 -17
  62. package/dist/contexts/salesAgentContext/salesAgentService.js +42 -17
  63. package/dist/contexts/types.d.cts +1 -1
  64. package/dist/contexts/types.d.ts +1 -1
  65. package/dist/contexts/typesV3.cjs +1 -1
  66. package/dist/contexts/typesV3.d.cts +4 -4
  67. package/dist/contexts/typesV3.d.ts +4 -4
  68. package/dist/contexts/typesV3.js +1 -1
  69. package/dist/contexts/userIdentityContext/userIdentityContext.cjs +1 -2
  70. package/dist/contexts/userIdentityContext/userIdentityContext.js +1 -2
  71. package/dist/hooks/GrabAndScroll/useGrabAndScroll.d.cts +2 -2
  72. package/dist/hooks/GraphQLConfig/useGraphQLConfig.cjs +1 -2
  73. package/dist/hooks/GraphQLConfig/useGraphQLConfig.js +1 -2
  74. package/dist/hooks/Search/useSearch.cjs +1 -1
  75. package/dist/hooks/Search/useSearch.js +1 -1
  76. package/dist/hooks/Search/useSearchInput.cjs +1 -1
  77. package/dist/hooks/Search/useSearchInput.js +1 -1
  78. package/dist/hooks/utils.d.cts +1 -1
  79. package/dist/packages/components-v3/dist/ChatHeader/hooks/useGetCloseButtonProperties.cjs +1 -1
  80. package/dist/packages/components-v3/dist/ChatHeader/hooks/useGetCloseButtonProperties.js +1 -1
  81. package/dist/packages/components-v3/dist/Container/Container.cjs +1 -1
  82. package/dist/packages/components-v3/dist/Container/Container.js +1 -1
  83. package/dist/packages/components-v3/dist/FloatingButton/FloatingButton.cjs +2 -0
  84. package/dist/packages/components-v3/dist/FloatingButton/FloatingButton.js +4 -0
  85. package/dist/packages/components-v3/dist/FloatingButton/components/Button.cjs +9 -0
  86. package/dist/packages/components-v3/dist/FloatingButton/components/Button.js +10 -0
  87. package/dist/packages/components-v3/dist/FloatingButton/components/Container.cjs +9 -0
  88. package/dist/packages/components-v3/dist/FloatingButton/components/Container.js +10 -0
  89. package/dist/packages/components-v3/dist/FloatingButton/components/Icon.cjs +3 -0
  90. package/dist/packages/components-v3/dist/FloatingButton/components/Icon.js +5 -0
  91. package/dist/packages/components-v3/dist/FloatingButton/components/Wrapper.cjs +9 -0
  92. package/dist/packages/components-v3/dist/FloatingButton/components/Wrapper.js +10 -0
  93. package/dist/packages/components-v3/dist/FloatingButton/components/index.cjs +4 -0
  94. package/dist/packages/components-v3/dist/FloatingButton/components/index.js +6 -0
  95. package/dist/packages/components-v3/dist/FloatingButton/index.cjs +2 -0
  96. package/dist/packages/components-v3/dist/FloatingButton/index.js +4 -0
  97. package/dist/packages/components-v3/dist/FloatingButton/types/types.cjs +17 -0
  98. package/dist/packages/components-v3/dist/FloatingButton/types/types.js +16 -0
  99. package/dist/packages/components-v3/dist/ImageGallery/components/Layout.cjs +3 -3
  100. package/dist/packages/components-v3/dist/ImageGallery/components/Layout.js +3 -3
  101. package/dist/packages/components-v3/dist/WelcomeMessage/components/Container.cjs +1 -1
  102. package/dist/packages/components-v3/dist/WelcomeMessage/components/Container.js +1 -1
  103. package/dist/packages/components-v3/dist/WelcomeMessage/components/SparkleIcon.cjs +1 -1
  104. package/dist/packages/components-v3/dist/WelcomeMessage/components/SparkleIcon.js +1 -1
  105. package/dist/packages/components-v3/dist/utils/CustomIcon.cjs +2 -0
  106. package/dist/packages/components-v3/dist/utils/CustomIcon.js +4 -0
  107. package/dist/services/amplitudeService/amplitudeService.cjs +5 -2
  108. package/dist/services/amplitudeService/amplitudeService.js +5 -2
  109. package/package.json +1 -5
  110. package/src/application/models/api/userEvent.ts +3 -1
  111. package/src/application/models/graphql/queries/getColorsAndFrontendQuery.ts +13 -0
  112. package/src/application/models/guards/api/isApiQueryTypedEventAttributes.ts +6 -1
  113. package/src/atoms/chat/index.ts +0 -1
  114. package/src/contexts/enviveContext/enviveContext.tsx +4 -2
  115. package/src/contexts/graphqlContext/graphqlContext.tsx +32 -41
  116. package/src/contexts/graphqlContext/mockV3Config.ts +3 -2
  117. package/src/contexts/newOrgConfigContext/__tests__/newOrgConfigContext.test.tsx +234 -0
  118. package/src/contexts/newOrgConfigContext/newOrgConfigContext.tsx +14 -8
  119. package/src/contexts/salesAgentContext/chatAPI.ts +21 -4
  120. package/src/contexts/salesAgentContext/salesAgentContext.tsx +3 -7
  121. package/src/contexts/salesAgentContext/salesAgentService.ts +36 -11
  122. package/src/contexts/typesV3.ts +3 -4
  123. package/src/contexts/uiConfigContext/__tests__/uiConfigContext.test.tsx +3 -2
  124. package/src/contexts/userIdentityContext/userIdentityContext.tsx +0 -1
  125. package/src/hooks/GraphQLConfig/useGraphQLConfig.ts +0 -1
  126. package/src/services/amplitudeService/__tests__/amplitudeService.test.ts +0 -15
  127. package/src/services/amplitudeService/amplitudeService.ts +2 -1
  128. package/dist/atoms/chat/replies.cjs +0 -46
  129. package/dist/atoms/chat/replies.d.cts +0 -14
  130. package/dist/atoms/chat/replies.d.ts +0 -14
  131. package/dist/atoms/chat/replies.js +0 -45
  132. package/src/atoms/chat/replies.ts +0 -56
@@ -457,4 +457,238 @@ describe('NewOrgConfigProvider', () => {
457
457
  });
458
458
  });
459
459
  });
460
+ describe('Override Config', () => {
461
+ it('should use overrideConfig when provided instead of fetched config', async () => {
462
+ const fetchedData = {
463
+ colorsConfig: { accentPrimary: '#FF0000' },
464
+ frontendConfig: { merchantOverrideCss: 'fetched-css' },
465
+ };
466
+
467
+ const overrideData = {
468
+ colorsConfig: { accentPrimary: '#0000FF' },
469
+ frontendConfig: { merchantOverrideCss: 'override-css' },
470
+ orgPageConfig: { pageVariants: [] },
471
+ };
472
+
473
+ // Updated TestWrapper to accept overrideConfig
474
+ const TestWrapperWithOverride: React.FC<{
475
+ children: React.ReactNode;
476
+ orgShortName?: string;
477
+ mockHookData?: any;
478
+ overrideConfig?: any;
479
+ }> = ({ children, orgShortName, mockHookData = {}, overrideConfig }) => {
480
+ const mockUseUIConfig = useUIConfig as any;
481
+ mockUseUIConfig.mockReturnValue({
482
+ data: mockHookData.data || {},
483
+ loading: mockHookData.loading ?? false,
484
+ error: mockHookData.error || null,
485
+ refetch: vi.fn(),
486
+ });
487
+
488
+ return (
489
+ <Provider>
490
+ <EnviveConfigProvider
491
+ identifyingPrefix="test"
492
+ orgShortName={orgShortName}
493
+ baseUrl="https://test-api.example.com"
494
+ orgLevelApiKey="test-key"
495
+ >
496
+ <LocalStorageProvider>
497
+ <GraphQLProvider>
498
+ <NewOrgConfigProvider overrideConfig={overrideConfig}>
499
+ {children}
500
+ </NewOrgConfigProvider>
501
+ </GraphQLProvider>
502
+ </LocalStorageProvider>
503
+ </EnviveConfigProvider>
504
+ </Provider>
505
+ );
506
+ };
507
+
508
+ const ConfigChecker: React.FC = () => {
509
+ const config = useNewOrgConfigContext();
510
+ return (
511
+ <div data-testid="config-checker">
512
+ <div data-testid="accent-color">{config.colorsConfig?.accentPrimary || 'none'}</div>
513
+ <div data-testid="css-value">
514
+ {config.frontendConfig?.merchantOverrideCss || 'none'}
515
+ </div>
516
+ </div>
517
+ );
518
+ };
519
+
520
+ render(
521
+ <TestWrapperWithOverride
522
+ orgShortName="test-org"
523
+ mockHookData={{ data: fetchedData, loading: false }}
524
+ overrideConfig={overrideData}
525
+ >
526
+ <ConfigChecker />
527
+ </TestWrapperWithOverride>,
528
+ );
529
+
530
+ await waitFor(() => {
531
+ expect(screen.getByTestId('accent-color')).toHaveTextContent('#0000FF');
532
+ });
533
+
534
+ // Should use override config, not fetched config
535
+ expect(screen.getByTestId('css-value')).toHaveTextContent('override-css');
536
+ });
537
+
538
+ it('should fall back to fetched config when overrideConfig is not provided', async () => {
539
+ const fetchedData = {
540
+ colorsConfig: { accentPrimary: '#FF0000' },
541
+ frontendConfig: { merchantOverrideCss: 'fetched-css' },
542
+ };
543
+
544
+ const ConfigChecker: React.FC = () => {
545
+ const config = useNewOrgConfigContext();
546
+ return (
547
+ <div data-testid="config-checker">
548
+ <div data-testid="accent-color">{config.colorsConfig?.accentPrimary || 'none'}</div>
549
+ <div data-testid="css-value">
550
+ {config.frontendConfig?.merchantOverrideCss || 'none'}
551
+ </div>
552
+ </div>
553
+ );
554
+ };
555
+
556
+ render(
557
+ <TestWrapper
558
+ orgShortName="test-org"
559
+ mockHookData={{ data: fetchedData, loading: false }}
560
+ >
561
+ <ConfigChecker />
562
+ </TestWrapper>,
563
+ );
564
+
565
+ await waitFor(() => {
566
+ expect(screen.getByTestId('accent-color')).toHaveTextContent('#FF0000');
567
+ });
568
+
569
+ expect(screen.getByTestId('css-value')).toHaveTextContent('fetched-css');
570
+ });
571
+
572
+ it('should set newOrgConfigAtom with overrideConfig when provided', async () => {
573
+ const fetchedData = {
574
+ colorsConfig: { accentPrimary: '#FF0000' },
575
+ };
576
+
577
+ const overrideData = {
578
+ colorsConfig: { accentPrimary: '#00FF00' },
579
+ frontendConfig: { merchantOverrideCss: 'override-css' },
580
+ };
581
+
582
+ const TestWrapperWithOverride: React.FC<{
583
+ children: React.ReactNode;
584
+ orgShortName?: string;
585
+ mockHookData?: any;
586
+ overrideConfig?: any;
587
+ }> = ({ children, orgShortName, mockHookData = {}, overrideConfig }) => {
588
+ const mockUseUIConfig = useUIConfig as any;
589
+ mockUseUIConfig.mockReturnValue({
590
+ data: mockHookData.data || {},
591
+ loading: mockHookData.loading ?? false,
592
+ error: mockHookData.error || null,
593
+ refetch: vi.fn(),
594
+ });
595
+
596
+ return (
597
+ <Provider>
598
+ <EnviveConfigProvider
599
+ identifyingPrefix="test"
600
+ orgShortName={orgShortName}
601
+ baseUrl="https://test-api.example.com"
602
+ orgLevelApiKey="test-key"
603
+ >
604
+ <LocalStorageProvider>
605
+ <GraphQLProvider>
606
+ <NewOrgConfigProvider overrideConfig={overrideConfig}>
607
+ {children}
608
+ </NewOrgConfigProvider>
609
+ </GraphQLProvider>
610
+ </LocalStorageProvider>
611
+ </EnviveConfigProvider>
612
+ </Provider>
613
+ );
614
+ };
615
+
616
+ render(
617
+ <TestWrapperWithOverride
618
+ orgShortName="test-org"
619
+ mockHookData={{ data: fetchedData, loading: false }}
620
+ overrideConfig={overrideData}
621
+ >
622
+ <AtomReaderComponent />
623
+ </TestWrapperWithOverride>,
624
+ );
625
+
626
+ await waitFor(() => {
627
+ expect(screen.getByTestId('atom-has-config')).toHaveTextContent('has-config');
628
+ });
629
+
630
+ // Atom should have the override config values
631
+ expect(screen.getByTestId('atom-has-colors')).toHaveTextContent('true');
632
+ expect(screen.getByTestId('atom-has-frontend')).toHaveTextContent('true');
633
+ });
634
+
635
+ it('should handle undefined overrideConfig gracefully', async () => {
636
+ const fetchedData = {
637
+ colorsConfig: { accentPrimary: '#FF0000' },
638
+ frontendConfig: { merchantOverrideCss: 'fetched-css' },
639
+ };
640
+
641
+ const TestWrapperWithOverride: React.FC<{
642
+ children: React.ReactNode;
643
+ orgShortName?: string;
644
+ mockHookData?: any;
645
+ overrideConfig?: any;
646
+ }> = ({ children, orgShortName, mockHookData = {}, overrideConfig }) => {
647
+ const mockUseUIConfig = useUIConfig as any;
648
+ mockUseUIConfig.mockReturnValue({
649
+ data: mockHookData.data || {},
650
+ loading: mockHookData.loading ?? false,
651
+ error: mockHookData.error || null,
652
+ refetch: vi.fn(),
653
+ });
654
+
655
+ return (
656
+ <Provider>
657
+ <EnviveConfigProvider
658
+ identifyingPrefix="test"
659
+ orgShortName={orgShortName}
660
+ baseUrl="https://test-api.example.com"
661
+ orgLevelApiKey="test-key"
662
+ >
663
+ <LocalStorageProvider>
664
+ <GraphQLProvider>
665
+ <NewOrgConfigProvider overrideConfig={overrideConfig}>
666
+ {children}
667
+ </NewOrgConfigProvider>
668
+ </GraphQLProvider>
669
+ </LocalStorageProvider>
670
+ </EnviveConfigProvider>
671
+ </Provider>
672
+ );
673
+ };
674
+
675
+ render(
676
+ <TestWrapperWithOverride
677
+ orgShortName="test-org"
678
+ mockHookData={{ data: fetchedData, loading: false }}
679
+ overrideConfig={undefined}
680
+ >
681
+ <MockNewOrgConfigComponent />
682
+ </TestWrapperWithOverride>,
683
+ );
684
+
685
+ await waitFor(() => {
686
+ expect(screen.getByTestId('loading')).toHaveTextContent('false');
687
+ });
688
+
689
+ // Should fall back to fetched data
690
+ expect(screen.getByTestId('has-colors-config')).toHaveTextContent('true');
691
+ expect(screen.getByTestId('has-frontend-config')).toHaveTextContent('true');
692
+ });
693
+ });
460
694
  });
@@ -14,26 +14,32 @@ const NewOrgConfigContext = createContext<NewOrgConfigContextType | undefined>(u
14
14
 
15
15
  interface NewOrgConfigProviderProps {
16
16
  children: ReactNode;
17
+ overrideConfig?: GraphQlConfigValues;
17
18
  }
18
19
 
19
- export const NewOrgConfigProvider: React.FC<NewOrgConfigProviderProps> = ({ children }) => {
20
+ export const NewOrgConfigProvider: React.FC<NewOrgConfigProviderProps> = ({
21
+ children,
22
+ overrideConfig,
23
+ }) => {
20
24
  const orgShortName = useAtomValue(orgShortNameAtom);
21
25
  const setNewOrgConfig = useSetAtom(newOrgConfigAtom);
22
26
 
23
- const { data: newConfig, loading, error } = useUIConfig();
24
- console.log('NewOrgConfigProvider: newConfig', newConfig, loading, error);
27
+ const { data: fetchedConfig, loading, error } = useUIConfig();
25
28
 
26
29
  const contextValue = useMemo(() => {
27
30
  if (!orgShortName || loading) {
28
- return { ...newConfig, loading: true, error: null };
31
+ return { ...fetchedConfig, loading: true, error: null };
29
32
  }
30
33
 
31
34
  if (error) {
32
- return { ...newConfig, loading: false, error };
35
+ return { ...fetchedConfig, loading: false, error };
33
36
  }
34
- setNewOrgConfig(newConfig);
35
- return { ...newConfig, loading: false, error: null };
36
- }, [orgShortName, loading, error, setNewOrgConfig, newConfig]);
37
+
38
+ const activeConfig = overrideConfig || fetchedConfig;
39
+
40
+ setNewOrgConfig(activeConfig);
41
+ return { ...activeConfig, loading: false, error: null };
42
+ }, [orgShortName, loading, error, setNewOrgConfig, fetchedConfig, overrideConfig]);
37
43
 
38
44
  return (
39
45
  <NewOrgConfigContext.Provider value={contextValue}>{children}</NewOrgConfigContext.Provider>
@@ -6,12 +6,13 @@ import { useSetAtom } from 'jotai';
6
6
  import { useCallback } from 'react';
7
7
  import { v4 as uuid } from 'uuid';
8
8
  import { queueUserEventAtom } from 'src/atoms/chat/messageQueue';
9
+ import { SpiffyMetricsEventName, useAmplitude } from '../amplitudeContext';
9
10
 
10
11
  export interface SalesAgentChatAPI {
11
12
  logPageVisit: ({ pageVisitCategory }: { pageVisitCategory: PageVisitCategory }) => void;
12
13
  logUserEvent: (event: UserEvent) => void;
13
14
  onSuggestionClicked: (suggestion: Suggestion) => void;
14
- onTypedMessageSubmitted: ({ query }: { query: string }) => void;
15
+ onTypedMessageSubmitted: ({ query, userTyped }: { query: string; userTyped: boolean }) => void;
15
16
  onFormResponseSubmitted: (formResponse: any) => void; // TODO: Figure out the right type
16
17
  }
17
18
 
@@ -19,6 +20,7 @@ export const useSalesAgentChatAPI = () => {
19
20
  // TODO: Each of these functions will trigger both the necessary amplitude events and initiate the
20
21
  // necessary actions to trigger the backend API
21
22
  const queueUserEvent = useSetAtom(queueUserEventAtom);
23
+ const { trackEvent } = useAmplitude();
22
24
 
23
25
  const logPageVisit = useCallback(
24
26
  ({ pageVisitCategory }: { pageVisitCategory: PageVisitCategory }) => {
@@ -44,6 +46,13 @@ export const useSalesAgentChatAPI = () => {
44
46
  );
45
47
  const onSuggestionClicked = useCallback(
46
48
  (suggestion: Suggestion) => {
49
+ trackEvent({
50
+ eventName: SpiffyMetricsEventName.ChatSuggestionClicked,
51
+ eventProps: {
52
+ suggestionId: suggestion.id,
53
+ content: suggestion.content,
54
+ },
55
+ });
47
56
  const event: UserEvent = {
48
57
  eventId: uuid(),
49
58
  category: UserEventCategory.SuggestionClicked,
@@ -55,21 +64,29 @@ export const useSalesAgentChatAPI = () => {
55
64
  };
56
65
  queueUserEvent(event);
57
66
  },
58
- [queueUserEvent],
67
+ [queueUserEvent, trackEvent],
59
68
  );
60
69
  const onTypedMessageSubmitted = useCallback(
61
- ({ query }: { query: string }) => {
70
+ ({ query, userTyped }: { query: string; userTyped: boolean }) => {
71
+ trackEvent({
72
+ eventName: SpiffyMetricsEventName.ChatUserMessageInput,
73
+ eventProps: {
74
+ query,
75
+ userTyped,
76
+ },
77
+ });
62
78
  const event: UserEvent = {
63
79
  eventId: uuid(),
64
80
  category: UserEventCategory.QueryTyped,
65
81
  createdAt: new Date().toISOString(),
66
82
  attributes: {
67
83
  query,
84
+ userTyped,
68
85
  },
69
86
  };
70
87
  queueUserEvent(event);
71
88
  },
72
- [queueUserEvent],
89
+ [queueUserEvent, trackEvent],
73
90
  );
74
91
  const onFormResponseSubmitted = useCallback(() => {
75
92
  // TODO: Implement the form response submitted
@@ -1,6 +1,7 @@
1
1
  import { useAtom, useAtomValue, useSetAtom } from 'jotai';
2
2
  import { ReactNode, createContext, useCallback, useEffect, useMemo } from 'react';
3
3
  import { UserEventCategory } from '@spiffy-ai/commerce-api-client';
4
+ import Logger from 'src/application/logging/logger';
4
5
  import { Message, MessageRole, MessageType } from 'src/application/models/message';
5
6
  import { NextMessageRequest, Suggestion, UserEvent } from 'src/application/models';
6
7
  import {
@@ -55,27 +56,22 @@ export const SalesAgentProvider: React.FC<{ children: ReactNode }> = ({ children
55
56
  const sendMessagesToBackend = useCallback(
56
57
  async (requestPayload: NextMessageRequest) => {
57
58
  try {
58
- const response = await getStreamingResponses(requestPayload);
59
+ await getStreamingResponses(requestPayload);
59
60
  setIsInitialized(true);
60
61
 
61
- console.log('response', response);
62
62
  // Remove the pending message from the user events
63
63
  requestPayload.userEvents?.map(userEvent => markUserEventsProcessed([userEvent.eventId]));
64
64
  } catch (error) {
65
- console.error('error sending message');
65
+ Logger.logError('[envive-ai] error sending message', error);
66
66
  }
67
67
  },
68
68
  [getStreamingResponses, setIsInitialized, markUserEventsProcessed],
69
69
  );
70
70
 
71
- console.log('userQueueEventCount', userQueueEventCount);
72
- console.log('userEvents', userEvents);
73
-
74
71
  // This is the primary event loop for communicating with the backend API
75
72
  // It will be triggered when there are pending messages to be sent to the backend
76
73
  // It will be responsible for sending the messages to the backend and receiving the responses
77
74
  useEffect(() => {
78
- console.log('SalesAgentProvider useEffect', userQueueEventCount, isInitialized, userEvents);
79
75
  if (userQueueEventCount > 0) {
80
76
  const payloadEvents = userEvents.slice(0, userQueueEventCount);
81
77
 
@@ -22,7 +22,10 @@ import {
22
22
  suggestionsAtom,
23
23
  } from 'src/atoms/chat/chatState';
24
24
  import { useMessageInterceptor } from 'src/interceptors/useMessageInterceptor';
25
- import { useFeatureFlagService } from '../featureFlagServiceContext';
25
+ import { SpiffyMetricsEventName, useAmplitude } from 'src/contexts/amplitudeContext';
26
+ import { useFeatureFlagService } from 'src/contexts/featureFlagServiceContext';
27
+ import { UserEventCategory } from '@spiffy-ai/commerce-api-client';
28
+
26
29
  import { StatusCodeError } from './statusCodeError';
27
30
 
28
31
  interface SalesAgentService {
@@ -35,6 +38,25 @@ interface SalesAgentService {
35
38
  hydrateMessages: () => Promise<void>;
36
39
  }
37
40
 
41
+ const inputPropsToTrackingProps = (
42
+ payload: NextMessageRequest,
43
+ ): Record<string, unknown> | undefined => {
44
+ const [userEvent] = payload.userEvents || [];
45
+ if (userEvent.category === UserEventCategory.SuggestionClicked) {
46
+ return {
47
+ user_event_type: 'suggestion_clicked',
48
+ user_query: userEvent.attributes.content,
49
+ };
50
+ }
51
+ if (userEvent.category === UserEventCategory.QueryTyped) {
52
+ return {
53
+ user_event_type: 'query_typed',
54
+ user_query: userEvent.attributes.query,
55
+ };
56
+ }
57
+ return {};
58
+ };
59
+
38
60
  export const getQueryParam = (key: string): string | null => {
39
61
  const urlObj = new URL(window.location.href);
40
62
  return urlObj.searchParams.get(key);
@@ -115,6 +137,7 @@ const processStreamingResponse = async (
115
137
  setResponseStreaming: (responseStreaming: boolean) => void,
116
138
  ): Promise<void> => {
117
139
  let lastMessage: Message | undefined;
140
+ setResponseStreaming(true);
118
141
 
119
142
  for await (const response of stream) {
120
143
  try {
@@ -126,8 +149,6 @@ const processStreamingResponse = async (
126
149
  throw new Error('No response from stream');
127
150
  }
128
151
 
129
- setResponseStreaming(true);
130
-
131
152
  const message = messageFromResponse(response);
132
153
  if (!message) {
133
154
  throw new Error('Failed to transform API response to client message');
@@ -148,6 +169,7 @@ const processStreamingResponse = async (
148
169
  response,
149
170
  });
150
171
  }
172
+ setResponseStreaming(false);
151
173
  }
152
174
  };
153
175
 
@@ -158,6 +180,7 @@ export const useSalesAgentService: () => SalesAgentService = () => {
158
180
  const setPendingResponse = useSetAtom(pendingResponseAtom);
159
181
  const setResponseStreaming = useSetAtom(responseStreamingAtom);
160
182
  const messageInterceptor = useMessageInterceptor();
183
+ const { trackEvent } = useAmplitude();
161
184
 
162
185
  const featureFlagService = useFeatureFlagService();
163
186
  const context = useAtomValue(appDetailsAtom);
@@ -192,10 +215,8 @@ export const useSalesAgentService: () => SalesAgentService = () => {
192
215
 
193
216
  const getStreamingResponses = useCallback(
194
217
  async (payload: NextMessageRequest): Promise<void> => {
195
- // logPerfMetric(PerfMetricsEvents.FirstResponseStarted);
196
- // const startTime = Date.now();
218
+ const startTime = Date.now();
197
219
  setPendingResponse(true);
198
- console.log('getStreamingResponses payload', JSON.stringify(payload, null, 2));
199
220
  const stream = CommerceApiClient.getNextResponseStreaming(payload);
200
221
 
201
222
  try {
@@ -208,9 +229,16 @@ export const useSalesAgentService: () => SalesAgentService = () => {
208
229
  setResponseStreaming,
209
230
  );
210
231
 
211
- // TODO: Add support for the Chrome Extension communication
212
232
  // Log successful next_responses call
213
- // const responseTime = Date.now() - startTime;
233
+ const responseTime = Date.now() - startTime;
234
+ trackEvent({
235
+ eventName: SpiffyMetricsEventName.ChatAssistantResponse,
236
+ eventProps: {
237
+ responseTimeMs: responseTime.toString(),
238
+ ...inputPropsToTrackingProps(payload),
239
+ },
240
+ });
241
+ // TODO: Add support for the Chrome Extension communication
214
242
  // await logBundleEvent({
215
243
  // level: 'info',
216
244
  // event: 'NEXT_RESPONSE_SUCCESS',
@@ -264,14 +292,11 @@ export const useSalesAgentService: () => SalesAgentService = () => {
264
292
 
265
293
  const hydrateMessages = useCallback(async () => {
266
294
  const { orgId, chatId, userId } = context;
267
- console.log('hydrateMessages', orgId, chatId, userId, context);
268
295
  const { messages: existingMessages, userEvents } = await CommerceApiClient.getResponses(
269
296
  orgId,
270
297
  chatId,
271
298
  userId,
272
299
  );
273
- console.log('existingMessages', existingMessages);
274
- console.log('userEvents', userEvents);
275
300
  setMessages([...existingMessages]);
276
301
  }, [context, setMessages]);
277
302
 
@@ -5,6 +5,7 @@ import { WidgetWrapperVariant } from '@envive-ai/react-toolkit-v3/WidgetWrapper'
5
5
  import { Theme } from '@envive-ai/react-toolkit-v3/Tokens';
6
6
  import { DynamicLayout } from '@envive-ai/react-toolkit-v3/SocialProof';
7
7
  import { SparkleIconColor } from '@envive-ai/react-toolkit-v3/WelcomeMessage';
8
+ import { FloatingButtonLocation } from '@envive-ai/react-toolkit-v3/FloatingButton';
8
9
  import { ColorNames } from '../application/models/colorsConfigV3';
9
10
  import { CustomerServiceType } from '../types/customerService';
10
11
  import type { MerchantVariantSettings, SearchConfig } from './types';
@@ -170,14 +171,12 @@ type ConfigVersion = ConfigVersionEnum.V3 | ConfigVersionEnum.Deprecated | undef
170
171
 
171
172
  type Mode = 'dark' | 'light';
172
173
 
173
- type Position = 'bottomLeft' | 'middleLeft' | 'middleRight' | 'bottomRight';
174
-
175
174
  type ShowOptions = 'always' | 'postInteraction' | 'none';
176
175
 
177
176
  type Style = 'attached' | 'detached';
178
177
 
179
178
  type FloatingButtonConfig = {
180
- position: Position;
179
+ position: FloatingButtonLocation;
181
180
  backgroundColor?: string;
182
181
  mode: Mode;
183
182
  showOption: ShowOptions;
@@ -341,8 +340,8 @@ export type {
341
340
  PromptButtonCarouselWithImageWidgetV3Config,
342
341
  FloatingChatWidgetV3Config,
343
342
  FloatingChatConfig,
343
+ FloatingButtonConfig,
344
344
  CustomerServiceIntegration,
345
- Position,
346
345
  };
347
346
 
348
347
  export { SocialProofWidgetKind };
@@ -4,9 +4,10 @@ import Logger from 'src/application/logging/logger';
4
4
  import { useNewOrgConfig } from 'src/hooks/NewOrgConfig';
5
5
  import { SparkleIconColor } from '@envive-ai/react-toolkit-v3/WelcomeMessage';
6
6
  import { ChatHeaderVariant } from '@envive-ai/react-toolkit-v3/ChatHeader';
7
+ import { Theme } from '@envive-ai/react-toolkit-v3/Tokens';
8
+ import { FloatingButtonLocation } from '@envive-ai/react-toolkit-v3/FloatingButton';
7
9
  import { UiConfigProvider, useUiConfig } from '../uiConfigContext';
8
10
  import { ConfigVersionEnum, OrgUIConfigV3 } from '../../typesV3';
9
- import { Theme } from '@envive-ai/react-toolkit-v3/Tokens';
10
11
 
11
12
  // Mock the Logger to avoid console output in tests
12
13
  vi.spyOn(Logger, 'logInfo').mockImplementation(() => {});
@@ -107,7 +108,7 @@ describe('UiConfigProvider', () => {
107
108
  },
108
109
  },
109
110
  floatingButton: {
110
- position: 'bottomRight',
111
+ position: FloatingButtonLocation.BOTTOM_RIGHT,
111
112
  backgroundColor: '#000000',
112
113
  mode: 'light',
113
114
  showOption: 'always',
@@ -156,7 +156,6 @@ export const UserIdentityProvider: React.FC<{ children: React.ReactNode }> = ({
156
156
  useEffect(() => {
157
157
  if (isReady && !localUserId) {
158
158
  const newUserId = getUserIdOrDefault();
159
- console.log('useUserIdentity useEffect - setting localUserId', newUserId);
160
159
  setLocalUserId(newUserId);
161
160
  setUserId(newUserId);
162
161
  }
@@ -54,7 +54,6 @@ export const useUIConfig = () => {
54
54
  }
55
55
  }, [data, getConfig, isReady, loading]);
56
56
 
57
- console.log('====== useUIConfig hook ======', isReady, loading, data, error);
58
57
  useEffect(() => {
59
58
  fetchConfig();
60
59
  }, [fetchConfig]);
@@ -435,21 +435,6 @@ describe('AmplitudeService', () => {
435
435
  const eventProps = callArgs[1];
436
436
  expect(eventProps).toHaveProperty('test', 'value');
437
437
  });
438
-
439
- it('should replace existing supplemental props', async () => {
440
- const service = createService();
441
- service.setSupplementalDefaultProps({ prop1: 'value1' });
442
- service.setSupplementalDefaultProps({ prop2: 'value2' });
443
-
444
- await service.trackEvent({
445
- eventName: SpiffyMetricsEventName.ChatUserMessageInput,
446
- });
447
-
448
- const callArgs = mockTrack.mock.calls[0];
449
- const eventProps = callArgs[1];
450
- expect(eventProps).toHaveProperty('prop2', 'value2');
451
- expect(eventProps).not.toHaveProperty('prop1');
452
- });
453
438
  });
454
439
 
455
440
  describe('Event enrichment', () => {
@@ -318,7 +318,8 @@ export class AmplitudeService {
318
318
  }
319
319
  }
320
320
 
321
+ // Ensure that supplemental default props are merged with the existing props
321
322
  setSupplementalDefaultProps(props: Record<string, unknown>): void {
322
- this.supplementalDefaultProps = props;
323
+ this.supplementalDefaultProps = { ...this.supplementalDefaultProps, ...props };
323
324
  }
324
325
  }
@@ -1,46 +0,0 @@
1
- const require_rolldown_runtime = require('../../_virtual/rolldown_runtime.cjs');
2
- const require_message = require('../../application/models/message.cjs');
3
- require('../../application/models/index.cjs');
4
- const require_amplitudeService = require('../../services/amplitudeService/amplitudeService.cjs');
5
- require('../../contexts/amplitudeContext/amplitudeContext.cjs');
6
- const require_atoms_chat_chatState = require('./chatState.cjs');
7
- const require_amplitudeTrackEventAtom = require('../amplitude/amplitudeTrackEventAtom.cjs');
8
- const require_messageQueue = require('./messageQueue.cjs');
9
- require('./index.cjs');
10
- let __spiffy_ai_commerce_api_client = require("@spiffy-ai/commerce-api-client");
11
- let jotai = require("jotai");
12
-
13
- //#region src/atoms/chat/replies.ts
14
- const handleReplyAtom = (0, jotai.atom)(null, (get, set, { message, userTyped }) => {
15
- if (message.type !== require_message.MessageType.QueryTyped) return;
16
- const trackEvent = get(require_amplitudeTrackEventAtom.amplitudeTrackEventAtom);
17
- const queryTyped = message.metadata.content;
18
- set(require_atoms_chat_chatState.replyEventCategoryAtom, __spiffy_ai_commerce_api_client.UserEventCategory.QueryTyped);
19
- set(require_atoms_chat_chatState.userQueryAtom, queryTyped);
20
- set(require_atoms_chat_chatState.messagesAtom, [...get(require_atoms_chat_chatState.messagesAtom), [message]]);
21
- set(require_atoms_chat_chatState.userHasRepliedAtom, true);
22
- set(require_messageQueue.queueUserEventAtom, {
23
- eventId: message.id,
24
- createdAt: message.createdAt,
25
- category: __spiffy_ai_commerce_api_client.UserEventCategory.QueryTyped,
26
- attributes: { query: queryTyped }
27
- });
28
- if (trackEvent) trackEvent({
29
- eventName: require_amplitudeService.SpiffyMetricsEventName.ChatUserMessageInput,
30
- eventProps: {
31
- message_id: message.id,
32
- message_role: message.role,
33
- message_type: message.type,
34
- message_metadata: {
35
- content: message?.metadata?.content,
36
- created_at: message.createdAt,
37
- user_typed: userTyped
38
- }
39
- },
40
- alsoSendToGoogleAnalytics: true
41
- });
42
- });
43
-
44
- //#endregion
45
- exports.handleReplyAtom = handleReplyAtom;
46
- //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVwbGllcy5janMiLCJuYW1lcyI6WyJNZXNzYWdlVHlwZSIsImFtcGxpdHVkZVRyYWNrRXZlbnRBdG9tIiwicmVwbHlFdmVudENhdGVnb3J5QXRvbSIsIlVzZXJFdmVudENhdGVnb3J5IiwidXNlclF1ZXJ5QXRvbSIsIm1lc3NhZ2VzQXRvbSIsInVzZXJIYXNSZXBsaWVkQXRvbSIsInF1ZXVlVXNlckV2ZW50QXRvbSIsIlNwaWZmeU1ldHJpY3NFdmVudE5hbWUiXSwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvYXRvbXMvY2hhdC9yZXBsaWVzLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGF0b20gfSBmcm9tICdqb3RhaSc7XG5pbXBvcnQgeyBNZXNzYWdlLCBNZXNzYWdlVHlwZSB9IGZyb20gJ3NyYy9hcHBsaWNhdGlvbi9tb2RlbHMnO1xuaW1wb3J0IHtcbiAgbWVzc2FnZXNBdG9tLFxuICByZXBseUV2ZW50Q2F0ZWdvcnlBdG9tLFxuICB1c2VySGFzUmVwbGllZEF0b20sXG4gIHVzZXJRdWVyeUF0b20sXG59IGZyb20gJ3NyYy9hdG9tcy9jaGF0JztcbmltcG9ydCB7IFVzZXJFdmVudENhdGVnb3J5IH0gZnJvbSAnQHNwaWZmeS1haS9jb21tZXJjZS1hcGktY2xpZW50JztcbmltcG9ydCB7IFNwaWZmeU1ldHJpY3NFdmVudE5hbWUgfSBmcm9tICdzcmMvY29udGV4dHMvYW1wbGl0dWRlQ29udGV4dC9hbXBsaXR1ZGVDb250ZXh0JztcbmltcG9ydCB7IGFtcGxpdHVkZVRyYWNrRXZlbnRBdG9tIH0gZnJvbSAnc3JjL2F0b21zL2FtcGxpdHVkZS9hbXBsaXR1ZGVUcmFja0V2ZW50QXRvbSc7XG5pbXBvcnQgeyBxdWV1ZVVzZXJFdmVudEF0b20gfSBmcm9tICcuL21lc3NhZ2VRdWV1ZSc7XG5cbnR5cGUgSGFuZGxlUmVwbHlQYXJhbXMgPSB7XG4gIG1lc3NhZ2U6IE1lc3NhZ2U7XG4gIHVzZXJUeXBlZDogYm9vbGVhbjtcbn07XG5cbmV4cG9ydCBjb25zdCBoYW5kbGVSZXBseUF0b20gPSBhdG9tKG51bGwsIChnZXQsIHNldCwgeyBtZXNzYWdlLCB1c2VyVHlwZWQgfTogSGFuZGxlUmVwbHlQYXJhbXMpID0+IHtcbiAgaWYgKG1lc3NhZ2UudHlwZSAhPT0gTWVzc2FnZVR5cGUuUXVlcnlUeXBlZCkge1xuICAgIHJldHVybjtcbiAgfVxuXG4gIGNvbnN0IHRyYWNrRXZlbnQgPSBnZXQoYW1wbGl0dWRlVHJhY2tFdmVudEF0b20pO1xuICBjb25zdCBxdWVyeVR5cGVkID0gbWVzc2FnZS5tZXRhZGF0YS5jb250ZW50O1xuXG4gIHNldChyZXBseUV2ZW50Q2F0ZWdvcnlBdG9tLCBVc2VyRXZlbnRDYXRlZ29yeS5RdWVyeVR5cGVkKTtcbiAgc2V0KHVzZXJRdWVyeUF0b20sIHF1ZXJ5VHlwZWQpO1xuICBzZXQobWVzc2FnZXNBdG9tLCBbLi4uZ2V0KG1lc3NhZ2VzQXRvbSksIFttZXNzYWdlXV0pO1xuICBzZXQodXNlckhhc1JlcGxpZWRBdG9tLCB0cnVlKTtcbiAgc2V0KHF1ZXVlVXNlckV2ZW50QXRvbSwge1xuICAgIGV2ZW50SWQ6IG1lc3NhZ2UuaWQsXG4gICAgY3JlYXRlZEF0OiBtZXNzYWdlLmNyZWF0ZWRBdCxcbiAgICBjYXRlZ29yeTogVXNlckV2ZW50Q2F0ZWdvcnkuUXVlcnlUeXBlZCxcbiAgICBhdHRyaWJ1dGVzOiB7XG4gICAgICBxdWVyeTogcXVlcnlUeXBlZCxcbiAgICB9LFxuICB9KTtcblxuICBpZiAodHJhY2tFdmVudCkge1xuICAgIHRyYWNrRXZlbnQoe1xuICAgICAgZXZlbnROYW1lOiBTcGlmZnlNZXRyaWNzRXZlbnROYW1lLkNoYXRVc2VyTWVzc2FnZUlucHV0LFxuICAgICAgZXZlbnRQcm9wczoge1xuICAgICAgICBtZXNzYWdlX2lkOiBtZXNzYWdlLmlkLFxuICAgICAgICBtZXNzYWdlX3JvbGU6IG1lc3NhZ2Uucm9sZSxcbiAgICAgICAgbWVzc2FnZV90eXBlOiBtZXNzYWdlLnR5cGUsXG4gICAgICAgIG1lc3NhZ2VfbWV0YWRhdGE6IHtcbiAgICAgICAgICBjb250ZW50OiBtZXNzYWdlPy5tZXRhZGF0YT8uY29udGVudCwgLy8gUmVtb3ZlZCBhbXBsaXR1ZGVTYWZlU3RyaW5nXG4gICAgICAgICAgY3JlYXRlZF9hdDogbWVzc2FnZS5jcmVhdGVkQXQsXG4gICAgICAgICAgdXNlcl90eXBlZDogdXNlclR5cGVkLFxuICAgICAgICB9LFxuICAgICAgfSxcbiAgICAgIGFsc29TZW5kVG9Hb29nbGVBbmFseXRpY3M6IHRydWUsXG4gICAgfSk7XG4gIH1cbn0pO1xuIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7O0FBa0JBLE1BQWEsa0NBQXVCLE9BQU8sS0FBSyxLQUFLLEVBQUUsU0FBUyxnQkFBbUM7QUFDakcsS0FBSSxRQUFRLFNBQVNBLDRCQUFZLFdBQy9CO0NBR0YsTUFBTSxhQUFhLElBQUlDLHdEQUF3QjtDQUMvQyxNQUFNLGFBQWEsUUFBUSxTQUFTO0FBRXBDLEtBQUlDLHFEQUF3QkMsa0RBQWtCLFdBQVc7QUFDekQsS0FBSUMsNENBQWUsV0FBVztBQUM5QixLQUFJQywyQ0FBYyxDQUFDLEdBQUcsSUFBSUEsMENBQWEsRUFBRSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0FBQ3BELEtBQUlDLGlEQUFvQixLQUFLO0FBQzdCLEtBQUlDLHlDQUFvQjtFQUN0QixTQUFTLFFBQVE7RUFDakIsV0FBVyxRQUFRO0VBQ25CLFVBQVVKLGtEQUFrQjtFQUM1QixZQUFZLEVBQ1YsT0FBTyxZQUNSO0VBQ0YsQ0FBQztBQUVGLEtBQUksV0FDRixZQUFXO0VBQ1QsV0FBV0ssZ0RBQXVCO0VBQ2xDLFlBQVk7R0FDVixZQUFZLFFBQVE7R0FDcEIsY0FBYyxRQUFRO0dBQ3RCLGNBQWMsUUFBUTtHQUN0QixrQkFBa0I7SUFDaEIsU0FBUyxTQUFTLFVBQVU7SUFDNUIsWUFBWSxRQUFRO0lBQ3BCLFlBQVk7SUFDYjtHQUNGO0VBQ0QsMkJBQTJCO0VBQzVCLENBQUM7RUFFSiJ9