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.
- package/CHANGELOG.md +33 -0
- package/package.json +1 -1
- package/src/config/datahub/actions.ts +3 -2
- package/src/config/datahub/constants.ts +1 -0
- package/src/config/datahub/entities.ts +2 -1
- package/src/config/datahub/schemas/constants.ts +2 -1
- package/src/config/datahub/schemas/tutor/__tests__/{try-send-tutor-message.spec.ts → try-send-message.spec.ts} +11 -6
- package/src/config/datahub/schemas/tutor/__tests__/{view-tutor-answer-message.spec.ts → view-answer-message.spec.ts} +9 -4
- package/src/config/datahub/schemas/tutor/__tests__/view-chat.spec.ts +38 -0
- package/src/config/datahub/schemas/tutor/index.ts +2 -2
- package/src/config/datahub/schemas/tutor/{try-send-tutor-message.ts → try-send-message.ts} +22 -12
- package/src/config/datahub/schemas/tutor/{view-tutor-answer-message.ts → view-answer-message.ts} +20 -7
- package/src/config/datahub/schemas/tutor/view-chat.ts +56 -0
- package/src/config/datahub/types.ts +5 -0
- package/src/config/styles/global.css +3 -0
- package/src/config/tests/handlers.ts +3 -3
- package/src/modules/messages/__tests__/{signed-urls.builder.ts → files-signed-urls.builder.ts} +3 -3
- package/src/modules/messages/components/chat-file-preview/chat-file-preview.tsx +0 -1
- package/src/modules/messages/components/chat-file-preview/components/document-preview/document-preview.tsx +4 -3
- package/src/modules/messages/components/chat-file-preview/components/document-preview/types.ts +0 -3
- package/src/modules/messages/components/chat-file-preview/utils.ts +17 -0
- package/src/modules/messages/components/chat-file-uploader/chat-file-uploader.builder.ts +4 -7
- package/src/modules/messages/components/chat-file-uploader/chat-file-uploader.spec.tsx +0 -20
- package/src/modules/messages/components/chat-file-uploader/chat-file-uploader.tsx +16 -14
- package/src/modules/messages/components/chat-file-uploader-wrapper/chat-file-uploader-wrapper.tsx +34 -0
- package/src/modules/messages/components/chat-file-uploader-wrapper/index.ts +1 -0
- package/src/modules/messages/components/message-item/message-item.tsx +10 -0
- package/src/modules/messages/constants.ts +1 -1
- package/src/{lib → modules/messages}/hooks/use-chat-file-upload/constants.ts +2 -0
- package/src/modules/messages/hooks/use-chat-file-upload/use-chat-file-upload.spec.ts +19 -0
- package/src/modules/messages/hooks/use-chat-file-upload/use-chat-file-upload.ts +102 -0
- package/src/modules/messages/hooks/use-get-files-signed-urls/index.ts +1 -0
- 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
- package/src/modules/messages/hooks/use-get-files-signed-urls/use-get-files-signed-urls.ts +25 -0
- package/src/modules/messages/hooks/use-send-text-message/use-send-text-message.spec.tsx +1 -1
- package/src/modules/messages/hooks/use-send-text-message/use-send-text-message.tsx +31 -19
- package/src/modules/messages/hooks/use-subscribe-message-received-event/use-subscribe-message-received-event.tsx +9 -5
- package/src/modules/messages/hooks/use-upload-file/index.ts +1 -0
- package/src/modules/messages/hooks/use-upload-file/use-upload-file.ts +15 -0
- package/src/modules/messages/service.direct.ts +27 -10
- package/src/modules/messages/store/attached-file.atom.ts +7 -0
- package/src/modules/messages/types.ts +30 -2
- package/src/modules/widget/components/chat-page/chat-page.tsx +26 -11
- package/src/modules/widget/hooks/index.ts +1 -0
- package/src/modules/widget/hooks/use-send-view-chat-event/index.ts +1 -0
- package/src/modules/widget/hooks/use-send-view-chat-event/use-send-view-chat-event.tsx +28 -0
- package/src/lib/hooks/use-chat-file-upload/types.ts +0 -14
- package/src/lib/hooks/use-chat-file-upload/use-chat-file-upload.spec.ts +0 -59
- package/src/lib/hooks/use-chat-file-upload/use-chat-file-upload.ts +0 -28
- package/src/modules/messages/components/chat-file-uploader/types.ts +0 -4
- package/src/modules/messages/hooks/use-get-signed-urls/index.ts +0 -1
- package/src/modules/messages/hooks/use-get-signed-urls/use-get-signed-urls.tsx +0 -38
- /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 {
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
11
|
+
const acceptImageTypes = FILE_TYPES.image.join()
|
|
12
|
+
const acceptFileTypes = [FILE_TYPES.document, FILE_TYPES.spreadsheet, FILE_TYPES.pdf].flat().join()
|
|
10
13
|
|
|
11
|
-
|
|
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
|
-
|
|
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
|
|
60
|
+
disabled={disabled}
|
|
59
61
|
items={[
|
|
60
62
|
{
|
|
61
63
|
label: 'general.buttons.file',
|
package/src/modules/messages/components/chat-file-uploader-wrapper/chat-file-uploader-wrapper.tsx
ADDED
|
@@ -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
|
-
|
|
9
|
+
getFilesSignedUrls: () => `${process.env.API_HOTMART_TUTOR}/api/v1/files/signed-urls`
|
|
10
10
|
}
|
|
@@ -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 {
|
|
4
|
-
import {
|
|
3
|
+
import type { UseGetFilesSignedUrlsProps } from './use-get-files-signed-urls'
|
|
4
|
+
import { useGetFilesSignedUrls } from './use-get-files-signed-urls'
|
|
5
5
|
|
|
6
|
-
describe('
|
|
7
|
-
const props:
|
|
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(() =>
|
|
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
|
+
}
|
|
@@ -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 {
|
|
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
|
-
|
|
137
|
+
onSettled(data, error) {
|
|
129
138
|
DataHubService.sendEvent({
|
|
130
|
-
schema: new
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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 {
|
|
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
|
|
71
|
-
correlationId: data
|
|
72
|
-
messageId: data
|
|
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
|
-
|
|
10
|
-
|
|
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
|
|
44
|
+
async getFilesSignedUrls({
|
|
43
45
|
productId,
|
|
44
46
|
chatId,
|
|
45
47
|
fileExtension,
|
|
46
48
|
fileNameWithExtension
|
|
47
|
-
}:
|
|
48
|
-
const { data } = await api.post<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
@@ -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
|
|
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
|
|
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
|