app-tutor-ai-consumer 1.29.2 → 1.31.0

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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ # [1.31.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.30.0...v1.31.0) (2025-09-22)
2
+
3
+ ### Features
4
+
5
+ - add quick actions feature flag ([fa1826a](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/fa1826afd4c2802322ff0277311126cbdb8caa78))
6
+
7
+ # [1.30.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.29.2...v1.30.0) (2025-09-18)
8
+
9
+ ### Bug Fixes
10
+
11
+ - pr issues ([09ad360](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/09ad360449e1f3b06e329f4594b00d9c245faaaa))
12
+
13
+ ### Features
14
+
15
+ - change datahub events to support product agent ([e6a91fb](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/e6a91fbb7995e16d50909ab71018073f41901694))
16
+
1
17
  ## [1.29.2](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.29.1...v1.29.2) (2025-09-16)
2
18
 
3
19
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "app-tutor-ai-consumer",
3
- "version": "1.29.2",
3
+ "version": "1.31.0",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "dev": "rspack serve --env=development --config config/rspack/rspack.config.js",
@@ -19,5 +19,6 @@ export const ActionNames = {
19
19
  CLICK_TUTOR_KNOW_MORE: `${DataHubActions.CLICK}_tutor_know_more`,
20
20
  CLICK_TUTOR_ONBOARDING_START: `${DataHubActions.CLICK}_tutor_onboarding_start`,
21
21
  CLICK_TUTOR_TEST_KNOWLEDGE: `${DataHubActions.CLICK}_tutor_test_knowledge`,
22
- CLOSE_TUTOR_ONBOARDING: `${DataHubActions.CLOSE}_tutor_onboarding`
23
- } as const
22
+ CLOSE_TUTOR_ONBOARDING: `${DataHubActions.CLOSE}_tutor_onboarding`,
23
+ VIEW_PRODUCT_TUTOR: `${DataHubActions.VIEW}_product_tutor`
24
+ }
@@ -24,7 +24,9 @@ export const Result = {
24
24
  export const ComponentNames = {
25
25
  BUTTON_LIKE_ANSWER: 'BUTTON_LIKE_ANSWER',
26
26
  BUTTON_DISLIKE_ANSWER: 'BUTTON_DISLIKE_ANSWER',
27
+ BUTTON_COPY_ANSWER: 'BUTTON_COPY_ANSWER',
27
28
  BUTTON_CLOSE_TUTOR_CHAT: 'BUTTON_CLOSE_TUTOR_CHAT',
29
+ BUTTON_VIEW_AGENT: 'BUTTON_VIEW_AGENT',
28
30
  PRODUCT_CONSUME_CLICK_TUTOR_BACK: 'product_consume_click_tutor_back',
29
31
  PRODUCT_CONSUME_CLICK_TUTOR_HISTORY: 'product_consume_click_tutor_history',
30
32
  PRODUCT_CONSUME_CLICK_TUTOR_INFO: 'product_consume_click_tutor_info',
@@ -35,7 +37,9 @@ export const ComponentNames = {
35
37
  } as const
36
38
 
37
39
  export const ComponentSource = {
38
- ACTION_BAR_STATUS: 'ACTION_BAR_STATUS'
40
+ ACTION_BAR_STATUS: 'ACTION_BAR_STATUS',
41
+ HOTMART_TUTOR: 'HOTMART_TUTOR',
42
+ PRODUCT_AGENT: 'PRODUCT_AGENT'
39
43
  } as const
40
44
 
41
45
  export const ArrivedFrom = {
@@ -0,0 +1,59 @@
1
+ import { DataHubEntities } from '../entities'
2
+ import type { ComponentNamesType, ComponentSourceType, DataHubEntityTypes } from '../types'
3
+
4
+ import BaseSchema from './base-schema'
5
+ import { ProductCategories } from './constants'
6
+ import type { ProductCategoriesType } from './types'
7
+
8
+ export type BaseViewSchemaConstructorArgs = {
9
+ componentName: ComponentNamesType
10
+ componentSource: ComponentSourceType
11
+ arrivedFrom?: string
12
+ entity?: DataHubEntityTypes
13
+ isLogged?: boolean
14
+ productType?: ProductCategoriesType
15
+ }
16
+
17
+ abstract class BaseViewSchema extends BaseSchema {
18
+ private arrivedFrom: string
19
+ private componentName: string
20
+ private componentSource: string
21
+ private productType: ProductCategoriesType
22
+
23
+ entity: DataHubEntityTypes
24
+ isLogged: boolean
25
+
26
+ constructor(args: BaseViewSchemaConstructorArgs) {
27
+ const {
28
+ componentName,
29
+ componentSource,
30
+ arrivedFrom = 'HOME_CLASS_CONSUMER',
31
+ entity = DataHubEntities.PRODUCT_CONSUME,
32
+ isLogged = true,
33
+ productType = ProductCategories.OnlineServices
34
+ } = args
35
+
36
+ super()
37
+
38
+ this.arrivedFrom = arrivedFrom
39
+ this.componentName = componentName
40
+ this.componentSource = componentSource
41
+ this.entity = entity
42
+ this.isLogged = isLogged
43
+ this.productType = productType
44
+ }
45
+
46
+ prepare(): Record<string, unknown> {
47
+ return {
48
+ ...super.prepare(),
49
+ arrivedFrom: this.arrivedFrom,
50
+ componentName: this.componentName,
51
+ componentSource: this.componentSource,
52
+ entity: this.entity,
53
+ isLogged: this.isLogged,
54
+ productType: this.productType
55
+ }
56
+ }
57
+ }
58
+
59
+ export default BaseViewSchema
@@ -1,9 +1,10 @@
1
1
  import { ActionNames } from '../../actions'
2
- import { ComponentNames, ScreenNames } from '../../constants'
2
+ import { ComponentNames, ComponentSource, ScreenNames } from '../../constants'
3
3
  import { DataHubEntities } from '../../entities'
4
4
  import type {
5
5
  ActionNamesType,
6
6
  ComponentNamesType,
7
+ ComponentSourceType,
7
8
  DataHubEntityTypes,
8
9
  ScreenNamesType
9
10
  } from '../../types'
@@ -14,6 +15,7 @@ import type { ButtonReactionsType } from './types'
14
15
 
15
16
  class ClickHotmartTutor extends BaseSchema {
16
17
  private componentName: ComponentNamesType
18
+ private componentSource: ComponentSourceType
17
19
  private screenName: ScreenNamesType
18
20
  private messageId: string
19
21
  private hasAccess: boolean
@@ -26,22 +28,27 @@ class ClickHotmartTutor extends BaseSchema {
26
28
  reactionType,
27
29
  messageId,
28
30
  hasAccess = true,
29
- isLogged = true
31
+ isLogged = true,
32
+ componentSource = ComponentSource.HOTMART_TUTOR
30
33
  }: {
31
34
  reactionType: ButtonReactionsType
32
35
  messageId: string
33
36
  hasAccess?: boolean
34
37
  isLogged?: boolean
38
+ componentSource?: ComponentSourceType
35
39
  }) {
36
40
  super()
37
41
 
38
42
  this.action = ActionNames.CLICK_HOTMART_TUTOR
39
43
  this.entity = DataHubEntities.HOME
40
44
 
41
- this.componentName =
42
- reactionType === ButtonReactions.LIKE
43
- ? ComponentNames.BUTTON_LIKE_ANSWER
44
- : ComponentNames.BUTTON_DISLIKE_ANSWER
45
+ this.componentName = {
46
+ [ButtonReactions.LIKE]: ComponentNames.BUTTON_LIKE_ANSWER,
47
+ [ButtonReactions.DISLIKE]: ComponentNames.BUTTON_DISLIKE_ANSWER,
48
+ [ButtonReactions.COPY]: ComponentNames.BUTTON_COPY_ANSWER
49
+ }[reactionType]
50
+
51
+ this.componentSource = componentSource
45
52
  this.screenName = ScreenNames.HOME_CONSUMER
46
53
  this.messageId = messageId
47
54
  this.hasAccess = hasAccess
@@ -52,6 +59,7 @@ class ClickHotmartTutor extends BaseSchema {
52
59
  return {
53
60
  ...super.prepare(),
54
61
  componentName: this.componentName,
62
+ componentSource: this.componentSource,
55
63
  screenName: this.screenName,
56
64
  messageId: this.messageId,
57
65
  hasAccess: this.hasAccess
@@ -1,4 +1,5 @@
1
1
  export const ButtonReactions = {
2
2
  LIKE: 'LIKE',
3
- DISLIKE: 'DISLIKE'
3
+ DISLIKE: 'DISLIKE',
4
+ COPY: 'COPY'
4
5
  } as const
@@ -0,0 +1,42 @@
1
+ import { ActionNames } from '../../actions'
2
+ import { ComponentNames, ComponentSource } from '../../constants'
3
+ import type { ActionNamesType, ComponentNamesType, ComponentSourceType } from '../../types'
4
+ import BaseViewSchema from '../base-view-schema'
5
+ import type { ProductCategoriesType } from '../types'
6
+
7
+ export type ViewProductTutorSchemaConstructorArgs = {
8
+ arrivedFrom?: string
9
+ componentName?: ComponentNamesType
10
+ componentSource?: ComponentSourceType
11
+ productType?: ProductCategoriesType
12
+ }
13
+
14
+ class ViewProductTutorSchema extends BaseViewSchema {
15
+ action: ActionNamesType
16
+
17
+ constructor(args?: ViewProductTutorSchemaConstructorArgs) {
18
+ const {
19
+ arrivedFrom,
20
+ productType,
21
+ componentName = ComponentNames.BUTTON_VIEW_AGENT,
22
+ componentSource = ComponentSource.HOTMART_TUTOR
23
+ } = args ?? {}
24
+
25
+ super({
26
+ arrivedFrom,
27
+ productType,
28
+ componentName,
29
+ componentSource
30
+ })
31
+
32
+ this.action = ActionNames.VIEW_PRODUCT_TUTOR
33
+ }
34
+
35
+ getDataHubEventData() {
36
+ return {
37
+ ...super.prepare()
38
+ }
39
+ }
40
+ }
41
+
42
+ export default ViewProductTutorSchema
@@ -1,5 +1,12 @@
1
1
  import type { ActionNames } from './actions'
2
- import type { ComponentNames, Platform, Result, ScreenNames, System } from './constants'
2
+ import type {
3
+ ComponentNames,
4
+ ComponentSource,
5
+ Platform,
6
+ Result,
7
+ ScreenNames,
8
+ System
9
+ } from './constants'
3
10
  import type { DataHubEntities } from './entities'
4
11
 
5
12
  export type ActionNamesType = (typeof ActionNames)[keyof typeof ActionNames]
@@ -7,6 +14,7 @@ export type DataHubEntityTypes = (typeof DataHubEntities)[keyof typeof DataHubEn
7
14
  export type PlatformType = (typeof Platform)[keyof typeof Platform]
8
15
  export type SystemType = (typeof System)[keyof typeof System]
9
16
  export type ComponentNamesType = (typeof ComponentNames)[keyof typeof ComponentNames]
17
+ export type ComponentSourceType = (typeof ComponentSource)[keyof typeof ComponentSource]
10
18
  export type ScreenNamesType = (typeof ScreenNames)[keyof typeof ScreenNames]
11
19
  export type ResultType = (typeof Result)[keyof typeof Result]
12
20
 
@@ -1,13 +1,14 @@
1
- import { type CSSProperties, useState } from 'react'
1
+ import { type CSSProperties, useCallback, useState } from 'react'
2
2
  import clsx from 'clsx'
3
3
  import dayjs from 'dayjs'
4
4
  import { useTranslation } from 'react-i18next'
5
5
 
6
- import { DataHubService } from '@/src/config/datahub'
6
+ import { ComponentSource, DataHubService } from '@/src/config/datahub'
7
7
  import type { ButtonReactionsType } from '@/src/config/datahub/schemas/tutor'
8
8
  import { ButtonReactions, ClickHotmartTutor } from '@/src/config/datahub/schemas/tutor'
9
9
  import { Button, Icon } from '@/src/lib/components'
10
10
  import { ToastUtils } from '@/src/lib/utils'
11
+ import { useIsAgentParentAtomValue } from '@/src/modules/widget'
11
12
  import type { ParsedMessage } from '../../types'
12
13
 
13
14
  export type MessageActionsProps = {
@@ -21,6 +22,19 @@ function MessageActions({ message, className, showActions = false }: MessageActi
21
22
  const [copying, setCopying] = useState(false)
22
23
  const [copied, setCopied] = useState(false)
23
24
  const [reaction, setReaction] = useState<ButtonReactionsType | null>(null)
25
+ const isAgentMode = useIsAgentParentAtomValue()
26
+
27
+ const sendEvent = useCallback(
28
+ ({ reactionType }: { reactionType: ButtonReactionsType }) => {
29
+ const schema = new ClickHotmartTutor({
30
+ messageId: message.id,
31
+ reactionType,
32
+ componentSource: isAgentMode ? ComponentSource.PRODUCT_AGENT : undefined
33
+ })
34
+ DataHubService.sendEvent({ schema })
35
+ },
36
+ [isAgentMode, message.id]
37
+ )
24
38
 
25
39
  const copyToClipboard = (): void => {
26
40
  if (!message.text) return
@@ -32,6 +46,7 @@ function MessageActions({ message, className, showActions = false }: MessageActi
32
46
  .then(() => {
33
47
  setCopied(true)
34
48
  ToastUtils.dispatch({ message: t('general.buttons.copied') })
49
+ sendEvent({ reactionType: ButtonReactions.COPY })
35
50
  })
36
51
  .catch((err) => {
37
52
  const errorMessage = t('generic_error.title')
@@ -45,8 +60,7 @@ function MessageActions({ message, className, showActions = false }: MessageActi
45
60
  }
46
61
 
47
62
  const handleReaction = (reactionType: ButtonReactionsType = ButtonReactions.LIKE): void => {
48
- const schema = new ClickHotmartTutor({ messageId: message.id, reactionType })
49
- DataHubService.sendEvent({ schema })
63
+ sendEvent({ reactionType })
50
64
  setReaction(reactionType)
51
65
  }
52
66
 
@@ -10,6 +10,7 @@ import { getAllMessagesQuery, useSendTextMessage } from '@/src/modules/messages/
10
10
  import { useMessagesMaxCount } from '@/src/modules/messages/store'
11
11
  import { useGetProfile } from '@/src/modules/profile'
12
12
  import { TutorWidgetEvents } from '../../events'
13
+ import { useSendViewTutorEvent } from '../../hooks/use-send-view-tutor-event'
13
14
  import {
14
15
  useWidgetLoadingAtom,
15
16
  useWidgetSettingsAtomValue,
@@ -47,6 +48,8 @@ function ChatPage() {
47
48
 
48
49
  const messagesQuery = useInfiniteQuery(messagesQueryConfig)
49
50
 
51
+ useSendViewTutorEvent()
52
+
50
53
  const handleSendMessage = () => {
51
54
  const text = chatInputRef.current?.value ?? ''
52
55
 
@@ -33,7 +33,7 @@ function WidgetStarterPage() {
33
33
  const isMobile = useMediaQuery({ maxSize: 'md' })
34
34
  const widgetLoading = useWidgetLoadingAtomValue()
35
35
  const hasSentInitialMessage = useRef(false)
36
- const [newTutorWidgetFF] = useDecision('lex_new_tutor_widget')
36
+ const [tutorQuickActionsFF] = useDecision('lex_tutor_quick_actions')
37
37
  const [lexTutorInitialMessageFF] = useDecision('lex_tutor_new_widget_initial_message')
38
38
 
39
39
  useRefEventListener<HTMLTextAreaElement>({
@@ -155,7 +155,7 @@ function WidgetStarterPage() {
155
155
  />
156
156
  </div>
157
157
  </div>
158
- {newTutorWidgetFF?.enabled && newTutorWidgetFF?.variables?.['show_quick_actions'] ? (
158
+ {tutorQuickActionsFF?.enabled ? (
159
159
  <QuickActionButtons
160
160
  className='grid-area-[b] my-4 flex flex-shrink-0 snap-x snap-mandatory gap-2 overflow-x-auto whitespace-nowrap [scrollbar-width:none] [&::-webkit-scrollbar]:hidden'
161
161
  isDarkTheme={isDarkTheme}
@@ -1,3 +1,4 @@
1
1
  export * from './use-init-widget'
2
2
  export * from './use-listen-to-theme-change-event'
3
3
  export * from './use-listen-to-visibility-events'
4
+ export * from './use-send-view-tutor-event'
@@ -0,0 +1 @@
1
+ export { default as useSendViewTutorEvent } from './use-send-view-tutor-event'
@@ -0,0 +1,36 @@
1
+ import { ComponentSource, DataHubService } from '@/src/config/datahub'
2
+ import ViewProductTutorSchema from '@/src/config/datahub/schemas/tutor/view-product-tutor'
3
+ import { renderHook } from '@/src/config/tests'
4
+ import { useIsAgentParentAtomValue } from '../../store'
5
+
6
+ import useSendViewTutorEvent from './use-send-view-tutor-event'
7
+
8
+ vi.mock('../../store', () => ({ useIsAgentParentAtomValue: vi.fn() }))
9
+
10
+ describe('useSendViewTutorEvent', () => {
11
+ const prepareHook = () => renderHook(useSendViewTutorEvent)
12
+
13
+ beforeEach(() => {
14
+ vi.mocked(useIsAgentParentAtomValue).mockReturnValue(true)
15
+ })
16
+
17
+ it('should send datahub event when product is of type agent', () => {
18
+ const schema = new ViewProductTutorSchema({ componentSource: ComponentSource.PRODUCT_AGENT })
19
+
20
+ vi.spyOn(DataHubService, 'sendEvent')
21
+
22
+ prepareHook()
23
+
24
+ expect(DataHubService.sendEvent).toHaveBeenNthCalledWith(1, { schema })
25
+ })
26
+
27
+ it('should not send datahub event when product is not of type agent', () => {
28
+ vi.mocked(useIsAgentParentAtomValue).mockReturnValue(false)
29
+
30
+ vi.spyOn(DataHubService, 'sendEvent')
31
+
32
+ prepareHook()
33
+
34
+ expect(DataHubService.sendEvent).not.toHaveBeenCalled()
35
+ })
36
+ })
@@ -0,0 +1,20 @@
1
+ import { useEffect, useRef } from 'react'
2
+
3
+ import { ComponentSource, DataHubService } from '@/src/config/datahub'
4
+ import ViewProductTutorSchema from '@/src/config/datahub/schemas/tutor/view-product-tutor'
5
+ import { useIsAgentParentAtomValue } from '../../store'
6
+
7
+ function useSendViewTutorEvent() {
8
+ const isAgentMode = useIsAgentParentAtomValue()
9
+ const sent = useRef(false)
10
+
11
+ useEffect(() => {
12
+ if (sent.current || !isAgentMode) return
13
+
14
+ const schema = new ViewProductTutorSchema({ componentSource: ComponentSource.PRODUCT_AGENT })
15
+ DataHubService.sendEvent({ schema })
16
+ sent.current = true
17
+ }, [isAgentMode])
18
+ }
19
+
20
+ export default useSendViewTutorEvent
@@ -2,4 +2,5 @@ export * from './widget-container-intrinsic-height.atom'
2
2
  export * from './widget-loading.atom'
3
3
  export * from './widget-scrolling.atom'
4
4
  export * from './widget-settings.atom'
5
+ export * from './widget-settings-config.atom'
5
6
  export * from './widget-tabs.atom'
@@ -0,0 +1,10 @@
1
+ import { atom, useAtomValue } from 'jotai'
2
+
3
+ import { widgetSettingsAtom } from './widget-settings.atom'
4
+
5
+ export const widgetSettingsConfigAgentParentAtom = atom((get) => {
6
+ const settings = get(widgetSettingsAtom)
7
+ return settings?.config?.metadata?.parent === 'AGENT'
8
+ })
9
+
10
+ export const useIsAgentParentAtomValue = () => useAtomValue(widgetSettingsConfigAgentParentAtom)