app-tutor-ai-consumer 1.38.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 +27 -0
- package/package.json +1 -1
- 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.tsx +20 -2
- 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 +23 -9
- 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
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
# [1.39.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.38.0...v1.39.0) (2025-11-11)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- changing file extension ([60d395b](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/60d395b9a34a49c1e7935dec0593ee75d23be8fe))
|
|
6
|
+
- code improvement ([e539ba3](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/e539ba3882c06dba661a7056476497fb929cd5be))
|
|
7
|
+
- code improvement ([b17de7b](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/b17de7b39bc5bfc13f7dbffbb322934e4c8d1973))
|
|
8
|
+
- code improvements ([ecf54e6](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/ecf54e6be8676890b67f8b4cfecb21dbc6f2b981))
|
|
9
|
+
- error i18n messages ([823ea93](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/823ea93bb38d3777fda686a47e60ab8c94738dfd))
|
|
10
|
+
- fixing productId parameter ([45a1d8c](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/45a1d8c277d6e43f698f90b32f53a87ade1b234d))
|
|
11
|
+
- fixing test ([6309da8](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/6309da8a97cce96543488f8689f1851677bf6a0d))
|
|
12
|
+
- fixing toast type ([71a23e8](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/71a23e8c1d1bfd48d2581d0425a525fb57fe7e38))
|
|
13
|
+
- integrate ChatFileUploaderWrapper with ChatFilePreview ([1b8cf1a](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/1b8cf1a5492b7b57f7303ec4172cc555c6f7fbbc))
|
|
14
|
+
- linking the ChatFileUploaderWrapper display to the showFileUpload parameter ([7826d85](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/7826d8590d304dc30c016ef4e0849ba74e8a6160))
|
|
15
|
+
- preview file on chat history ([aa09c31](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/aa09c317b2299cd9ced9cbe65822838778aabdae))
|
|
16
|
+
- refactoring ([890838b](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/890838b2f79b31797fa593eb5f2218b0d8c5115c))
|
|
17
|
+
- refactoring ([665ff74](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/665ff741df81e2b4dcecedbddc0e0912dac40f89))
|
|
18
|
+
- removing temporary component ([e4bfbf0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/e4bfbf08b44a20d744b7f0e7c0ab18e2ff5b2519))
|
|
19
|
+
- removing tests ([fa4b836](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/fa4b83631f0aa7542e12f39cbe06c17007a6cc18))
|
|
20
|
+
- removing tooltip ([8e4450e](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/8e4450e87d89cca21855e3270e046817187defda))
|
|
21
|
+
- sending file data to conversation api ([40fe90e](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/40fe90edc4c5f67cd3f527cc33738e1dea0839bc))
|
|
22
|
+
- tooltip refactoring ([df0a66a](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/df0a66ac6d469aea33dbede90fbccce24f6e631a))
|
|
23
|
+
- typing fix ([4ab00a0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/4ab00a082dbb29d5d848350231f38f79d44d1684))
|
|
24
|
+
- undoing tooltip refactoring ([26c1072](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/26c10722dab2d5669ff972cfbe1aefaeb05b8850))
|
|
25
|
+
- update file size ([bf92561](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/bf92561945896a01dc5032e1b9c2dbcc78110d7d))
|
|
26
|
+
- wip ([3160ab4](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/3160ab4c6d2ea5417213fbbe8ce031b97de853c4))
|
|
27
|
+
|
|
1
28
|
# [1.38.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.37.0...v1.38.0) (2025-11-10)
|
|
2
29
|
|
|
3
30
|
### Features
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { http, HttpResponse } from 'msw'
|
|
2
2
|
|
|
3
3
|
import { MessagesEndpoints, MSG_MAX_COUNT } from '@/src/modules/messages'
|
|
4
|
+
import SignedFilesUrlsResponseBuilder from '@/src/modules/messages/__tests__/files-signed-urls.builder'
|
|
4
5
|
import IMessageWithSenderDataMock from '@/src/modules/messages/__tests__/imessage-with-sender-data.mock'
|
|
5
|
-
import SignedUrlsResponseBuilder from '@/src/modules/messages/__tests__/signed-urls.builder'
|
|
6
6
|
import { ProfileEndpoints } from '@/src/modules/profile'
|
|
7
7
|
import ProfileAPIPropsBuilder from '@/src/modules/profile/__tests__/profile-api-props.builder'
|
|
8
8
|
|
|
@@ -19,8 +19,8 @@ export const handlers = [
|
|
|
19
19
|
http.all(ProfileEndpoints.getProfile(), () => {
|
|
20
20
|
return HttpResponse.json(new ProfileAPIPropsBuilder())
|
|
21
21
|
}),
|
|
22
|
-
http.all(MessagesEndpoints.
|
|
23
|
-
return HttpResponse.json(new
|
|
22
|
+
http.all(MessagesEndpoints.getFilesSignedUrls(), () => {
|
|
23
|
+
return HttpResponse.json(new SignedFilesUrlsResponseBuilder())
|
|
24
24
|
}),
|
|
25
25
|
http.all(MessagesEndpoints.getAll(':conversationId'), ({ request }) => {
|
|
26
26
|
const limit = Number(new URL(request.url)?.searchParams?.get?.('limit'))
|
package/src/modules/messages/__tests__/{signed-urls.builder.ts → files-signed-urls.builder.ts}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { chance } from '@/src/config/tests'
|
|
2
|
-
import type {
|
|
2
|
+
import type { IGetFilesSignedUrlsResponse } from '../types'
|
|
3
3
|
|
|
4
|
-
class
|
|
4
|
+
class FilesSignedUrlsResponseBuilder implements IGetFilesSignedUrlsResponse {
|
|
5
5
|
uploadUrl: string
|
|
6
6
|
futureDownloadUrl: string
|
|
7
7
|
imagePreviewUrl: string
|
|
@@ -39,4 +39,4 @@ class SignedUrlsResponseBuilder implements IGetSignedUrlsResponse {
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
export default
|
|
42
|
+
export default FilesSignedUrlsResponseBuilder
|
|
@@ -5,7 +5,7 @@ import { Icon, Spinner } from '@/src/lib/components'
|
|
|
5
5
|
import { useMediaQuery } from '@/src/lib/hooks'
|
|
6
6
|
import { useWidgetSettingsAtom } from '@/src/modules/widget/store'
|
|
7
7
|
import { CHAT_FILE_PREVIEW_CONFIG } from '../../constants'
|
|
8
|
-
import { formatFileSize } from '../../utils'
|
|
8
|
+
import { formatFileSize, getFileTypeFromFileName } from '../../utils'
|
|
9
9
|
import { CloseButton } from '../close-button'
|
|
10
10
|
|
|
11
11
|
import type { DocumentPreviewProps } from './types'
|
|
@@ -13,7 +13,6 @@ import type { DocumentPreviewProps } from './types'
|
|
|
13
13
|
export default function DocumentPreview({
|
|
14
14
|
name,
|
|
15
15
|
size,
|
|
16
|
-
type,
|
|
17
16
|
isLoading,
|
|
18
17
|
showCloseButton,
|
|
19
18
|
onClose
|
|
@@ -23,7 +22,9 @@ export default function DocumentPreview({
|
|
|
23
22
|
const isMobile = useMediaQuery({ maxSize: 'md' })
|
|
24
23
|
const formattedSize = formatFileSize(size)
|
|
25
24
|
const isDarkMode = settings?.config?.theme === 'dark'
|
|
26
|
-
|
|
25
|
+
|
|
26
|
+
const fileType = getFileTypeFromFileName(name)
|
|
27
|
+
const config = CHAT_FILE_PREVIEW_CONFIG[fileType]
|
|
27
28
|
|
|
28
29
|
return (
|
|
29
30
|
<div
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { FILE_TYPES } from '../../hooks/use-chat-file-upload/constants'
|
|
2
|
+
|
|
1
3
|
import { FILE_SIZE_UNITS } from './constants'
|
|
4
|
+
import type { FilePreviewProps } from './types'
|
|
2
5
|
|
|
3
6
|
export function formatFileSize(size: number): string {
|
|
4
7
|
if (size < FILE_SIZE_UNITS.KB) {
|
|
@@ -11,3 +14,17 @@ export function formatFileSize(size: number): string {
|
|
|
11
14
|
|
|
12
15
|
return `${Math.round((size / FILE_SIZE_UNITS.MB) * 10) / 10}MB`
|
|
13
16
|
}
|
|
17
|
+
|
|
18
|
+
export function getFileTypeFromFileName(
|
|
19
|
+
fileName: string
|
|
20
|
+
): Exclude<FilePreviewProps['type'], 'image'> {
|
|
21
|
+
const extension = `.${fileName.split('.').pop()?.toLowerCase() || ''}`
|
|
22
|
+
|
|
23
|
+
for (const [category, extensions] of Object.entries(FILE_TYPES)) {
|
|
24
|
+
if (category !== 'image' && (extensions as readonly string[]).includes(extension)) {
|
|
25
|
+
return category as Exclude<FilePreviewProps['type'], 'image'>
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return 'document'
|
|
30
|
+
}
|
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
import type { ChatFileUploaderProps } from './
|
|
1
|
+
import type { ChatFileUploaderProps } from './chat-file-uploader'
|
|
2
2
|
|
|
3
3
|
export class ChatFileUploaderBuilder {
|
|
4
|
-
private props: ChatFileUploaderProps = {
|
|
4
|
+
private props: ChatFileUploaderProps = {
|
|
5
|
+
onUploadFile: () => {}
|
|
6
|
+
}
|
|
5
7
|
|
|
6
8
|
withDisabled(disabled: boolean): ChatFileUploaderBuilder {
|
|
7
9
|
this.props.disabled = disabled
|
|
8
10
|
return this
|
|
9
11
|
}
|
|
10
12
|
|
|
11
|
-
withLoading(loading: boolean): ChatFileUploaderBuilder {
|
|
12
|
-
this.props.loading = loading
|
|
13
|
-
return this
|
|
14
|
-
}
|
|
15
|
-
|
|
16
13
|
build(): ChatFileUploaderProps {
|
|
17
14
|
return this.props
|
|
18
15
|
}
|
|
@@ -24,26 +24,6 @@ describe('ChatFileUploader', () => {
|
|
|
24
24
|
expect(button).toBeDisabled()
|
|
25
25
|
})
|
|
26
26
|
|
|
27
|
-
it('should be disabled when loading prop is true', () => {
|
|
28
|
-
const props = new ChatFileUploaderBuilder().withLoading(true).build()
|
|
29
|
-
renderComponent(props)
|
|
30
|
-
|
|
31
|
-
const button = screen.getByRole('button', {
|
|
32
|
-
name: /general.buttons.open_options/i
|
|
33
|
-
})
|
|
34
|
-
expect(button).toBeDisabled()
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
it('should be enabled when both disabled and loading are false', () => {
|
|
38
|
-
const props = new ChatFileUploaderBuilder().withDisabled(false).withLoading(false).build()
|
|
39
|
-
renderComponent(props)
|
|
40
|
-
|
|
41
|
-
const button = screen.getByRole('button', {
|
|
42
|
-
name: /general.buttons.open_options/i
|
|
43
|
-
})
|
|
44
|
-
expect(button).toBeEnabled()
|
|
45
|
-
})
|
|
46
|
-
|
|
47
27
|
it('should trigger file input when clicking file option', async () => {
|
|
48
28
|
const { user } = renderComponent()
|
|
49
29
|
|
|
@@ -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
|
+
}
|
|
@@ -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,6 +132,7 @@ function useSendTextMessage() {
|
|
|
115
132
|
},
|
|
116
133
|
onSuccess(data) {
|
|
117
134
|
setMessagesCache({ queryKey: messagesQueryConfig.queryKey, queryClient, data })()
|
|
135
|
+
setAttachedFileAtom(null)
|
|
118
136
|
},
|
|
119
137
|
onSettled(data, error) {
|
|
120
138
|
DataHubService.sendEvent({
|
|
@@ -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
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
|
1
|
+
import { Fragment, useCallback, useEffect, useMemo, useRef } from 'react'
|
|
2
2
|
import { useInfiniteQuery } from '@tanstack/react-query'
|
|
3
3
|
|
|
4
4
|
import { useMediaQuery } from '@/src/lib/hooks'
|
|
5
5
|
import { isTextEmpty } from '@/src/lib/utils/is-text-empty'
|
|
6
6
|
import { ChatInput, MessagesList, useChatInputValueAtom } from '@/src/modules/messages/components'
|
|
7
|
+
import { ChatFileUploaderWrapper } from '@/src/modules/messages/components/chat-file-uploader-wrapper'
|
|
7
8
|
import { MessagesContainer } from '@/src/modules/messages/components/messages-container'
|
|
8
9
|
import { getAllMessagesQuery, useSendTextMessage } from '@/src/modules/messages/hooks'
|
|
9
10
|
import { useMessagesMaxCount } from '@/src/modules/messages/store'
|
|
11
|
+
import { useAttachedFileAtom } from '@/src/modules/messages/store/attached-file.atom'
|
|
10
12
|
import { useGetProfile } from '@/src/modules/profile'
|
|
11
13
|
import { TutorWidgetEvents } from '../../events'
|
|
12
14
|
import { useSendViewChatEvent } from '../../hooks'
|
|
@@ -25,6 +27,7 @@ function ChatPage() {
|
|
|
25
27
|
const chatInputRef = useRef<HTMLTextAreaElement>(null)
|
|
26
28
|
const scrollerRef = useRef<HTMLDivElement>(null)
|
|
27
29
|
const settings = useWidgetSettingsAtomValue()
|
|
30
|
+
const [attachedFileAtom] = useAttachedFileAtom()
|
|
28
31
|
const profileQuery = useGetProfile()
|
|
29
32
|
const widgetTabs = useWidgetTabsValueAtom()
|
|
30
33
|
const sendTextMessageMutation = useSendTextMessage()
|
|
@@ -101,18 +104,29 @@ function ChatPage() {
|
|
|
101
104
|
}
|
|
102
105
|
}, [messagesQuery.isError, setWidgetLoading])
|
|
103
106
|
|
|
107
|
+
const ChatInputWrapper = settings?.config?.metadata?.showFileUpload
|
|
108
|
+
? ChatFileUploaderWrapper
|
|
109
|
+
: Fragment
|
|
110
|
+
|
|
104
111
|
return (
|
|
105
112
|
<PageLayout
|
|
106
113
|
asideChild={
|
|
107
114
|
<>
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
<ChatInputWrapper>
|
|
116
|
+
<ChatInput
|
|
117
|
+
name='new-chat-msg-input'
|
|
118
|
+
ref={chatInputRef}
|
|
119
|
+
onSend={widgetTabs.currentTab === 'chat' ? handleSendMessage : undefined}
|
|
120
|
+
loading={sendTextMessageMutation.isPending}
|
|
121
|
+
inputDisabled={messagesQuery?.isLoading}
|
|
122
|
+
buttonDisabled={
|
|
123
|
+
widgetLoading ||
|
|
124
|
+
messagesQuery?.isLoading ||
|
|
125
|
+
!value.trim() ||
|
|
126
|
+
(Boolean(attachedFileAtom) && !attachedFileAtom?.downloadUrl)
|
|
127
|
+
}
|
|
128
|
+
/>
|
|
129
|
+
</ChatInputWrapper>
|
|
116
130
|
|
|
117
131
|
<div className='mx-auto w-fit'>
|
|
118
132
|
<AIDisclaimer />
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export type FileType = 'document' | 'image'
|
|
2
|
-
|
|
3
|
-
export type SelectedFile = {
|
|
4
|
-
file: File
|
|
5
|
-
type: FileType
|
|
6
|
-
name: string
|
|
7
|
-
size: number
|
|
8
|
-
previewUrl?: string
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export type UseFileUploadReturn = {
|
|
12
|
-
selectedFile: SelectedFile | null
|
|
13
|
-
handleSelectFile: (file: File, type: FileType) => void
|
|
14
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { act, renderHook } from '@/src/config/tests'
|
|
2
|
-
|
|
3
|
-
import { useChatFileUpload } from './use-chat-file-upload'
|
|
4
|
-
|
|
5
|
-
describe('useChatFileUpload', () => {
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
global.URL.createObjectURL = vi.fn(() => 'blob:mock-url')
|
|
8
|
-
})
|
|
9
|
-
|
|
10
|
-
afterEach(() => {
|
|
11
|
-
vi.clearAllMocks()
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
it('should initialize with default values', () => {
|
|
15
|
-
const { result } = renderHook(() => useChatFileUpload())
|
|
16
|
-
|
|
17
|
-
expect(result.current.selectedFile).toBeNull()
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
describe('when selecting a document file', () => {
|
|
21
|
-
it('should store the file with correct properties', () => {
|
|
22
|
-
const { result } = renderHook(() => useChatFileUpload())
|
|
23
|
-
|
|
24
|
-
const mockFile = new File(['content'], 'document.pdf', { type: 'application/pdf' })
|
|
25
|
-
|
|
26
|
-
act(() => {
|
|
27
|
-
result.current.handleSelectFile(mockFile, 'document')
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
expect(result.current.selectedFile).toEqual({
|
|
31
|
-
file: mockFile,
|
|
32
|
-
type: 'document',
|
|
33
|
-
name: 'document.pdf',
|
|
34
|
-
size: mockFile.size
|
|
35
|
-
})
|
|
36
|
-
})
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
describe('when selecting an image file', () => {
|
|
40
|
-
it('should create a preview URL', () => {
|
|
41
|
-
const { result } = renderHook(() => useChatFileUpload())
|
|
42
|
-
|
|
43
|
-
const mockImage = new File(['image'], 'photo.jpg', { type: 'image/jpeg' })
|
|
44
|
-
|
|
45
|
-
act(() => {
|
|
46
|
-
result.current.handleSelectFile(mockImage, 'image')
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
expect(result.current.selectedFile).toEqual({
|
|
50
|
-
file: mockImage,
|
|
51
|
-
type: 'image',
|
|
52
|
-
name: 'photo.jpg',
|
|
53
|
-
size: mockImage.size,
|
|
54
|
-
previewUrl: 'blob:mock-url'
|
|
55
|
-
})
|
|
56
|
-
expect(global.URL.createObjectURL).toHaveBeenCalledWith(mockImage)
|
|
57
|
-
})
|
|
58
|
-
})
|
|
59
|
-
})
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
|
-
|
|
3
|
-
import { FILE_TYPES_KEYS } from './constants'
|
|
4
|
-
import type { FileType, SelectedFile, UseFileUploadReturn } from './types'
|
|
5
|
-
|
|
6
|
-
export function useChatFileUpload(): UseFileUploadReturn {
|
|
7
|
-
const [selectedFile, setSelectedFile] = useState<SelectedFile | null>(null)
|
|
8
|
-
|
|
9
|
-
const handleSelectFile = (file: File, type: FileType) => {
|
|
10
|
-
const newFile: SelectedFile = {
|
|
11
|
-
file,
|
|
12
|
-
type,
|
|
13
|
-
name: file.name,
|
|
14
|
-
size: file.size
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (type === FILE_TYPES_KEYS.IMAGE) {
|
|
18
|
-
newFile.previewUrl = URL.createObjectURL(file)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
setSelectedFile(newFile)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
selectedFile,
|
|
26
|
-
handleSelectFile
|
|
27
|
-
}
|
|
28
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './use-get-signed-urls'
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { useQuery } from '@tanstack/react-query'
|
|
2
|
-
|
|
3
|
-
import { MessagesEndpoints } from '../../constants'
|
|
4
|
-
import DirectMessagesService from '../../service.direct'
|
|
5
|
-
|
|
6
|
-
export type UseGetSignedUrlsProps = {
|
|
7
|
-
chatId: string
|
|
8
|
-
fileExtension: string
|
|
9
|
-
fileNameWithExtension: string
|
|
10
|
-
productId: number
|
|
11
|
-
enabled?: boolean
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function useGetSignedUrls({
|
|
15
|
-
chatId,
|
|
16
|
-
fileExtension,
|
|
17
|
-
fileNameWithExtension,
|
|
18
|
-
productId,
|
|
19
|
-
enabled
|
|
20
|
-
}: UseGetSignedUrlsProps) {
|
|
21
|
-
return useQuery({
|
|
22
|
-
queryKey: [
|
|
23
|
-
MessagesEndpoints.getSignedUrls(),
|
|
24
|
-
chatId,
|
|
25
|
-
fileExtension,
|
|
26
|
-
fileNameWithExtension,
|
|
27
|
-
productId
|
|
28
|
-
],
|
|
29
|
-
queryFn: () =>
|
|
30
|
-
DirectMessagesService.getSignedUrls({
|
|
31
|
-
chatId,
|
|
32
|
-
fileExtension,
|
|
33
|
-
fileNameWithExtension,
|
|
34
|
-
productId
|
|
35
|
-
}),
|
|
36
|
-
enabled
|
|
37
|
-
})
|
|
38
|
-
}
|
|
File without changes
|