app-tutor-ai-consumer 1.37.0 → 1.39.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 (53) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/package.json +1 -1
  3. package/src/config/datahub/actions.ts +3 -2
  4. package/src/config/datahub/constants.ts +1 -0
  5. package/src/config/datahub/entities.ts +2 -1
  6. package/src/config/datahub/schemas/constants.ts +2 -1
  7. package/src/config/datahub/schemas/tutor/__tests__/{try-send-tutor-message.spec.ts → try-send-message.spec.ts} +11 -6
  8. package/src/config/datahub/schemas/tutor/__tests__/{view-tutor-answer-message.spec.ts → view-answer-message.spec.ts} +9 -4
  9. package/src/config/datahub/schemas/tutor/__tests__/view-chat.spec.ts +38 -0
  10. package/src/config/datahub/schemas/tutor/index.ts +2 -2
  11. package/src/config/datahub/schemas/tutor/{try-send-tutor-message.ts → try-send-message.ts} +22 -12
  12. package/src/config/datahub/schemas/tutor/{view-tutor-answer-message.ts → view-answer-message.ts} +20 -7
  13. package/src/config/datahub/schemas/tutor/view-chat.ts +56 -0
  14. package/src/config/datahub/types.ts +5 -0
  15. package/src/config/styles/global.css +3 -0
  16. package/src/config/tests/handlers.ts +3 -3
  17. package/src/modules/messages/__tests__/{signed-urls.builder.ts → files-signed-urls.builder.ts} +3 -3
  18. package/src/modules/messages/components/chat-file-preview/chat-file-preview.tsx +0 -1
  19. package/src/modules/messages/components/chat-file-preview/components/document-preview/document-preview.tsx +4 -3
  20. package/src/modules/messages/components/chat-file-preview/components/document-preview/types.ts +0 -3
  21. package/src/modules/messages/components/chat-file-preview/utils.ts +17 -0
  22. package/src/modules/messages/components/chat-file-uploader/chat-file-uploader.builder.ts +4 -7
  23. package/src/modules/messages/components/chat-file-uploader/chat-file-uploader.spec.tsx +0 -20
  24. package/src/modules/messages/components/chat-file-uploader/chat-file-uploader.tsx +16 -14
  25. package/src/modules/messages/components/chat-file-uploader-wrapper/chat-file-uploader-wrapper.tsx +34 -0
  26. package/src/modules/messages/components/chat-file-uploader-wrapper/index.ts +1 -0
  27. package/src/modules/messages/components/message-item/message-item.tsx +10 -0
  28. package/src/modules/messages/constants.ts +1 -1
  29. package/src/{lib → modules/messages}/hooks/use-chat-file-upload/constants.ts +2 -0
  30. package/src/modules/messages/hooks/use-chat-file-upload/use-chat-file-upload.spec.ts +19 -0
  31. package/src/modules/messages/hooks/use-chat-file-upload/use-chat-file-upload.ts +102 -0
  32. package/src/modules/messages/hooks/use-get-files-signed-urls/index.ts +1 -0
  33. package/src/modules/messages/hooks/{use-get-signed-urls/use-get-signed-urls.spec.tsx → use-get-files-signed-urls/use-get-files-signed-urls.spec.ts} +7 -7
  34. package/src/modules/messages/hooks/use-get-files-signed-urls/use-get-files-signed-urls.ts +25 -0
  35. package/src/modules/messages/hooks/use-send-text-message/use-send-text-message.spec.tsx +1 -1
  36. package/src/modules/messages/hooks/use-send-text-message/use-send-text-message.tsx +31 -19
  37. package/src/modules/messages/hooks/use-subscribe-message-received-event/use-subscribe-message-received-event.tsx +9 -5
  38. package/src/modules/messages/hooks/use-upload-file/index.ts +1 -0
  39. package/src/modules/messages/hooks/use-upload-file/use-upload-file.ts +15 -0
  40. package/src/modules/messages/service.direct.ts +27 -10
  41. package/src/modules/messages/store/attached-file.atom.ts +7 -0
  42. package/src/modules/messages/types.ts +30 -2
  43. package/src/modules/widget/components/chat-page/chat-page.tsx +26 -11
  44. package/src/modules/widget/hooks/index.ts +1 -0
  45. package/src/modules/widget/hooks/use-send-view-chat-event/index.ts +1 -0
  46. package/src/modules/widget/hooks/use-send-view-chat-event/use-send-view-chat-event.tsx +28 -0
  47. package/src/lib/hooks/use-chat-file-upload/types.ts +0 -14
  48. package/src/lib/hooks/use-chat-file-upload/use-chat-file-upload.spec.ts +0 -59
  49. package/src/lib/hooks/use-chat-file-upload/use-chat-file-upload.ts +0 -28
  50. package/src/modules/messages/components/chat-file-uploader/types.ts +0 -4
  51. package/src/modules/messages/hooks/use-get-signed-urls/index.ts +0 -1
  52. package/src/modules/messages/hooks/use-get-signed-urls/use-get-signed-urls.tsx +0 -38
  53. /package/src/{lib → modules/messages}/hooks/use-chat-file-upload/index.ts +0 -0
@@ -2,24 +2,25 @@ import { type ChangeEvent, useRef } from 'react'
2
2
  import { useTranslation } from 'react-i18next'
3
3
 
4
4
  import { DropdownActions } from '@/src/lib/components'
5
- import { useChatFileUpload } from '@/src/lib/hooks/use-chat-file-upload'
6
- import { FILE_TYPES, FILE_TYPES_KEYS } from '@/src/lib/hooks/use-chat-file-upload/constants'
7
- import type { FileType } from '@/src/lib/hooks/use-chat-file-upload/types'
5
+ import {
6
+ FILE_TYPES,
7
+ FILE_TYPES_KEYS
8
+ } from '@/src/modules/messages/hooks/use-chat-file-upload/constants'
9
+ import type { FileType } from '../../types'
8
10
 
9
- import type { ChatFileUploaderProps } from './types'
11
+ const acceptImageTypes = FILE_TYPES.image.join()
12
+ const acceptFileTypes = [FILE_TYPES.document, FILE_TYPES.spreadsheet, FILE_TYPES.pdf].flat().join()
10
13
 
11
- function ChatFileUploader({ disabled = false, loading = false }: ChatFileUploaderProps) {
14
+ export type ChatFileUploaderProps = {
15
+ onUploadFile: (file: File, type: FileType) => void
16
+ disabled?: boolean
17
+ }
18
+
19
+ function ChatFileUploader({ onUploadFile, disabled = false }: ChatFileUploaderProps) {
12
20
  const { t } = useTranslation()
13
21
  const fileInputRef = useRef<HTMLInputElement>(null)
14
22
  const imageInputRef = useRef<HTMLInputElement>(null)
15
23
 
16
- const acceptFileTypes = [FILE_TYPES.document, FILE_TYPES.spreadsheet, FILE_TYPES.pdf]
17
- .flat()
18
- .join()
19
- const acceptImageTypes = FILE_TYPES.image.join()
20
-
21
- const { handleSelectFile, selectedFile } = useChatFileUpload()
22
-
23
24
  const triggerFileInput = (type: FileType) => {
24
25
  const inputRef = type === FILE_TYPES_KEYS.DOCUMENT ? fileInputRef : imageInputRef
25
26
  inputRef.current?.click()
@@ -28,7 +29,7 @@ function ChatFileUploader({ disabled = false, loading = false }: ChatFileUploade
28
29
  const handleFileInputChange = (e: ChangeEvent<HTMLInputElement>, type: FileType) => {
29
30
  const file = e.target.files?.[0]
30
31
  if (file) {
31
- handleSelectFile(file, type)
32
+ onUploadFile(file, type)
32
33
  }
33
34
  e.target.value = ''
34
35
  }
@@ -53,9 +54,10 @@ function ChatFileUploader({ disabled = false, loading = false }: ChatFileUploade
53
54
  aria-hidden='true'
54
55
  aria-label={t('general.buttons.image')}
55
56
  />
57
+
56
58
  <DropdownActions
57
59
  triggerIcon='plus'
58
- disabled={disabled || loading || selectedFile !== null}
60
+ disabled={disabled}
59
61
  items={[
60
62
  {
61
63
  label: 'general.buttons.file',
@@ -0,0 +1,34 @@
1
+ import type { PropsWithChildren } from 'react'
2
+
3
+ import { useChatFileUpload } from '@/src/modules/messages/hooks/use-chat-file-upload'
4
+ import { useAttachedFileAtom } from '../../store/attached-file.atom'
5
+ import { ChatFilePreview } from '../chat-file-preview'
6
+ import { ChatFileUploader } from '../chat-file-uploader'
7
+
8
+ function ChatFileUploaderWrapper({ children }: PropsWithChildren) {
9
+ const { handleUploadFile, handleRemoveFile, isUploading } = useChatFileUpload()
10
+ const [attachedFileAtom] = useAttachedFileAtom()
11
+
12
+ return (
13
+ <div className='flex flex-col gap-2'>
14
+ {attachedFileAtom && (
15
+ <ChatFilePreview
16
+ name={attachedFileAtom.name}
17
+ size={attachedFileAtom.size}
18
+ type={attachedFileAtom.type}
19
+ imageUrl={attachedFileAtom.previewUrl}
20
+ isLoading={isUploading}
21
+ onClose={handleRemoveFile}
22
+ showCloseButton
23
+ />
24
+ )}
25
+
26
+ <div className='flex flex-row gap-2'>
27
+ <ChatFileUploader onUploadFile={handleUploadFile} disabled={Boolean(attachedFileAtom)} />
28
+ <div className='flex-1'>{children}</div>
29
+ </div>
30
+ </div>
31
+ )
32
+ }
33
+
34
+ export default ChatFileUploaderWrapper
@@ -0,0 +1 @@
1
+ export { default as ChatFileUploaderWrapper } from './chat-file-uploader-wrapper'
@@ -1,6 +1,8 @@
1
1
  import clsx from 'clsx'
2
2
 
3
3
  import type { ParsedMessage } from '../../types'
4
+ import { ChatFilePreview } from '../chat-file-preview'
5
+ import type { FilePreviewTypes } from '../chat-file-preview/types'
4
6
  import { MessageActions } from '../message-actions'
5
7
  import { MessageContentTypeRenderer } from '../message-content-type-renderer'
6
8
 
@@ -19,6 +21,14 @@ function MessageItem({ message }: { message: ParsedMessage }) {
19
21
  'self-end': messageFromUser
20
22
  }
21
23
  )}>
24
+ {message?.file && message?.metadata && (
25
+ <ChatFilePreview
26
+ name={message.file.name}
27
+ size={message.file.size}
28
+ imageUrl={message.file.imagePreviewUrl || ''}
29
+ type={message.metadata.context?.document_type?.toLocaleLowerCase() as FilePreviewTypes}
30
+ />
31
+ )}
22
32
  <div
23
33
  data-test='messages-item'
24
34
  className={clsx('w-full overflow-x-hidden rounded-lg p-3', {
@@ -6,5 +6,5 @@ export const MessagesEndpoints = {
6
6
  `${process.env.API_CONVERSATION_URL}/v1/conversations/${conversationId}/messages`,
7
7
  create: (conversationId: string) =>
8
8
  `${process.env.API_CONVERSATION_URL}/v1/conversations/${conversationId}/messages`,
9
- getSignedUrls: () => `${process.env.API_HOTMART_TUTOR}/api/v1/files/signed-urls`
9
+ getFilesSignedUrls: () => `${process.env.API_HOTMART_TUTOR}/api/v1/files/signed-urls`
10
10
  }
@@ -9,3 +9,5 @@ export const FILE_TYPES_KEYS = {
9
9
  DOCUMENT: 'document',
10
10
  IMAGE: 'image'
11
11
  } as const
12
+
13
+ export const MAX_FILE_SIZE = 15 * 1024 * 1024
@@ -0,0 +1,19 @@
1
+ import { renderHook, waitFor } from '@/src/config/tests'
2
+
3
+ import { useChatFileUpload } from './use-chat-file-upload'
4
+
5
+ describe('useChatFileUpload', () => {
6
+ beforeEach(() => {
7
+ vi.clearAllMocks()
8
+ })
9
+
10
+ const createHook = () => renderHook(() => useChatFileUpload())
11
+
12
+ it('should return correct values', async () => {
13
+ const { result } = createHook()
14
+
15
+ await waitFor(() => expect(result.current).toBeDefined())
16
+
17
+ expect(result.current.isUploading).toBe(false)
18
+ })
19
+ })
@@ -0,0 +1,102 @@
1
+ import { useState } from 'react'
2
+ import { useTranslation } from 'react-i18next'
3
+
4
+ import { Toast, ToastUtils } from '@/src/lib/utils'
5
+ import { useUploadFile } from '@/src/modules/messages/hooks/use-upload-file'
6
+ import { useWidgetSettingsAtomValue } from '@/src/modules/widget'
7
+ import { formatFileSize } from '../../components/chat-file-preview/utils'
8
+ import { useAttachedFileAtom } from '../../store/attached-file.atom'
9
+ import type { FileType, IAttachedFile } from '../../types'
10
+ import { useGetFilesSignedUrls } from '../use-get-files-signed-urls'
11
+
12
+ import { FILE_TYPES_KEYS, MAX_FILE_SIZE } from './constants'
13
+
14
+ export type UseChatFileUploadReturn = {
15
+ handleUploadFile: (file: File, type: FileType) => void
16
+ handleRemoveFile: () => void
17
+ isUploading: boolean
18
+ }
19
+
20
+ export function useChatFileUpload(): UseChatFileUploadReturn {
21
+ const { t } = useTranslation()
22
+ const [isUploading, setIsUploading] = useState<boolean>(false)
23
+ const settings = useWidgetSettingsAtomValue()
24
+ const [attachedFileAtom, setAttachedFileAtom] = useAttachedFileAtom()
25
+ const uploadFile = useUploadFile()
26
+ const getSignedUrls = useGetFilesSignedUrls({
27
+ chatId: settings?.conversationId || '',
28
+ productId: settings?.config?.metadata?.agentProductId || 0
29
+ })
30
+
31
+ const handleUploadFile = (file: File, type: FileType) => {
32
+ if (file.size > MAX_FILE_SIZE) {
33
+ ToastUtils.dispatch({
34
+ type: Toast.DANGER,
35
+ message: t('send_message.file_upload.error.file_size_exceeded', {
36
+ max_file_size: formatFileSize(MAX_FILE_SIZE)
37
+ })
38
+ })
39
+ return
40
+ }
41
+
42
+ const errorMessageLabel = `send_message.file_upload.error.upload_${type === FILE_TYPES_KEYS.IMAGE ? 'image' : 'file'}`
43
+
44
+ setIsUploading(true)
45
+
46
+ const newFile: IAttachedFile = {
47
+ file,
48
+ type,
49
+ name: file.name,
50
+ size: file.size
51
+ }
52
+
53
+ if (type === FILE_TYPES_KEYS.IMAGE) {
54
+ newFile.previewUrl = URL.createObjectURL(file)
55
+ }
56
+
57
+ setAttachedFileAtom(newFile)
58
+
59
+ getSignedUrls.mutate(
60
+ { fileExtension: file.name.split('.').pop() || '', fileNameWithExtension: file.name },
61
+ {
62
+ onSuccess: (data) => {
63
+ setAttachedFileAtom((prevAttachedFile) => ({
64
+ ...(prevAttachedFile as IAttachedFile),
65
+ downloadUrl: data.futureDownloadUrl,
66
+ imagePreviewUrl: data.imagePreviewUrl
67
+ }))
68
+ uploadFile.mutate(
69
+ { signedUrl: data.uploadUrl, file },
70
+ {
71
+ onSuccess: () => {
72
+ setIsUploading(false)
73
+ },
74
+ onError: () => {
75
+ handleRemoveFile()
76
+ ToastUtils.dispatch({ message: t(errorMessageLabel), type: 'danger' })
77
+ }
78
+ }
79
+ )
80
+ },
81
+ onError: () => {
82
+ handleRemoveFile()
83
+ ToastUtils.dispatch({ message: t(errorMessageLabel), type: 'danger' })
84
+ }
85
+ }
86
+ )
87
+ }
88
+
89
+ const handleRemoveFile = () => {
90
+ if (attachedFileAtom?.previewUrl) {
91
+ URL.revokeObjectURL(attachedFileAtom.previewUrl)
92
+ }
93
+ setAttachedFileAtom(null)
94
+ setIsUploading(false)
95
+ }
96
+
97
+ return {
98
+ handleUploadFile,
99
+ handleRemoveFile,
100
+ isUploading
101
+ }
102
+ }
@@ -0,0 +1 @@
1
+ export * from './use-get-files-signed-urls'
@@ -1,20 +1,20 @@
1
1
  import { renderHook, waitFor } from '@/src/config/tests'
2
2
 
3
- import type { UseGetSignedUrlsProps } from './use-get-signed-urls'
4
- import { useGetSignedUrls } from './use-get-signed-urls'
3
+ import type { UseGetFilesSignedUrlsProps } from './use-get-files-signed-urls'
4
+ import { useGetFilesSignedUrls } from './use-get-files-signed-urls'
5
5
 
6
- describe('useGetSignedUrls', () => {
7
- const props: UseGetSignedUrlsProps = {
6
+ describe('useGetFilesSignedUrls', () => {
7
+ const props: UseGetFilesSignedUrlsProps = {
8
8
  chatId: 'chat-id',
9
- fileExtension: 'pdf',
10
- fileNameWithExtension: 'file.pdf',
11
9
  productId: 123
12
10
  }
13
- const render = () => renderHook(() => useGetSignedUrls(props))
11
+ const render = () => renderHook(() => useGetFilesSignedUrls(props))
14
12
 
15
13
  it('should get signed upload url', async () => {
16
14
  const { result } = render()
17
15
 
16
+ result.current.mutate({ fileExtension: 'png', fileNameWithExtension: 'image.png' })
17
+
18
18
  await waitFor(() => expect(result.current.isSuccess).toBeTruthy())
19
19
 
20
20
  expect(result.current.data).toMatchObject({
@@ -0,0 +1,25 @@
1
+ import { useMutation } from '@tanstack/react-query'
2
+
3
+ import DirectMessagesService from '../../service.direct'
4
+
5
+ export type UseGetFilesSignedUrlsProps = {
6
+ chatId: string
7
+ productId: number
8
+ }
9
+
10
+ export type UseGetFilesSignedUrlsMutationProps = {
11
+ fileExtension: string
12
+ fileNameWithExtension: string
13
+ }
14
+
15
+ export function useGetFilesSignedUrls({ chatId, productId }: UseGetFilesSignedUrlsProps) {
16
+ return useMutation({
17
+ mutationFn: ({ fileExtension, fileNameWithExtension }: UseGetFilesSignedUrlsMutationProps) =>
18
+ DirectMessagesService.getFilesSignedUrls({
19
+ chatId,
20
+ fileExtension,
21
+ fileNameWithExtension,
22
+ productId
23
+ })
24
+ })
25
+ }
@@ -76,7 +76,7 @@ describe('useSendTextMessage', () => {
76
76
 
77
77
  expect(DataHubService.sendEvent).toHaveBeenCalledWith({
78
78
  schema: expect.objectContaining({
79
- questionId: mockResponse.id,
79
+ messageId: mockResponse.id,
80
80
  questionType: 'TEXT'
81
81
  })
82
82
  })
@@ -3,8 +3,8 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'
3
3
  import { UAParser } from 'ua-parser-js'
4
4
  import { v4 } from 'uuid'
5
5
 
6
- import { ComponentSource, DataHubService } from '@/src/config/datahub'
7
- import { TrySendTutorMessageSchema } from '@/src/config/datahub/schemas/tutor'
6
+ import { AgentType, ComponentSource, DataHubService } from '@/src/config/datahub'
7
+ import { TrySendMessageSchema } from '@/src/config/datahub/schemas/tutor'
8
8
  import { APP_VERSION, HttpCodes, PLATFORM } from '@/src/lib/utils'
9
9
  import { DirectMessagesService as MessagesService } from '@/src/modules/messages'
10
10
  import { useGetProfile } from '@/src/modules/profile'
@@ -15,6 +15,7 @@ import {
15
15
  } from '@/src/modules/widget'
16
16
  import { MessagesEvents } from '../../events'
17
17
  import { useMessagesMaxCount } from '../../store'
18
+ import { useAttachedFileAtom } from '../../store/attached-file.atom'
18
19
  import { setMessagesCache } from '../../utils'
19
20
  import { getAllMessagesQuery } from '../use-infinite-get-messages'
20
21
 
@@ -25,6 +26,7 @@ function useSendTextMessage() {
25
26
  const queryClient = useQueryClient()
26
27
  const limit = useMessagesMaxCount()
27
28
  const isAgentMode = useIsAgentParentAtomValue()
29
+ const [attachedFileAtom, setAttachedFileAtom] = useAttachedFileAtom()
28
30
 
29
31
  const userId = useMemo(() => profileQuery.data?.userId?.toString(), [profileQuery.data?.userId])
30
32
  const profileId = useMemo(() => profileQuery.data?.id?.toString(), [profileQuery.data?.id])
@@ -60,7 +62,16 @@ function useSendTextMessage() {
60
62
  message: {
61
63
  content: {
62
64
  type: 'text/plain',
63
- text: processedMessage
65
+ text: processedMessage,
66
+ ...(attachedFileAtom
67
+ ? {
68
+ file: {
69
+ name: attachedFileAtom.name,
70
+ size: attachedFileAtom.size,
71
+ imagePreviewUrl: attachedFileAtom.imagePreviewUrl
72
+ }
73
+ }
74
+ : {})
64
75
  },
65
76
  metadata: {
66
77
  author: 'user',
@@ -88,7 +99,13 @@ function useSendTextMessage() {
88
99
  source: settings.config?.metadata?.source,
89
100
  prompt: settings.config?.metadata?.promptId,
90
101
  membershipSlug: settings.membershipSlug,
91
- membershipId: settings.membershipId
102
+ membershipId: settings.membershipId,
103
+ ...(attachedFileAtom
104
+ ? {
105
+ document_type: attachedFileAtom.type.toUpperCase(),
106
+ document_url: attachedFileAtom.downloadUrl
107
+ }
108
+ : {})
92
109
  },
93
110
  externalId: v4(),
94
111
  namespace: settings.namespace,
@@ -115,25 +132,20 @@ function useSendTextMessage() {
115
132
  },
116
133
  onSuccess(data) {
117
134
  setMessagesCache({ queryKey: messagesQueryConfig.queryKey, queryClient, data })()
118
-
119
- DataHubService.sendEvent({
120
- schema: new TrySendTutorMessageSchema({
121
- questionId: data.id,
122
- componentSource: isAgentMode
123
- ? ComponentSource.PRODUCT_AGENT
124
- : ComponentSource.HOTMART_TUTOR
125
- })
126
- })
135
+ setAttachedFileAtom(null)
127
136
  },
128
- onError(error) {
137
+ onSettled(data, error) {
129
138
  DataHubService.sendEvent({
130
- schema: new TrySendTutorMessageSchema({
131
- statusCode: HttpCodes.BAD_REQUEST,
132
- result: 'FAILURE',
133
- failureDescription: error.message,
139
+ schema: new TrySendMessageSchema({
140
+ messageId: data?.id,
141
+ statusCode: error ? HttpCodes.BAD_REQUEST : HttpCodes.OK,
142
+ result: error ? 'FAILURE' : 'SUCCESSFUL',
143
+ failureDescription: error?.message,
134
144
  componentSource: isAgentMode
135
145
  ? ComponentSource.PRODUCT_AGENT
136
- : ComponentSource.HOTMART_TUTOR
146
+ : ComponentSource.HOTMART_TUTOR,
147
+ agent: isAgentMode ? AgentType.PRODUCT_AGENT : AgentType.HOTMART_TUTOR,
148
+ conversationId: settings?.conversationId
137
149
  })
138
150
  })
139
151
  }
@@ -4,7 +4,8 @@ import { useQueryClient } from '@tanstack/react-query'
4
4
  import { produce } from 'immer'
5
5
 
6
6
  import { ComponentSource, DataHubService } from '@/src/config/datahub'
7
- import { ViewTutorAnswerMessageSchema } from '@/src/config/datahub/schemas/tutor'
7
+ import { ViewAnswerMessageSchema } from '@/src/config/datahub/schemas/tutor'
8
+ import { AgentType } from '@/src/config/datahub/types'
8
9
  import { useUpdateCursor } from '@/src/modules/cursor/hooks'
9
10
  import { useGetProfile } from '@/src/modules/profile'
10
11
  import { SparkieService } from '@/src/modules/sparkie'
@@ -67,12 +68,14 @@ const useSubscribeMessageReceivedEvent = () => {
67
68
 
68
69
  if (!isMine) {
69
70
  DataHubService.sendEvent({
70
- schema: new ViewTutorAnswerMessageSchema({
71
- correlationId: data.metadata.correlationId,
72
- messageId: data.id,
71
+ schema: new ViewAnswerMessageSchema({
72
+ correlationId: data?.metadata?.correlationId,
73
+ messageId: data?.id,
73
74
  componentSource: isAgentMode
74
75
  ? ComponentSource.PRODUCT_AGENT
75
- : ComponentSource.HOTMART_TUTOR
76
+ : ComponentSource.HOTMART_TUTOR,
77
+ agent: isAgentMode ? AgentType.PRODUCT_AGENT : AgentType.HOTMART_TUTOR,
78
+ conversationId: settings?.conversationId
76
79
  })
77
80
  })
78
81
 
@@ -91,6 +94,7 @@ const useSubscribeMessageReceivedEvent = () => {
91
94
  profileId,
92
95
  queryClient,
93
96
  setWidgetLoading,
97
+ settings?.conversationId,
94
98
  useUpdateCursorMutation
95
99
  ]
96
100
  )
@@ -0,0 +1 @@
1
+ export * from './use-upload-file'
@@ -0,0 +1,15 @@
1
+ import { useMutation } from '@tanstack/react-query'
2
+
3
+ import DirectMessagesService from '../../service.direct'
4
+
5
+ export type UseUploadFileMutationProps = {
6
+ signedUrl: string
7
+ file: File
8
+ }
9
+
10
+ export function useUploadFile() {
11
+ return useMutation({
12
+ mutationFn: ({ signedUrl, file }: UseUploadFileMutationProps) =>
13
+ DirectMessagesService.uploadFile({ signedUrl, file })
14
+ })
15
+ }
@@ -1,4 +1,5 @@
1
1
  import type { Message } from '@hotmart-org-ca/sparkie/dist/MessageService'
2
+ import axios from 'axios'
2
3
 
3
4
  import { api } from '@/src/config/request'
4
5
 
@@ -6,9 +7,10 @@ import { MessagesEndpoints } from './constants'
6
7
  import type {
7
8
  DirectMessagesServiceProps,
8
9
  FetchMessagesResponse,
9
- IGetSignedUrlsPayload,
10
- IGetSignedUrlsResponse,
11
- IMessageWithSenderData
10
+ IGetFilesSignedUrlsPayload,
11
+ IGetFilesSignedUrlsResponse,
12
+ IMessageWithSenderData,
13
+ IUploadFilePayload
12
14
  } from './types'
13
15
 
14
16
  class DirectMessagesService {
@@ -39,17 +41,32 @@ class DirectMessagesService {
39
41
  return data
40
42
  }
41
43
 
42
- async getSignedUrls({
44
+ async getFilesSignedUrls({
43
45
  productId,
44
46
  chatId,
45
47
  fileExtension,
46
48
  fileNameWithExtension
47
- }: IGetSignedUrlsPayload): Promise<IGetSignedUrlsResponse> {
48
- const { data } = await api.post<IGetSignedUrlsResponse>(MessagesEndpoints.getSignedUrls(), {
49
- productId,
50
- chatId,
51
- fileExtension,
52
- fileNameWithExtension
49
+ }: IGetFilesSignedUrlsPayload): Promise<IGetFilesSignedUrlsResponse> {
50
+ const { data } = await api.post<IGetFilesSignedUrlsResponse>(
51
+ MessagesEndpoints.getFilesSignedUrls(),
52
+ {
53
+ productId,
54
+ chatId,
55
+ fileExtension,
56
+ fileNameWithExtension
57
+ }
58
+ )
59
+
60
+ return data
61
+ }
62
+
63
+ async uploadFile({ signedUrl, file }: IUploadFilePayload) {
64
+ // New Axios instance without Bearer Token
65
+ const api = axios.create()
66
+ const { data } = await api.put<IGetFilesSignedUrlsResponse>(signedUrl, file, {
67
+ headers: {
68
+ 'Content-Type': file.type
69
+ }
53
70
  })
54
71
 
55
72
  return data
@@ -0,0 +1,7 @@
1
+ import { atom, useAtom } from 'jotai'
2
+
3
+ import type { IAttachedFile } from '../types'
4
+
5
+ const attachedFileAtom = atom<IAttachedFile | null>(null)
6
+
7
+ export const useAttachedFileAtom = () => useAtom(attachedFileAtom)
@@ -10,6 +10,17 @@ export type IMessage = SparkieMsg & {
10
10
  sessionId: string
11
11
  externalId: string
12
12
  correlationId: string
13
+ context?: {
14
+ ['document_type']: string
15
+ ['document_url']: string
16
+ }
17
+ }
18
+ content: MessageContent & {
19
+ file?: {
20
+ name: string
21
+ size: number
22
+ imagePreviewUrl?: string
23
+ }
13
24
  }
14
25
  sending?: boolean // indicates when the current message is being sent
15
26
  }
@@ -19,13 +30,18 @@ export type IGetMessagesPayload = {
19
30
  before?: number
20
31
  }
21
32
 
22
- export type IGetSignedUrlsPayload = {
33
+ export type IGetFilesSignedUrlsPayload = {
23
34
  chatId: string
24
35
  fileExtension: string
25
36
  fileNameWithExtension: string
26
37
  productId: number
27
38
  }
28
39
 
40
+ export type IUploadFilePayload = {
41
+ signedUrl: string
42
+ file: File
43
+ }
44
+
29
45
  export type ISendTextMessagePayload = {
30
46
  conversationId: string
31
47
  content: {
@@ -70,13 +86,25 @@ export type MessageParserArgs = {
70
86
  profileId: string
71
87
  }
72
88
 
73
- export type IGetSignedUrlsResponse = {
89
+ export type IGetFilesSignedUrlsResponse = {
74
90
  uploadUrl: string
75
91
  futureDownloadUrl: string
76
92
  imagePreviewUrl: string
77
93
  fileNameWithExtension: string
78
94
  }
79
95
 
96
+ export type FileType = 'document' | 'image'
97
+
98
+ export type IAttachedFile = {
99
+ file: File
100
+ type: FileType
101
+ name: string
102
+ size: number
103
+ previewUrl?: string
104
+ downloadUrl?: string
105
+ imagePreviewUrl?: string
106
+ }
107
+
80
108
  export type ParsedMessage = {
81
109
  from: string
82
110
  id: string