app-tutor-ai-consumer 1.47.0 → 1.48.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.
Files changed (26) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/package.json +1 -1
  3. package/src/config/tests/handlers.ts +11 -1
  4. package/src/modules/conversation/constants.ts +6 -0
  5. package/src/modules/conversation/events.ts +27 -0
  6. package/src/modules/conversation/hooks/update-conversation-title/index.ts +2 -0
  7. package/src/modules/conversation/hooks/update-conversation-title/types.ts +5 -0
  8. package/src/modules/conversation/hooks/update-conversation-title/update-conversation-title.spec.tsx +34 -0
  9. package/src/modules/conversation/hooks/update-conversation-title/update-conversation-title.tsx +18 -0
  10. package/src/modules/conversation/index.ts +1 -0
  11. package/src/modules/conversation/service.ts +20 -0
  12. package/src/modules/conversation/types.ts +5 -0
  13. package/src/modules/messages/components/messages-container/messages-container.tsx +12 -10
  14. package/src/modules/messages/components/messages-list/messages-list-empty-state.tsx +12 -0
  15. package/src/modules/messages/components/messages-list/messages-list.tsx +12 -1
  16. package/src/modules/messages/constants.ts +1 -0
  17. package/src/modules/messages/hooks/use-send-first-message/index.ts +1 -0
  18. package/src/modules/messages/hooks/use-send-first-message/use-send-first-message.spec.tsx +142 -0
  19. package/src/modules/messages/hooks/use-send-first-message/use-send-first-message.tsx +48 -0
  20. package/src/modules/messages/store/messages-count.atom.ts +12 -0
  21. package/src/modules/messages/utils/excerpt-message/excerpt-message.spec.ts +77 -0
  22. package/src/modules/messages/utils/excerpt-message/excerpt-message.ts +11 -0
  23. package/src/modules/messages/utils/excerpt-message/index.ts +1 -0
  24. package/src/modules/messages/utils/index.ts +1 -0
  25. package/src/modules/widget/components/chat-page/chat-page.tsx +4 -3
  26. package/src/modules/widget/components/header/widget-header.tsx +4 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # [1.48.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.47.0...v1.48.0) (2026-01-05)
2
+
3
+ ### Features
4
+
5
+ - add new conversation handling ([e075045](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/e075045f8f0aa5833c2b11d0cd4ef1ff4bef5bfd))
6
+
1
7
  # [1.47.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.46.2...v1.47.0) (2026-01-02)
2
8
 
3
9
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "app-tutor-ai-consumer",
3
- "version": "1.47.0",
3
+ "version": "1.48.0",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "dev": "rspack serve --env=development --config config/rspack/rspack.config.js",
@@ -1,5 +1,6 @@
1
1
  import { http, HttpResponse } from 'msw'
2
2
 
3
+ import { ConversationEndpoints } from '@/src/modules/conversation/constants'
3
4
  import { MessagesEndpoints, MSG_MAX_COUNT } from '@/src/modules/messages'
4
5
  import SignedFilesUrlsResponseBuilder from '@/src/modules/messages/__tests__/files-signed-urls.builder'
5
6
  import IMessageWithSenderDataMock from '@/src/modules/messages/__tests__/imessage-with-sender-data.mock'
@@ -28,5 +29,14 @@ export const handlers = [
28
29
  return HttpResponse.json(
29
30
  new IMessageWithSenderDataMock().getMany(isNaN(limit) ? MSG_MAX_COUNT : limit)
30
31
  )
31
- })
32
+ }),
33
+ http.patch(
34
+ ConversationEndpoints.updateTitle({
35
+ productId: ':productId' as unknown as number,
36
+ conversationId: ':conversationId'
37
+ }),
38
+ () => {
39
+ return HttpResponse.json({ ok: true })
40
+ }
41
+ )
32
42
  ]
@@ -0,0 +1,6 @@
1
+ import type { ConversationUpdateRequestProps } from './types'
2
+
3
+ export const ConversationEndpoints = {
4
+ updateTitle: ({ productId, conversationId }: Omit<ConversationUpdateRequestProps, 'title'>) =>
5
+ `${process.env.API_HOTMART_TUTOR}/api/v1/chat/product/${productId}/buyer/chat/${conversationId}`
6
+ }
@@ -0,0 +1,27 @@
1
+ import type { ICustomEvent } from '@/src/types'
2
+
3
+ import type { UpdateConversationTitleParams } from './hooks/update-conversation-title'
4
+
5
+ export const ConversationEventTypes = {
6
+ UPDATE_CONVERSATION_TITLE: 'tutor-app-widget:update-conversation-title'
7
+ } as const
8
+
9
+ export const ConversationEvents = {
10
+ [ConversationEventTypes.UPDATE_CONVERSATION_TITLE]: {
11
+ name: ConversationEventTypes.UPDATE_CONVERSATION_TITLE,
12
+ handler(callback) {
13
+ window.addEventListener(ConversationEventTypes.UPDATE_CONVERSATION_TITLE, callback)
14
+
15
+ return () => {
16
+ window.removeEventListener(ConversationEventTypes.UPDATE_CONVERSATION_TITLE, callback)
17
+ }
18
+ },
19
+ dispatch(payload: UpdateConversationTitleParams) {
20
+ window.dispatchEvent(
21
+ new CustomEvent(ConversationEventTypes.UPDATE_CONVERSATION_TITLE, {
22
+ detail: payload
23
+ })
24
+ )
25
+ }
26
+ } as ICustomEvent<typeof ConversationEventTypes>
27
+ } as const
@@ -0,0 +1,2 @@
1
+ export * from './types'
2
+ export { default as useUpdateConversationTitle } from './update-conversation-title'
@@ -0,0 +1,5 @@
1
+ export type UpdateConversationTitleParams = {
2
+ conversationId: string
3
+ productId: number
4
+ subject: string
5
+ }
@@ -0,0 +1,34 @@
1
+ import { chance, renderHook, waitFor } from '@/src/config/tests'
2
+ import { ConversationEvents, ConversationEventTypes } from '../../events'
3
+
4
+ import useUpdateConversationTitle from './update-conversation-title'
5
+
6
+ describe('useUpdateConversationTitle', () => {
7
+ const payload = {
8
+ conversationId: chance.guid({ version: 4 }),
9
+ productId: chance.integer({ min: 1, max: 100 }),
10
+ subject: chance.sentence({ words: 5 })
11
+ }
12
+
13
+ const customRenderHook = () => renderHook(useUpdateConversationTitle)
14
+
15
+ it('should update the conversation title without errors', () => {
16
+ const { result } = customRenderHook()
17
+
18
+ expect(result.current).toBeDefined()
19
+ })
20
+
21
+ it('should dispatch event when update is done successfully', async () => {
22
+ const evt = ConversationEvents[ConversationEventTypes.UPDATE_CONVERSATION_TITLE]
23
+
24
+ vi.spyOn(evt, 'dispatch')
25
+
26
+ const { result } = customRenderHook()
27
+
28
+ await waitFor(async () => {
29
+ await result.current.mutateAsync(payload)
30
+ })
31
+
32
+ expect(evt.dispatch).toHaveBeenCalledExactlyOnceWith(payload)
33
+ })
34
+ })
@@ -0,0 +1,18 @@
1
+ import { useMutation } from '@tanstack/react-query'
2
+
3
+ import { ConversationService } from '../..'
4
+ import { ConversationEvents, ConversationEventTypes } from '../../events'
5
+
6
+ import type { UpdateConversationTitleParams } from './types'
7
+
8
+ function useUpdateConversationTitle() {
9
+ return useMutation({
10
+ mutationFn: async ({ conversationId, productId, subject }: UpdateConversationTitleParams) =>
11
+ ConversationService.updateTitle({ conversationId, productId, title: subject }),
12
+ onSuccess: (_, variables) => {
13
+ ConversationEvents[ConversationEventTypes.UPDATE_CONVERSATION_TITLE].dispatch(variables)
14
+ }
15
+ })
16
+ }
17
+
18
+ export default useUpdateConversationTitle
@@ -0,0 +1 @@
1
+ export { default as ConversationService } from './service'
@@ -0,0 +1,20 @@
1
+ import { api } from '@/src/config/request'
2
+
3
+ import { ConversationEndpoints } from './constants'
4
+ import type { ConversationUpdateRequestProps } from './types'
5
+
6
+ class ConversationService {
7
+ async updateTitle({ productId, conversationId, title }: ConversationUpdateRequestProps) {
8
+ const { data } = await api.patch<object>(
9
+ ConversationEndpoints.updateTitle({
10
+ productId,
11
+ conversationId
12
+ }),
13
+ { subject: title }
14
+ )
15
+
16
+ return data
17
+ }
18
+ }
19
+
20
+ export default new ConversationService()
@@ -0,0 +1,5 @@
1
+ export type ConversationUpdateRequestProps = {
2
+ productId: number
3
+ conversationId: string
4
+ title: string
5
+ }
@@ -64,16 +64,18 @@ const MessagesContainer = forwardRef<HTMLDivElement, MessagesContainerProps>(
64
64
  <div
65
65
  ref={scrollerRef}
66
66
  className='flex h-full flex-col gap-2 overflow-auto max-md:p-[1.125rem] md:p-5'>
67
- <div className='mb-auto flex-1 self-center'>
68
- <Button
69
- className='max-w-max rounded-full border border-neutral-300 bg-neutral-200 px-2 py-1 text-xs/normal tracking-wide text-neutral-900 hover:text-neutral-900 focus:text-neutral-900 active:text-neutral-900'
70
- onClick={handleClickShowMore}
71
- loading={loading}
72
- show={showButton}>
73
- <Icon name='arrow-up' className='h-4 w-3' aria-hidden />
74
- <span className='text-nowrap'>{t('general.buttons.show_more')}</span>
75
- </Button>
76
- </div>
67
+ {showButton && (
68
+ <div className='mb-auto flex-1 self-center'>
69
+ <Button
70
+ className='max-w-max rounded-full border border-neutral-300 bg-neutral-200 px-2 py-1 text-xs/normal tracking-wide text-neutral-900 hover:text-neutral-900 focus:text-neutral-900 active:text-neutral-900'
71
+ onClick={handleClickShowMore}
72
+ loading={loading}
73
+ show={showButton}>
74
+ <Icon name='arrow-up' className='h-4 w-3' aria-hidden />
75
+ <span className='text-nowrap'>{t('general.buttons.show_more')}</span>
76
+ </Button>
77
+ </div>
78
+ )}
77
79
  {children}
78
80
 
79
81
  {error?.show &&
@@ -0,0 +1,12 @@
1
+ import { useIsAgentParentAtomValue } from '@/src/modules/widget'
2
+ import { WidgetStarterPageContent } from '@/src/modules/widget/components/starter-page/starter-page-content'
3
+
4
+ function MessagesListEmptyState() {
5
+ const isAgentMode = useIsAgentParentAtomValue()
6
+
7
+ if (!isAgentMode) return null
8
+
9
+ return <WidgetStarterPageContent />
10
+ }
11
+
12
+ export default MessagesListEmptyState
@@ -1,8 +1,19 @@
1
+ import { useEffect } from 'react'
2
+
1
3
  import type { ParsedMessage } from '@/src/modules/messages'
2
4
  import { MessageItem } from '@/src/modules/messages/components'
5
+ import { useMessagesCountAtom } from '../../store/messages-count.atom'
6
+
7
+ import MessagesListEmptyState from './messages-list-empty-state'
3
8
 
4
9
  function MessagesList({ messagesMap }: { messagesMap: Map<string, ParsedMessage[]> }) {
5
- if (!(messagesMap.size > 0)) return null
10
+ const [, setMessagesCount] = useMessagesCountAtom()
11
+
12
+ useEffect(() => {
13
+ setMessagesCount(messagesMap?.size ?? 0)
14
+ }, [messagesMap, setMessagesCount])
15
+
16
+ if (!(messagesMap.size > 0)) return <MessagesListEmptyState />
6
17
 
7
18
  return Array.from(messagesMap).map(([, messages], i) => (
8
19
  <div key={i} className='flex flex-1 flex-col justify-center gap-6'>
@@ -1,5 +1,6 @@
1
1
  export const MSG_MAX_COUNT = 20
2
2
  export const MSG_MAX_PAGES = 20
3
+ export const EXCERPT_SIZE = 47
3
4
 
4
5
  export const MessagesEndpoints = {
5
6
  getAll: (conversationId: string) =>
@@ -0,0 +1 @@
1
+ export { default as useSendFirstMessage } from './use-send-first-message'
@@ -0,0 +1,142 @@
1
+ import { renderHook } from '@/src/config/tests'
2
+ import { useUpdateConversationTitle } from '@/src/modules/conversation/hooks/update-conversation-title'
3
+ import { useWidgetSettingsAtomValue } from '@/src/modules/widget/store'
4
+ import { useMessagesCountAtomValue } from '../../store/messages-count.atom'
5
+ import { useSendTextMessage } from '../use-send-text-message'
6
+
7
+ import useSendFirstMessage from './use-send-first-message'
8
+
9
+ vi.mock('@/src/modules/conversation/hooks/update-conversation-title', () => ({
10
+ useUpdateConversationTitle: vi.fn()
11
+ }))
12
+
13
+ vi.mock('@/src/modules/widget/store', () => ({
14
+ useWidgetSettingsAtomValue: vi.fn()
15
+ }))
16
+
17
+ vi.mock('../../store/messages-count.atom', () => ({
18
+ useMessagesCountAtomValue: vi.fn()
19
+ }))
20
+
21
+ vi.mock('../use-send-text-message', () => ({
22
+ useSendTextMessage: vi.fn()
23
+ }))
24
+
25
+ describe('useSendFirstMessage', () => {
26
+ const mockSendMessage = vi.fn()
27
+ const mockUpdateTitle = vi.fn()
28
+
29
+ beforeEach(() => {
30
+ vi.clearAllMocks()
31
+
32
+ vi.mocked(useSendTextMessage).mockReturnValue({
33
+ mutateAsync: mockSendMessage,
34
+ isPending: false,
35
+ error: null
36
+ } as never)
37
+
38
+ vi.mocked(useUpdateConversationTitle).mockReturnValue({
39
+ mutateAsync: mockUpdateTitle,
40
+ isPending: false,
41
+ error: null
42
+ } as never)
43
+
44
+ vi.mocked(useWidgetSettingsAtomValue).mockReturnValue({
45
+ conversationId: null,
46
+ productId: 'prod-456'
47
+ } as never)
48
+
49
+ vi.mocked(useMessagesCountAtomValue).mockReturnValue(0)
50
+ })
51
+
52
+ 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
+ } as never)
58
+ const mockMessage = { id: 'msg-1', content: 'Hello' }
59
+ mockSendMessage.mockResolvedValue(mockMessage)
60
+ mockUpdateTitle.mockResolvedValue({})
61
+
62
+ const { result } = renderHook(() => useSendFirstMessage())
63
+
64
+ await result.current.sendFirstMessage('Hello world')
65
+
66
+ expect(mockSendMessage).toHaveBeenCalledWith('Hello world', undefined)
67
+ expect(mockUpdateTitle).toHaveBeenCalledWith({
68
+ conversationId: 'conv-123',
69
+ productId: 'prod-456',
70
+ subject: 'Hello world'
71
+ })
72
+ })
73
+ })
74
+
75
+ describe('when not first message', () => {
76
+ it('should only send message without updating title', async () => {
77
+ vi.mocked(useMessagesCountAtomValue).mockReturnValue(5)
78
+
79
+ const mockMessage = { id: 'msg-1', content: 'Hello' }
80
+ mockSendMessage.mockResolvedValue(mockMessage)
81
+
82
+ const { result } = renderHook(() => useSendFirstMessage())
83
+
84
+ await result.current.sendFirstMessage('Hello world')
85
+
86
+ expect(mockSendMessage).toHaveBeenCalledWith('Hello world', undefined)
87
+ expect(mockUpdateTitle).not.toHaveBeenCalled()
88
+ })
89
+ })
90
+
91
+ describe('when missing settings', () => {
92
+ it('should not update title if conversationId is missing', async () => {
93
+ const mockMessage = { id: 'msg-1', content: 'Hello' }
94
+ mockSendMessage.mockResolvedValue(mockMessage)
95
+
96
+ const { result } = renderHook(() => useSendFirstMessage())
97
+
98
+ await result.current.sendFirstMessage('Hello world')
99
+
100
+ expect(mockSendMessage).toHaveBeenCalled()
101
+ expect(mockUpdateTitle).not.toHaveBeenCalled()
102
+ })
103
+ })
104
+
105
+ describe('error handling', () => {
106
+ it('should throw error when send message fails', async () => {
107
+ vi.spyOn(console, 'error').mockImplementationOnce(() => {})
108
+ const error = new Error('Send failed')
109
+ mockSendMessage.mockRejectedValue(error)
110
+
111
+ const { result } = renderHook(() => useSendFirstMessage())
112
+
113
+ await expect(result.current.sendFirstMessage('Hello')).rejects.toThrow('Send failed')
114
+ })
115
+ })
116
+
117
+ describe('loading states', () => {
118
+ it('should return loading state when sending message', () => {
119
+ vi.mocked(useSendTextMessage).mockReturnValue({
120
+ mutateAsync: mockSendMessage,
121
+ isPending: true,
122
+ error: null
123
+ } as never)
124
+
125
+ const { result } = renderHook(() => useSendFirstMessage())
126
+
127
+ expect(result.current.isLoading).toBe(true)
128
+ })
129
+
130
+ it('should return loading state when updating title', () => {
131
+ vi.mocked(useUpdateConversationTitle).mockReturnValue({
132
+ mutateAsync: mockUpdateTitle,
133
+ isPending: true,
134
+ error: null
135
+ } as never)
136
+
137
+ const { result } = renderHook(() => useSendFirstMessage())
138
+
139
+ expect(result.current.isLoading).toBe(true)
140
+ })
141
+ })
142
+ })
@@ -0,0 +1,48 @@
1
+ import { useCallback } from 'react'
2
+ import type { Message } from '@hotmart-org-ca/sparkie/dist/MessageService'
3
+ import type { MutateOptions } from '@tanstack/react-query'
4
+
5
+ import { useUpdateConversationTitle } from '@/src/modules/conversation/hooks/update-conversation-title'
6
+ import { useWidgetSettingsAtomValue } from '@/src/modules/widget/store'
7
+ import { useMessagesCountAtomValue } from '../../store/messages-count.atom'
8
+ import { excerptMessage } from '../../utils'
9
+ import { useSendTextMessage } from '../use-send-text-message'
10
+
11
+ function useSendFirstMessage() {
12
+ const messagesCount = useMessagesCountAtomValue()
13
+ const sendMessageMutation = useSendTextMessage()
14
+ const updateTitleMutation = useUpdateConversationTitle()
15
+ const settings = useWidgetSettingsAtomValue()
16
+
17
+ const sendFirstMessage = useCallback(
18
+ async (message: string, options?: MutateOptions<Message, Error, string, void>) => {
19
+ const isFirstMessage = messagesCount === 0
20
+
21
+ try {
22
+ const messageResult = await sendMessageMutation.mutateAsync(message, options)
23
+
24
+ if (isFirstMessage && settings?.conversationId && settings?.productId) {
25
+ await updateTitleMutation.mutateAsync({
26
+ conversationId: settings.conversationId,
27
+ productId: settings.productId,
28
+ subject: excerptMessage({ message })
29
+ })
30
+ }
31
+
32
+ return messageResult
33
+ } catch (error) {
34
+ console.error('Failed to send first message:', error)
35
+ throw error
36
+ }
37
+ },
38
+ [messagesCount, sendMessageMutation, updateTitleMutation, settings]
39
+ )
40
+
41
+ return {
42
+ sendFirstMessage,
43
+ isLoading: sendMessageMutation.isPending || updateTitleMutation.isPending,
44
+ error: sendMessageMutation.error || updateTitleMutation.error
45
+ }
46
+ }
47
+
48
+ export default useSendFirstMessage
@@ -0,0 +1,12 @@
1
+ import { atom, useAtom, useAtomValue } from 'jotai'
2
+
3
+ const messagesCountAtom = atom(0)
4
+
5
+ const setMessagesCountAtom = atom(
6
+ (get) => get(messagesCountAtom),
7
+ (_, set, count: number) => set(messagesCountAtom, count)
8
+ )
9
+
10
+ export const useMessagesCountAtom = () => useAtom(setMessagesCountAtom)
11
+
12
+ export const useMessagesCountAtomValue = () => useAtomValue(messagesCountAtom)
@@ -0,0 +1,77 @@
1
+ import { EXCERPT_SIZE } from '../../constants'
2
+
3
+ import { excerptMessage } from './excerpt-message'
4
+
5
+ describe('excerptMessage', () => {
6
+ describe('when message is shorter than excerpt size', () => {
7
+ it('should return the original message without ellipsis', () => {
8
+ const message = 'Short message'
9
+ const result = excerptMessage({ message })
10
+
11
+ expect(result).toBe('Short message')
12
+ })
13
+ })
14
+
15
+ describe('when message is longer than excerpt size', () => {
16
+ it('should return truncated message with ellipsis', () => {
17
+ const message = 'This is a very long message that exceeds the default excerpt size limit'
18
+ const result = excerptMessage({ message })
19
+
20
+ expect(result).toBe('This is a very long message that exceeds the de...')
21
+ expect(result.length).toBe(EXCERPT_SIZE + 3)
22
+ })
23
+ })
24
+
25
+ describe('when message is exactly the excerpt size', () => {
26
+ it('should return the original message without ellipsis', () => {
27
+ const message = 'A'.repeat(EXCERPT_SIZE)
28
+ const result = excerptMessage({ message })
29
+
30
+ expect(result).toBe(message)
31
+ expect(result).not.toContain('...')
32
+ })
33
+ })
34
+
35
+ describe('when custom excerpt size is provided', () => {
36
+ it('should use custom size for truncation', () => {
37
+ const message = 'This is a test message'
38
+ const customSize = 10
39
+ const result = excerptMessage({ message, excerptSize: customSize })
40
+
41
+ expect(result).toBe(message.slice(0, customSize).trim() + '...')
42
+ expect(result.length).toBe(customSize + 2)
43
+ })
44
+ })
45
+
46
+ describe('when message is empty', () => {
47
+ it('should return empty string', () => {
48
+ const result = excerptMessage({ message: '' })
49
+
50
+ expect(result).toBe('')
51
+ })
52
+ })
53
+
54
+ describe('when message has leading/trailing whitespace', () => {
55
+ it('should trim whitespace from truncated message', () => {
56
+ const customSize = 15
57
+ const message = ' This is a message with spaces '
58
+ const result = excerptMessage({ message, excerptSize: customSize })
59
+
60
+ expect(result).toBe(message.slice(0, customSize).trim() + '...')
61
+ })
62
+ })
63
+
64
+ describe('when message is null or undefined', () => {
65
+ it('should handle null message', () => {
66
+ const result = excerptMessage({ message: null as never })
67
+
68
+ expect(result).toBe(null)
69
+ })
70
+
71
+ it('should handle undefined message', () => {
72
+ const result = excerptMessage({ message: undefined as never })
73
+
74
+ expect(result).toBe(undefined)
75
+ })
76
+ })
77
+ })
@@ -0,0 +1,11 @@
1
+ import { EXCERPT_SIZE } from '../../constants'
2
+
3
+ type ExcerptMessageProps = {
4
+ message: string
5
+ excerptSize?: number
6
+ }
7
+
8
+ export const excerptMessage = ({ message, excerptSize = EXCERPT_SIZE }: ExcerptMessageProps) =>
9
+ Number(message?.length) > 0
10
+ ? message.slice(0, excerptSize).trim() + (message.length > excerptSize ? '...' : '')
11
+ : message
@@ -0,0 +1 @@
1
+ export * from './excerpt-message'
@@ -1,3 +1,4 @@
1
+ export * from './excerpt-message'
1
2
  export * from './has-to-update-cursor'
2
3
  export * from './messages-parser'
3
4
  export * from './set-messages-cache'
@@ -7,6 +7,7 @@ import { ChatInput, MessagesList, useChatInputValueAtom } from '@/src/modules/me
7
7
  import { ChatFileUploaderWrapper } from '@/src/modules/messages/components/chat-file-uploader-wrapper'
8
8
  import { MessagesContainer } from '@/src/modules/messages/components/messages-container'
9
9
  import { getAllMessagesQuery, useSendTextMessage } from '@/src/modules/messages/hooks'
10
+ import { useSendFirstMessage } from '@/src/modules/messages/hooks/use-send-first-message'
10
11
  import { useMessagesMaxCount } from '@/src/modules/messages/store'
11
12
  import { useAttachedFileAtom } from '@/src/modules/messages/store/attached-file.atom'
12
13
  import { useGetProfile } from '@/src/modules/profile'
@@ -30,7 +31,7 @@ function ChatPage() {
30
31
  const [attachedFileAtom] = useAttachedFileAtom()
31
32
  const profileQuery = useGetProfile()
32
33
  const widgetTabs = useWidgetTabsValueAtom()
33
- const sendTextMessageMutation = useSendTextMessage()
34
+ const sendTextMessageMutation = useSendFirstMessage()
34
35
  const sendInitialMessageMutation = useSendTextMessage()
35
36
  const limit = useMessagesMaxCount()
36
37
  const [value, setValue] = useChatInputValueAtom()
@@ -59,7 +60,7 @@ function ChatPage() {
59
60
 
60
61
  if (!isTextEmpty(text)) return
61
62
 
62
- sendTextMessageMutation.mutate(text, {
63
+ void sendTextMessageMutation.sendFirstMessage(text, {
63
64
  onSuccess() {
64
65
  if (chatInputRef.current?.value) chatInputRef.current.value = ''
65
66
  setValue('')
@@ -117,7 +118,7 @@ function ChatPage() {
117
118
  name='new-chat-msg-input'
118
119
  ref={chatInputRef}
119
120
  onSend={widgetTabs.currentTab === 'chat' ? handleSendMessage : undefined}
120
- loading={sendTextMessageMutation.isPending}
121
+ loading={sendTextMessageMutation?.isLoading}
121
122
  inputDisabled={messagesQuery?.isLoading}
122
123
  buttonDisabled={
123
124
  widgetLoading ||
@@ -13,7 +13,7 @@ import { AiIconCircle, Button, Icon, Tooltip } from '@/src/lib/components'
13
13
  import { useMediaQuery } from '@/src/lib/hooks'
14
14
  import { TutorWidgetEvents } from '../../events'
15
15
  import { useMembershipColor } from '../../hooks'
16
- import { useWidgetGoBackTabAtom, useWidgetTabsAtom } from '../../store'
16
+ import { useIsAgentParentAtomValue, useWidgetGoBackTabAtom, useWidgetTabsAtom } from '../../store'
17
17
 
18
18
  import type { WidgetHeaderProps } from './types'
19
19
 
@@ -93,6 +93,7 @@ function WidgetHeader({
93
93
  const [, goBack] = useWidgetGoBackTabAtom()
94
94
  const membershipColor = useMembershipColor()
95
95
  const name = tutorName ?? t('general.name')
96
+ const isAgentMode = useIsAgentParentAtomValue()
96
97
 
97
98
  const handleHideWidget = () => {
98
99
  TutorWidgetEvents['c3po-app-widget-hide'].dispatch()
@@ -111,6 +112,8 @@ function WidgetHeader({
111
112
  DataHubService.sendEvent({ schema: new ClickTutorBackSchema() })
112
113
  }
113
114
 
115
+ if (isAgentMode) return null
116
+
114
117
  return (
115
118
  <div
116
119
  id='tutor-ai-consumer-widget-header'