app-tutor-ai-consumer 1.52.1 → 1.54.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,20 @@
1
+ # [1.54.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.53.0...v1.54.0) (2026-01-28)
2
+
3
+ ### Bug Fixes
4
+
5
+ - code review issues ([6d0a17a](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/6d0a17a8874001b7d30d889152a432fbf5046467))
6
+
7
+ ### Features
8
+
9
+ - add create thread datahub event ([3f8fa1e](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/3f8fa1e5e98f9bef7201a7835a56252943a35122))
10
+
11
+ # [1.53.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.52.1...v1.53.0) (2026-01-27)
12
+
13
+ ### Features
14
+
15
+ - send message tsx ([db84786](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/db84786c6d67bef222476e2afdc2c5460495da4c))
16
+ - send user_name ([4ee8dfc](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/4ee8dfcca0cebfec8855bfc4e9df18ce22479912))
17
+
1
18
  ## [1.52.1](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.52.0...v1.52.1) (2026-01-20)
2
19
 
3
20
  # [1.52.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.51.0...v1.52.0) (2026-01-19)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "app-tutor-ai-consumer",
3
- "version": "1.52.1",
3
+ "version": "1.54.0",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "dev": "rspack serve --env=development --config config/rspack/rspack.config.js",
@@ -23,5 +23,6 @@ export const ActionNames = {
23
23
  CLOSE_TUTOR_ONBOARDING: `${DataHubActions.CLOSE}_tutor_onboarding`,
24
24
  VIEW_PRODUCT_TUTOR: `${DataHubActions.VIEW}_product_tutor`,
25
25
  VIEW_ANSWER_MESSAGE: `${DataHubActions.VIEW}_answer_message`,
26
- VIEW_CHAT: `${DataHubActions.VIEW}_chat`
26
+ VIEW_CHAT: `${DataHubActions.VIEW}_chat`,
27
+ TRY_CREATE_THREAD: `${DataHubActions.TRY}_create_thread`
27
28
  }
@@ -0,0 +1,60 @@
1
+ import HttpCodes from '@/src/lib/utils/http-codes'
2
+ import { ActionNames } from '../../actions'
3
+ import { DataHubEntities } from '../../entities'
4
+ import type { ActionNamesType, DataHubEntityTypes } from '../../types'
5
+ import BaseTrySchema from '../base-try-schema'
6
+ import { ProductCategories } from '../constants'
7
+
8
+ type Result = 'SUCCESSFUL' | 'FAILURE'
9
+
10
+ type ConstructorParams = {
11
+ title: string
12
+ componentName: string
13
+ conversationId: string
14
+ entity?: DataHubEntityTypes
15
+ result?: Result
16
+ statusCode?: number
17
+ failureDescription?: string
18
+ }
19
+
20
+ export class TryCreateThreadSchema extends BaseTrySchema {
21
+ action: ActionNamesType
22
+
23
+ private agent: 'HOTMART_TUTOR' | 'PRODUCT_AGENT'
24
+ private conversationId: string
25
+ private title: string
26
+
27
+ constructor({
28
+ title,
29
+ componentName,
30
+ conversationId,
31
+ entity = DataHubEntities.CONVERSATIONAL_AGENT,
32
+ result = 'SUCCESSFUL',
33
+ statusCode = HttpCodes.OK,
34
+ failureDescription = 'NOT_FAILURE_RESULT_EVENT'
35
+ }: ConstructorParams) {
36
+ super({
37
+ result,
38
+ componentName,
39
+ statusCode,
40
+ failureDescription,
41
+ componentSource: 'CONTENT_LESSON',
42
+ productType: ProductCategories.ConversationalAgent
43
+ })
44
+
45
+ this.entity = entity
46
+ this.action = ActionNames.TRY_CREATE_THREAD
47
+ this.agent = 'PRODUCT_AGENT'
48
+ this.conversationId = conversationId
49
+ this.title = title
50
+ }
51
+
52
+ getDataHubEventData(): Record<string, unknown> {
53
+ return {
54
+ ...super.prepare(),
55
+ agent: this.agent,
56
+ conversationId: this.conversationId,
57
+ title: this.title
58
+ }
59
+ }
60
+ }
@@ -4,6 +4,7 @@ import { DataHubEntities } from '../entities'
4
4
  import type { DataHubEntityTypes, ResultType, ScreenNamesType } from '../types'
5
5
 
6
6
  import BaseProductSchema from './base-product-schema'
7
+ import type { ProductCategoriesType } from './types'
7
8
 
8
9
  export type BaseTrySchemaConstructorArgs = {
9
10
  componentName: string
@@ -14,6 +15,7 @@ export type BaseTrySchemaConstructorArgs = {
14
15
  statusCode?: number
15
16
  failureDescription?: string
16
17
  screenName?: ScreenNamesType
18
+ productType?: ProductCategoriesType
17
19
  }
18
20
 
19
21
  abstract class BaseTrySchema extends BaseProductSchema {
@@ -31,6 +33,7 @@ abstract class BaseTrySchema extends BaseProductSchema {
31
33
  const {
32
34
  componentName,
33
35
  componentSource,
36
+ productType,
34
37
  entity = DataHubEntities.HOME,
35
38
  failureDescription = Result.FAILURE_DESCRIPTION,
36
39
  isLogged = true,
@@ -39,7 +42,7 @@ abstract class BaseTrySchema extends BaseProductSchema {
39
42
  screenName = ScreenNames.HOME_CONSUMER
40
43
  } = args
41
44
 
42
- super()
45
+ super({ productType })
43
46
 
44
47
  this.componentName = componentName
45
48
  this.componentSource = componentSource
@@ -1,6 +1,9 @@
1
+ import { DataHubService } from '@/src/config/datahub'
2
+ import { TryCreateThreadSchema } from '@/src/config/datahub/schemas/agent/tryCreateThreadSchema'
1
3
  import { renderHook } from '@/src/config/tests'
4
+ import HttpCodes from '@/src/lib/utils/http-codes'
2
5
  import { useUpdateConversationTitle } from '@/src/modules/conversation/hooks/update-conversation-title'
3
- import { useWidgetSettingsAtomValue } from '@/src/modules/widget/store'
6
+ import { useIsAgentParentAtomValue, useWidgetSettingsAtomValue } from '@/src/modules/widget/store'
4
7
  import { useMessagesCountAtomValue } from '../../store/messages-count.atom'
5
8
  import { useSendTextMessage } from '../use-send-text-message'
6
9
 
@@ -11,7 +14,8 @@ vi.mock('@/src/modules/conversation/hooks/update-conversation-title', () => ({
11
14
  }))
12
15
 
13
16
  vi.mock('@/src/modules/widget/store', () => ({
14
- useWidgetSettingsAtomValue: vi.fn()
17
+ useWidgetSettingsAtomValue: vi.fn(),
18
+ useIsAgentParentAtomValue: vi.fn()
15
19
  }))
16
20
 
17
21
  vi.mock('../../store/messages-count.atom', () => ({
@@ -25,6 +29,19 @@ vi.mock('../use-send-text-message', () => ({
25
29
  describe('useSendFirstMessage', () => {
26
30
  const mockSendMessage = vi.fn()
27
31
  const mockUpdateTitle = vi.fn()
32
+ const mockMessage = { id: 'msg-1', content: 'Hello' }
33
+ const mockSettings = {
34
+ conversationId: 'conv-123',
35
+ productId: 'prod-456',
36
+ config: {
37
+ metadata: {
38
+ agentProductId: 'agent-prod-42',
39
+ agentName: 'Agent Name',
40
+ courseName: 'Course Name',
41
+ source: 'Source'
42
+ }
43
+ }
44
+ }
28
45
 
29
46
  beforeEach(() => {
30
47
  vi.clearAllMocks()
@@ -47,35 +64,55 @@ describe('useSendFirstMessage', () => {
47
64
  } as never)
48
65
 
49
66
  vi.mocked(useMessagesCountAtomValue).mockReturnValue(0)
67
+
68
+ vi.mocked(useIsAgentParentAtomValue).mockReturnValue(true)
50
69
  })
51
70
 
52
71
  describe('when sending first message', () => {
53
- it('should send message and update conversation title', async () => {
54
- vi.mocked(useWidgetSettingsAtomValue).mockReturnValue({
55
- conversationId: 'conv-123',
56
- productId: 'prod-456',
57
- config: {
58
- metadata: {
59
- agentProductId: 'agent-prod-42',
60
- agentName: 'Agent Name',
61
- courseName: 'Course Name',
62
- source: 'Source'
63
- }
64
- }
65
- } as never)
66
- const mockMessage = { id: 'msg-1', content: 'Hello' }
72
+ it('should not dispatch create thread event when in Tutor Mode', async () => {
73
+ vi.spyOn(DataHubService, 'sendEvent').mockImplementationOnce(() => null)
74
+ vi.mocked(useIsAgentParentAtomValue).mockReturnValue(false)
75
+ vi.mocked(useWidgetSettingsAtomValue).mockReturnValue(mockSettings as never)
67
76
  mockSendMessage.mockResolvedValue(mockMessage)
68
77
  mockUpdateTitle.mockResolvedValue({})
69
78
 
79
+ const expectedResult = {
80
+ conversationId: 'conv-123',
81
+ productId: 'agent-prod-42',
82
+ subject: 'Hello world'
83
+ }
84
+
70
85
  const { result } = renderHook(() => useSendFirstMessage())
71
86
 
72
- await result.current.sendFirstMessage('Hello world')
87
+ await result.current.sendFirstMessage(expectedResult.subject)
73
88
 
74
- expect(mockSendMessage).toHaveBeenCalledWith('Hello world', undefined)
75
- expect(mockUpdateTitle).toHaveBeenCalledWith({
89
+ expect(DataHubService.sendEvent).not.toHaveBeenCalled()
90
+ })
91
+
92
+ it('should send message and update conversation title', async () => {
93
+ vi.spyOn(DataHubService, 'sendEvent').mockImplementation(() => null)
94
+ vi.mocked(useWidgetSettingsAtomValue).mockReturnValue(mockSettings as never)
95
+ mockSendMessage.mockResolvedValue(mockMessage)
96
+ mockUpdateTitle.mockResolvedValue({})
97
+
98
+ const expectedResult = {
76
99
  conversationId: 'conv-123',
77
100
  productId: 'agent-prod-42',
78
101
  subject: 'Hello world'
102
+ }
103
+
104
+ const { result } = renderHook(() => useSendFirstMessage())
105
+
106
+ await result.current.sendFirstMessage(expectedResult.subject)
107
+
108
+ expect(mockSendMessage).toHaveBeenCalledWith(expectedResult.subject, undefined)
109
+ expect(mockUpdateTitle).toHaveBeenCalledWith(expectedResult)
110
+ expect(DataHubService.sendEvent).toHaveBeenCalledExactlyOnceWith({
111
+ schema: new TryCreateThreadSchema({
112
+ title: expectedResult.subject,
113
+ componentName: 'AGENT_CREATE_THREAD',
114
+ conversationId: expectedResult.conversationId
115
+ })
79
116
  })
80
117
  })
81
118
  })
@@ -112,6 +149,7 @@ describe('useSendFirstMessage', () => {
112
149
 
113
150
  describe('error handling', () => {
114
151
  it('should throw error when send message fails', async () => {
152
+ vi.mocked(useWidgetSettingsAtomValue).mockReturnValue(mockSettings as never)
115
153
  vi.spyOn(console, 'error').mockImplementationOnce(() => {})
116
154
  const error = new Error('Send failed')
117
155
  mockSendMessage.mockRejectedValue(error)
@@ -119,6 +157,17 @@ describe('useSendFirstMessage', () => {
119
157
  const { result } = renderHook(() => useSendFirstMessage())
120
158
 
121
159
  await expect(result.current.sendFirstMessage('Hello')).rejects.toThrow('Send failed')
160
+
161
+ expect(DataHubService.sendEvent).toHaveBeenCalledExactlyOnceWith({
162
+ schema: new TryCreateThreadSchema({
163
+ title: mockMessage.content,
164
+ componentName: 'AGENT_CREATE_THREAD',
165
+ conversationId: mockSettings.conversationId,
166
+ result: 'FAILURE',
167
+ statusCode: HttpCodes.BAD_REQUEST,
168
+ failureDescription: 'Send failed'
169
+ })
170
+ })
122
171
  })
123
172
  })
124
173
 
@@ -2,8 +2,11 @@ import { useCallback } from 'react'
2
2
  import type { Message } from '@hotmart-org-ca/sparkie/dist/MessageService'
3
3
  import type { MutateOptions } from '@tanstack/react-query'
4
4
 
5
+ import { DataHubService } from '@/src/config/datahub'
6
+ import { TryCreateThreadSchema } from '@/src/config/datahub/schemas/agent/tryCreateThreadSchema'
7
+ import HttpCodes from '@/src/lib/utils/http-codes'
5
8
  import { useUpdateConversationTitle } from '@/src/modules/conversation/hooks/update-conversation-title'
6
- import { useWidgetSettingsAtomValue } from '@/src/modules/widget/store'
9
+ import { useIsAgentParentAtomValue, useWidgetSettingsAtomValue } from '@/src/modules/widget/store'
7
10
  import { useMessagesCountAtomValue } from '../../store/messages-count.atom'
8
11
  import { excerptMessage } from '../../utils'
9
12
  import { useSendTextMessage } from '../use-send-text-message'
@@ -13,10 +16,12 @@ function useSendFirstMessage() {
13
16
  const sendMessageMutation = useSendTextMessage()
14
17
  const updateTitleMutation = useUpdateConversationTitle()
15
18
  const settings = useWidgetSettingsAtomValue()
19
+ const isAgent = useIsAgentParentAtomValue()
16
20
 
17
21
  const sendFirstMessage = useCallback(
18
22
  async (message: string, options?: MutateOptions<Message, Error, string, void>) => {
19
23
  const isFirstMessage = messagesCount === 0
24
+ const subject = excerptMessage({ message })
20
25
 
21
26
  try {
22
27
  const messageResult = await sendMessageMutation.mutateAsync(message, options)
@@ -29,17 +34,46 @@ function useSendFirstMessage() {
29
34
  await updateTitleMutation.mutateAsync({
30
35
  conversationId: settings.conversationId,
31
36
  productId: settings?.config?.metadata?.agentProductId,
32
- subject: excerptMessage({ message })
37
+ subject
38
+ })
39
+ }
40
+
41
+ if (isAgent) {
42
+ DataHubService.sendEvent({
43
+ schema: new TryCreateThreadSchema({
44
+ title: subject,
45
+ componentName: 'AGENT_CREATE_THREAD',
46
+ conversationId: settings?.conversationId || ''
47
+ })
33
48
  })
34
49
  }
35
50
 
36
51
  return messageResult
37
52
  } catch (error) {
38
- console.error('Failed to send first message:', error)
53
+ if (isAgent) {
54
+ DataHubService.sendEvent({
55
+ schema: new TryCreateThreadSchema({
56
+ title: subject,
57
+ componentName: 'AGENT_CREATE_THREAD',
58
+ conversationId: settings?.conversationId || '',
59
+ result: 'FAILURE',
60
+ statusCode: HttpCodes.BAD_REQUEST,
61
+ failureDescription: error instanceof Error ? error?.message : String(error)
62
+ })
63
+ })
64
+ }
65
+
39
66
  throw error
40
67
  }
41
68
  },
42
- [messagesCount, sendMessageMutation, updateTitleMutation, settings]
69
+ [
70
+ messagesCount,
71
+ sendMessageMutation,
72
+ settings?.conversationId,
73
+ settings?.config?.metadata?.agentProductId,
74
+ isAgent,
75
+ updateTitleMutation
76
+ ]
43
77
  )
44
78
 
45
79
  return {
@@ -25,7 +25,7 @@ vi.mock('ua-parser-js', () => ({ UAParser: vi.fn() }))
25
25
 
26
26
  describe('useSendTextMessage', () => {
27
27
  const message = chance.animal()
28
- const getProfileMock = { data: { userId: chance.integer() } }
28
+ const getProfileMock = { data: { userId: chance.integer(), name: chance.name() } }
29
29
  const defaultSettings = new WidgetSettingPropsBuilder()
30
30
  .withClassHashId(chance.guid())
31
31
  .withOwner_id(chance.guid())
@@ -116,6 +116,7 @@ describe('useSendTextMessage', () => {
116
116
  osVersion: UAParserMock.browser.version,
117
117
  platformDetail: UAParserMock.browser.name,
118
118
  ucode: defaultSettings.user?.ucode,
119
+ user_name: getProfileMock.data.name || defaultSettings.user?.name,
119
120
  appVersion: APP_VERSION,
120
121
  membershipId: defaultSettings.membershipId,
121
122
  membershipSlug: defaultSettings.membershipSlug,
@@ -94,6 +94,7 @@ function useSendTextMessage() {
94
94
  platform: PLATFORM.WEB,
95
95
  platformDetail: browserInfo.name,
96
96
  ucode: settings.user?.ucode,
97
+ user_name: profileQuery.data?.name || settings.user?.name,
97
98
  agentProductId: settings.config?.metadata?.agentProductId,
98
99
  agentName: settings.config?.metadata?.agentName,
99
100
  courseName: settings.config?.metadata?.courseName,
@@ -3,8 +3,10 @@ import clsx from 'clsx'
3
3
 
4
4
  import { Icon } from '@/src/lib/components'
5
5
 
6
- export interface IScrollToBottomButtonProps
7
- extends DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> {
6
+ export interface IScrollToBottomButtonProps extends DetailedHTMLProps<
7
+ ButtonHTMLAttributes<HTMLButtonElement>,
8
+ HTMLButtonElement
9
+ > {
8
10
  show?: boolean
9
11
  top?: number
10
12
  }