app-tutor-ai-consumer 1.33.1 → 1.35.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 +37 -0
- package/config/vitest/__mocks__/sparkie.tsx +2 -2
- package/package.json +2 -2
- package/src/@types/index.d.ts +3 -2
- package/src/bootstrap.ts +40 -0
- package/src/config/tanstack/query-provider.tsx +15 -4
- package/src/config/tests/handlers.ts +5 -4
- package/src/config/theme/init-theme.ts +11 -5
- package/src/index.backup.tsx +61 -0
- package/src/index.tsx +80 -17
- package/src/lib/components/dropdown-actions/dropdown-actions.tsx +87 -0
- package/src/lib/components/dropdown-actions/dropdownActions.builder.ts +58 -0
- package/src/lib/components/dropdown-actions/dropdownActions.spec.tsx +76 -0
- package/src/lib/components/dropdown-actions/index.ts +1 -0
- package/src/lib/components/dropdown-actions/types.ts +16 -0
- package/src/lib/components/errors/generic/generic-error.tsx +19 -10
- package/src/lib/components/icons/document.svg +3 -0
- package/src/lib/components/icons/file.svg +3 -0
- package/src/lib/components/icons/icon-names.d.ts +7 -0
- package/src/lib/components/icons/image.svg +3 -0
- package/src/lib/components/icons/pdf.svg +3 -0
- package/src/lib/components/icons/plus.svg +3 -0
- package/src/lib/components/icons/retry.svg +3 -0
- package/src/lib/components/icons/spreadsheet.svg +3 -0
- package/src/lib/components/index.ts +1 -0
- package/src/lib/components/markdownrenderer/markdownrenderer.tsx +1 -3
- package/src/lib/hooks/index.ts +1 -0
- package/src/lib/hooks/use-chat-file-upload/constants.ts +11 -0
- package/src/lib/hooks/use-chat-file-upload/index.ts +1 -0
- package/src/lib/hooks/use-chat-file-upload/types.ts +14 -0
- package/src/lib/hooks/use-chat-file-upload/use-chat-file-upload.spec.ts +59 -0
- package/src/lib/hooks/use-chat-file-upload/use-chat-file-upload.ts +28 -0
- package/src/lib/hooks/use-click-outside/index.ts +1 -0
- package/src/lib/hooks/use-click-outside/use-click-outside.tsx +23 -0
- package/src/lib/hooks/use-click-outside/useClickOutside.spec.ts +102 -0
- package/src/lib/utils/index.ts +1 -0
- package/src/lib/utils/is-theme-dark.ts +21 -0
- package/src/main/hooks/use-initial-store/index.ts +1 -0
- package/src/main/hooks/use-initial-store/use-initial-store.tsx +64 -0
- package/src/main/hooks/use-initial-tab/index.ts +1 -0
- package/src/main/hooks/use-initial-tab/use-initial-tab.tsx +48 -0
- package/src/main/index.ts +1 -0
- package/src/main/main-content.tsx +14 -0
- package/src/main/main-wrapper.tsx +16 -0
- package/src/main/main.spec.tsx +5 -3
- package/src/main/main.tsx +7 -16
- package/src/main/types.ts +5 -0
- package/src/modules/global-providers/global-providers.tsx +2 -21
- package/src/modules/messages/__tests__/imessage-with-sender-data.builder.ts +1 -1
- package/src/modules/messages/__tests__/signed-urls.builder.ts +42 -0
- package/src/modules/messages/components/chat-file-preview/chat-file-preview.builder.ts +121 -0
- package/src/modules/messages/components/chat-file-preview/chat-file-preview.spec.tsx +107 -0
- package/src/modules/messages/components/chat-file-preview/chat-file-preview.tsx +45 -0
- package/src/modules/messages/components/chat-file-preview/components/close-button/close-button.tsx +31 -0
- package/src/modules/messages/components/chat-file-preview/components/close-button/index.ts +1 -0
- package/src/modules/messages/components/chat-file-preview/components/close-button/types.ts +4 -0
- package/src/modules/messages/components/chat-file-preview/components/document-preview/document-preview.tsx +63 -0
- package/src/modules/messages/components/chat-file-preview/components/document-preview/index.ts +1 -0
- package/src/modules/messages/components/chat-file-preview/components/document-preview/types.ts +10 -0
- package/src/modules/messages/components/chat-file-preview/components/error-preview/error-preview.tsx +37 -0
- package/src/modules/messages/components/chat-file-preview/components/error-preview/index.ts +1 -0
- package/src/modules/messages/components/chat-file-preview/components/error-preview/types.ts +4 -0
- package/src/modules/messages/components/chat-file-preview/components/image-preview/image-preview.tsx +32 -0
- package/src/modules/messages/components/chat-file-preview/components/image-preview/index.ts +1 -0
- package/src/modules/messages/components/chat-file-preview/components/image-preview/types.ts +6 -0
- package/src/modules/messages/components/chat-file-preview/constants.ts +22 -0
- package/src/modules/messages/components/chat-file-preview/index.ts +1 -0
- package/src/modules/messages/components/chat-file-preview/types.ts +13 -0
- package/src/modules/messages/components/chat-file-preview/utils.spec.ts +38 -0
- package/src/modules/messages/components/chat-file-preview/utils.ts +13 -0
- package/src/modules/messages/components/chat-file-uploader/chat-file-uploader.builder.ts +19 -0
- package/src/modules/messages/components/chat-file-uploader/chat-file-uploader.spec.tsx +58 -0
- package/src/modules/messages/components/chat-file-uploader/chat-file-uploader.tsx +80 -0
- package/src/modules/messages/components/chat-file-uploader/index.ts +1 -0
- package/src/modules/messages/components/chat-file-uploader/types.ts +4 -0
- package/src/modules/messages/components/index.ts +1 -0
- package/src/modules/messages/constants.ts +2 -1
- package/src/modules/messages/hooks/use-get-signed-urls/index.ts +1 -0
- package/src/modules/messages/hooks/use-get-signed-urls/use-get-signed-urls.spec.tsx +27 -0
- package/src/modules/messages/hooks/use-get-signed-urls/use-get-signed-urls.tsx +38 -0
- package/src/modules/messages/hooks/use-infinite-get-messages/use-infinite-get-messages.spec.tsx +1 -2
- package/src/modules/messages/hooks/use-infinite-get-messages/use-infinite-get-messages.tsx +6 -8
- package/src/modules/messages/hooks/use-prefetch-messages/index.ts +1 -0
- package/src/modules/messages/hooks/use-prefetch-messages/use-prefetch-messages.tsx +28 -0
- package/src/modules/messages/hooks/use-suspense-messages/index.ts +2 -0
- package/src/modules/messages/hooks/use-suspense-messages/types.ts +4 -0
- package/src/modules/messages/hooks/use-suspense-messages/use-suspense-messages.tsx +21 -0
- package/src/modules/messages/service.direct.ts +19 -1
- package/src/modules/messages/service.ts +1 -1
- package/src/modules/messages/store/messages-max-count.atom.ts +2 -2
- package/src/modules/messages/types.ts +15 -1
- package/src/modules/messages/utils/set-messages-cache/utils.ts +1 -1
- package/src/modules/profile/hooks/use-get-profile/use-get-profile.tsx +7 -6
- package/src/modules/sparkie/__tests__/sparkie.mock.ts +1 -4
- package/src/modules/sparkie/hooks/use-init-sparkie/use-init-sparkie.tsx +34 -28
- package/src/modules/sparkie/service.ts +1 -1
- package/src/modules/sparkie/store/index.ts +1 -0
- package/src/modules/sparkie/store/sparkie-state.atom.ts +13 -0
- package/src/modules/widget/__tests__/widget-settings-props.builder.ts +20 -1
- package/src/modules/widget/components/chat-page/chat-page.tsx +58 -0
- package/src/modules/widget/components/constants.tsx +3 -1
- package/src/modules/widget/components/error-page/error-page.spec.tsx +17 -0
- package/src/modules/widget/components/error-page/error-page.tsx +12 -0
- package/src/modules/widget/components/error-page/index.ts +1 -0
- package/src/modules/widget/components/loading-page/loading-page.tsx +9 -15
- package/src/modules/widget/components/starter-page/starter-page-actions/index.ts +1 -0
- package/src/modules/widget/components/starter-page/starter-page-actions/starter-page-actions.spec.tsx +68 -0
- package/src/modules/widget/components/starter-page/starter-page-actions/starter-page-actions.tsx +34 -0
- package/src/modules/widget/components/starter-page/starter-page-content/index.ts +1 -0
- package/src/modules/widget/components/starter-page/starter-page-content/starter-page-content.spec.tsx +16 -0
- package/src/modules/widget/components/starter-page/starter-page-content/starter-page-content.tsx +28 -0
- package/src/modules/widget/components/starter-page/starter-page-header/index.ts +1 -0
- package/src/modules/widget/components/starter-page/starter-page-header/starter-page-header.spec.tsx +45 -0
- package/src/modules/widget/components/starter-page/starter-page-header/starter-page-header.tsx +36 -0
- package/src/modules/widget/components/starter-page/starter-page.spec.tsx +13 -3
- package/src/modules/widget/components/starter-page/starter-page.tsx +15 -109
- package/src/modules/widget/hooks/index.ts +1 -1
- package/src/modules/widget/hooks/use-listen-to-theme-change-event/use-listen-to-theme-change-event.tsx +8 -6
- package/src/modules/widget/hooks/use-retry-last-message/index.ts +1 -0
- package/src/modules/widget/hooks/use-retry-last-message/use-retry-last-message.tsx +37 -0
- package/src/modules/widget/store/create-store.ts +7 -0
- package/src/modules/widget/store/index.ts +2 -0
- package/src/modules/widget/store/widget-last-user-message.atom.ts +12 -0
- package/src/modules/widget/store/widget-settings-config.atom.ts +1 -6
- package/src/modules/widget/store/widget-tabs.atom.ts +17 -6
- package/src/types.ts +10 -0
- package/src/wrapper.tsx +52 -0
- package/src/modules/widget/hooks/use-init-widget/index.ts +0 -1
- package/src/modules/widget/hooks/use-init-widget/use-init-widget.tsx +0 -56
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ChatFileUploaderProps } from './types'
|
|
2
|
+
|
|
3
|
+
export class ChatFileUploaderBuilder {
|
|
4
|
+
private props: ChatFileUploaderProps = {}
|
|
5
|
+
|
|
6
|
+
withDisabled(disabled: boolean): ChatFileUploaderBuilder {
|
|
7
|
+
this.props.disabled = disabled
|
|
8
|
+
return this
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
withLoading(loading: boolean): ChatFileUploaderBuilder {
|
|
12
|
+
this.props.loading = loading
|
|
13
|
+
return this
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
build(): ChatFileUploaderProps {
|
|
17
|
+
return this.props
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { render, screen } from '@/src/config/tests'
|
|
2
|
+
|
|
3
|
+
import ChatFileUploader from './chat-file-uploader'
|
|
4
|
+
import { ChatFileUploaderBuilder } from './chat-file-uploader.builder'
|
|
5
|
+
|
|
6
|
+
vi.mock('@/src/lib/hooks/use-chat-file-upload', () => ({
|
|
7
|
+
useChatFileUpload: vi.fn(() => ({
|
|
8
|
+
handleSelectFile: vi.fn(),
|
|
9
|
+
selectedFile: null
|
|
10
|
+
}))
|
|
11
|
+
}))
|
|
12
|
+
|
|
13
|
+
const defaultProps = new ChatFileUploaderBuilder().build()
|
|
14
|
+
const renderComponent = (props = defaultProps) => render(<ChatFileUploader {...props} />)
|
|
15
|
+
|
|
16
|
+
describe('ChatFileUploader', () => {
|
|
17
|
+
it('should be disabled when disabled prop is true', () => {
|
|
18
|
+
const props = new ChatFileUploaderBuilder().withDisabled(true).build()
|
|
19
|
+
renderComponent(props)
|
|
20
|
+
|
|
21
|
+
const button = screen.getByRole('button', {
|
|
22
|
+
name: /general.buttons.open_options/i
|
|
23
|
+
})
|
|
24
|
+
expect(button).toBeDisabled()
|
|
25
|
+
})
|
|
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
|
+
it('should trigger file input when clicking file option', async () => {
|
|
48
|
+
const { user } = renderComponent()
|
|
49
|
+
|
|
50
|
+
const button = screen.getByRole('button', {
|
|
51
|
+
name: /general.buttons.open_options/i
|
|
52
|
+
})
|
|
53
|
+
await user.click(button)
|
|
54
|
+
|
|
55
|
+
expect(screen.getByText('general.buttons.file')).toBeInTheDocument()
|
|
56
|
+
expect(screen.getByText('general.buttons.image')).toBeInTheDocument()
|
|
57
|
+
})
|
|
58
|
+
})
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { type ChangeEvent, useRef } from 'react'
|
|
2
|
+
import { useTranslation } from 'react-i18next'
|
|
3
|
+
|
|
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'
|
|
8
|
+
|
|
9
|
+
import type { ChatFileUploaderProps } from './types'
|
|
10
|
+
|
|
11
|
+
function ChatFileUploader({ disabled = false, loading = false }: ChatFileUploaderProps) {
|
|
12
|
+
const { t } = useTranslation()
|
|
13
|
+
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
14
|
+
const imageInputRef = useRef<HTMLInputElement>(null)
|
|
15
|
+
|
|
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
|
+
const triggerFileInput = (type: FileType) => {
|
|
24
|
+
const inputRef = type === FILE_TYPES_KEYS.DOCUMENT ? fileInputRef : imageInputRef
|
|
25
|
+
inputRef.current?.click()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const handleFileInputChange = (e: ChangeEvent<HTMLInputElement>, type: FileType) => {
|
|
29
|
+
const file = e.target.files?.[0]
|
|
30
|
+
if (file) {
|
|
31
|
+
handleSelectFile(file, type)
|
|
32
|
+
}
|
|
33
|
+
e.target.value = ''
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<>
|
|
38
|
+
<input
|
|
39
|
+
type='file'
|
|
40
|
+
ref={fileInputRef}
|
|
41
|
+
onChange={(e) => handleFileInputChange(e, FILE_TYPES_KEYS.DOCUMENT)}
|
|
42
|
+
accept={acceptFileTypes}
|
|
43
|
+
className='hidden'
|
|
44
|
+
aria-hidden='true'
|
|
45
|
+
aria-label={t('general.buttons.file')}
|
|
46
|
+
/>
|
|
47
|
+
<input
|
|
48
|
+
type='file'
|
|
49
|
+
ref={imageInputRef}
|
|
50
|
+
onChange={(e) => handleFileInputChange(e, FILE_TYPES_KEYS.IMAGE)}
|
|
51
|
+
accept={acceptImageTypes}
|
|
52
|
+
className='hidden'
|
|
53
|
+
aria-hidden='true'
|
|
54
|
+
aria-label={t('general.buttons.image')}
|
|
55
|
+
/>
|
|
56
|
+
<DropdownActions
|
|
57
|
+
triggerIcon='plus'
|
|
58
|
+
disabled={disabled || loading || selectedFile !== null}
|
|
59
|
+
items={[
|
|
60
|
+
{
|
|
61
|
+
label: 'general.buttons.file',
|
|
62
|
+
icon: 'file',
|
|
63
|
+
visible: true,
|
|
64
|
+
callback: () => triggerFileInput(FILE_TYPES_KEYS.DOCUMENT)
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
label: 'general.buttons.image',
|
|
68
|
+
icon: 'image',
|
|
69
|
+
visible: true,
|
|
70
|
+
callback: () => triggerFileInput(FILE_TYPES_KEYS.IMAGE)
|
|
71
|
+
}
|
|
72
|
+
]}
|
|
73
|
+
triggerClassName='rounded-full border border-neutral-300 h-10 w-10 flex items-center justify-center'
|
|
74
|
+
triggerIconClassName='w-3.5 h-3.5'
|
|
75
|
+
/>
|
|
76
|
+
</>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export default ChatFileUploader
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as ChatFileUploader } from './chat-file-uploader'
|
|
@@ -5,5 +5,6 @@ export const MessagesEndpoints = {
|
|
|
5
5
|
getAll: (conversationId: string) =>
|
|
6
6
|
`${process.env.API_CONVERSATION_URL}/v1/conversations/${conversationId}/messages`,
|
|
7
7
|
create: (conversationId: string) =>
|
|
8
|
-
`${process.env.API_CONVERSATION_URL}/v1/conversations/${conversationId}/messages
|
|
8
|
+
`${process.env.API_CONVERSATION_URL}/v1/conversations/${conversationId}/messages`,
|
|
9
|
+
getSignedUrls: () => `${process.env.API_HOTMART_TUTOR}/api/v1/files/signed-urls`
|
|
9
10
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './use-get-signed-urls'
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { renderHook, waitFor } from '@/src/config/tests'
|
|
2
|
+
|
|
3
|
+
import type { UseGetSignedUrlsProps } from './use-get-signed-urls'
|
|
4
|
+
import { useGetSignedUrls } from './use-get-signed-urls'
|
|
5
|
+
|
|
6
|
+
describe('useGetSignedUrls', () => {
|
|
7
|
+
const props: UseGetSignedUrlsProps = {
|
|
8
|
+
chatId: 'chat-id',
|
|
9
|
+
fileExtension: 'pdf',
|
|
10
|
+
fileNameWithExtension: 'file.pdf',
|
|
11
|
+
productId: 123
|
|
12
|
+
}
|
|
13
|
+
const render = () => renderHook(() => useGetSignedUrls(props))
|
|
14
|
+
|
|
15
|
+
it('should get signed upload url', async () => {
|
|
16
|
+
const { result } = render()
|
|
17
|
+
|
|
18
|
+
await waitFor(() => expect(result.current.isSuccess).toBeTruthy())
|
|
19
|
+
|
|
20
|
+
expect(result.current.data).toMatchObject({
|
|
21
|
+
uploadUrl: expect.any(String) as string,
|
|
22
|
+
futureDownloadUrl: expect.any(String) as string,
|
|
23
|
+
imagePreviewUrl: expect.any(String) as string,
|
|
24
|
+
fileNameWithExtension: expect.any(String) as string
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
}
|
package/src/modules/messages/hooks/use-infinite-get-messages/use-infinite-get-messages.spec.tsx
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { act, MockRequest, renderHook, waitFor } from '@/src/config/tests'
|
|
2
2
|
import IMessageWithSenderDataMock from '../../__tests__/imessage-with-sender-data.mock'
|
|
3
3
|
import { MessagesEndpoints, MSG_MAX_COUNT } from '../../constants'
|
|
4
|
-
import type { IMessageWithSenderData } from '../../types'
|
|
5
4
|
|
|
6
5
|
import useInfiniteGetMessages from './use-infinite-get-messages'
|
|
7
6
|
|
|
@@ -34,7 +33,7 @@ describe('useInfiniteGetMessages', () => {
|
|
|
34
33
|
|
|
35
34
|
MockRequest.mock(
|
|
36
35
|
MessagesEndpoints.getAll(mockConversationId),
|
|
37
|
-
new IMessageWithSenderDataMock().getMany(allMessages)
|
|
36
|
+
new IMessageWithSenderDataMock().getMany(allMessages)
|
|
38
37
|
)
|
|
39
38
|
|
|
40
39
|
const { result } = createHook({
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { useInfiniteQuery } from '@tanstack/react-query'
|
|
1
|
+
import { infiniteQueryOptions, useInfiniteQuery } from '@tanstack/react-query'
|
|
3
2
|
|
|
4
3
|
import { formatTime } from '@/src/config/dayjs'
|
|
5
4
|
import { api } from '@/src/config/request'
|
|
@@ -18,7 +17,7 @@ export const getAllMessagesQuery = ({
|
|
|
18
17
|
profileId,
|
|
19
18
|
limit = MSG_MAX_COUNT
|
|
20
19
|
}: GetAllMessagesQueryProps) =>
|
|
21
|
-
({
|
|
20
|
+
infiniteQueryOptions({
|
|
22
21
|
queryKey: ['getAllMessagesWithoutSparkie', limit, conversationId],
|
|
23
22
|
queryFn: async ({ pageParam }: { pageParam?: number }) => {
|
|
24
23
|
const { data: messages } = await api.get<IMessageWithSenderData[]>(
|
|
@@ -45,6 +44,9 @@ export const getAllMessagesQuery = ({
|
|
|
45
44
|
return minSentAt
|
|
46
45
|
},
|
|
47
46
|
enabled: Boolean(conversationId && profileId) && limit > 0,
|
|
47
|
+
meta: {
|
|
48
|
+
persist: false
|
|
49
|
+
},
|
|
48
50
|
select(data) {
|
|
49
51
|
const messages = data.pages?.flatMap((page) => page.messages) ?? []
|
|
50
52
|
return messagesParser({ messages, profileId: profileId as string }).reduce(
|
|
@@ -68,11 +70,7 @@ export const getAllMessagesQuery = ({
|
|
|
68
70
|
new Map<string, ParsedMessage[]>()
|
|
69
71
|
)
|
|
70
72
|
}
|
|
71
|
-
})
|
|
72
|
-
FetchMessagesResponse,
|
|
73
|
-
Error,
|
|
74
|
-
Map<string, ParsedMessage[]>
|
|
75
|
-
>
|
|
73
|
+
})
|
|
76
74
|
|
|
77
75
|
function useInfiniteGetMessages({ conversationId, profileId, limit }: GetAllMessagesQueryProps) {
|
|
78
76
|
const query = getAllMessagesQuery({
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as usePrefetchMessages } from './use-prefetch-messages'
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useCallback, useEffect } from 'react'
|
|
2
|
+
import { useQueryClient, useSuspenseQuery } from '@tanstack/react-query'
|
|
3
|
+
|
|
4
|
+
import { getProfileQuery } from '@/src/modules/profile'
|
|
5
|
+
import type { WidgetSettingProps } from '@/src/types'
|
|
6
|
+
import { MSG_MAX_COUNT } from '../../constants'
|
|
7
|
+
import { getAllMessagesQuery } from '../use-infinite-get-messages'
|
|
8
|
+
|
|
9
|
+
function usePrefetchMessages(settings: WidgetSettingProps) {
|
|
10
|
+
const queryClient = useQueryClient()
|
|
11
|
+
const profileQuery = useSuspenseQuery(getProfileQuery())
|
|
12
|
+
|
|
13
|
+
const prefetch = useCallback(async () => {
|
|
14
|
+
await queryClient.prefetchInfiniteQuery(
|
|
15
|
+
getAllMessagesQuery({
|
|
16
|
+
conversationId: settings.conversationId,
|
|
17
|
+
profileId: typeof profileQuery?.data?.id === 'string' ? profileQuery?.data?.id : undefined,
|
|
18
|
+
limit: MSG_MAX_COUNT
|
|
19
|
+
})
|
|
20
|
+
)
|
|
21
|
+
}, [profileQuery?.data?.id, queryClient, settings.conversationId])
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
void prefetch()
|
|
25
|
+
}, [prefetch])
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default usePrefetchMessages
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useSuspenseInfiniteQuery, useSuspenseQuery } from '@tanstack/react-query'
|
|
2
|
+
|
|
3
|
+
import { getProfileQuery } from '@/src/modules/profile'
|
|
4
|
+
import { MSG_MAX_COUNT } from '../../constants'
|
|
5
|
+
import { getAllMessagesQuery } from '../use-infinite-get-messages'
|
|
6
|
+
|
|
7
|
+
import type { UseSuspenseMessagesProps } from './types'
|
|
8
|
+
|
|
9
|
+
function useSuspenseMessages({ conversationId, limit = MSG_MAX_COUNT }: UseSuspenseMessagesProps) {
|
|
10
|
+
const profileQuery = useSuspenseQuery(getProfileQuery())
|
|
11
|
+
|
|
12
|
+
return useSuspenseInfiniteQuery(
|
|
13
|
+
getAllMessagesQuery({
|
|
14
|
+
conversationId: conversationId,
|
|
15
|
+
profileId: typeof profileQuery?.data?.id === 'string' ? profileQuery?.data?.id : undefined,
|
|
16
|
+
limit
|
|
17
|
+
})
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default useSuspenseMessages
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Message } from '@hotmart/sparkie/dist/MessageService'
|
|
1
|
+
import type { Message } from '@hotmart-org-ca/sparkie/dist/MessageService'
|
|
2
2
|
|
|
3
3
|
import { api } from '@/src/config/request'
|
|
4
4
|
|
|
@@ -6,6 +6,8 @@ import { MessagesEndpoints } from './constants'
|
|
|
6
6
|
import type {
|
|
7
7
|
DirectMessagesServiceProps,
|
|
8
8
|
FetchMessagesResponse,
|
|
9
|
+
IGetSignedUrlsPayload,
|
|
10
|
+
IGetSignedUrlsResponse,
|
|
9
11
|
IMessageWithSenderData
|
|
10
12
|
} from './types'
|
|
11
13
|
|
|
@@ -36,6 +38,22 @@ class DirectMessagesService {
|
|
|
36
38
|
|
|
37
39
|
return data
|
|
38
40
|
}
|
|
41
|
+
|
|
42
|
+
async getSignedUrls({
|
|
43
|
+
productId,
|
|
44
|
+
chatId,
|
|
45
|
+
fileExtension,
|
|
46
|
+
fileNameWithExtension
|
|
47
|
+
}: IGetSignedUrlsPayload): Promise<IGetSignedUrlsResponse> {
|
|
48
|
+
const { data } = await api.post<IGetSignedUrlsResponse>(MessagesEndpoints.getSignedUrls(), {
|
|
49
|
+
productId,
|
|
50
|
+
chatId,
|
|
51
|
+
fileExtension,
|
|
52
|
+
fileNameWithExtension
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
return data
|
|
56
|
+
}
|
|
39
57
|
}
|
|
40
58
|
|
|
41
59
|
export default new DirectMessagesService()
|
|
@@ -2,7 +2,7 @@ import { atom, useAtom, useAtomValue } from 'jotai'
|
|
|
2
2
|
|
|
3
3
|
const INITIAL_MAX_COUNT = 2
|
|
4
4
|
|
|
5
|
-
const messagesMaxCountAtom = atom(INITIAL_MAX_COUNT)
|
|
5
|
+
export const messagesMaxCountAtom = atom(INITIAL_MAX_COUNT)
|
|
6
6
|
|
|
7
7
|
const setMessagesMaxCountAtom = atom(
|
|
8
8
|
(get) => get(messagesMaxCountAtom),
|
|
@@ -10,4 +10,4 @@ const setMessagesMaxCountAtom = atom(
|
|
|
10
10
|
)
|
|
11
11
|
|
|
12
12
|
export const useSetMessagesMaxCountAtom = () => useAtom(setMessagesMaxCountAtom)
|
|
13
|
-
export const useMessagesMaxCount = () => useAtomValue(
|
|
13
|
+
export const useMessagesMaxCount = () => useAtomValue(messagesMaxCountAtom)
|
|
@@ -2,7 +2,7 @@ import type {
|
|
|
2
2
|
Message as SparkieMsg,
|
|
3
3
|
MessageContent,
|
|
4
4
|
SenderData
|
|
5
|
-
} from '@hotmart/sparkie/dist/MessageService'
|
|
5
|
+
} from '@hotmart-org-ca/sparkie/dist/MessageService'
|
|
6
6
|
|
|
7
7
|
export type IMessage = SparkieMsg & {
|
|
8
8
|
metadata: {
|
|
@@ -19,6 +19,13 @@ export type IGetMessagesPayload = {
|
|
|
19
19
|
before?: number
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
export type IGetSignedUrlsPayload = {
|
|
23
|
+
chatId: string
|
|
24
|
+
fileExtension: string
|
|
25
|
+
fileNameWithExtension: string
|
|
26
|
+
productId: number
|
|
27
|
+
}
|
|
28
|
+
|
|
22
29
|
export type ISendTextMessagePayload = {
|
|
23
30
|
conversationId: string
|
|
24
31
|
content: {
|
|
@@ -63,6 +70,13 @@ export type MessageParserArgs = {
|
|
|
63
70
|
profileId: string
|
|
64
71
|
}
|
|
65
72
|
|
|
73
|
+
export type IGetSignedUrlsResponse = {
|
|
74
|
+
uploadUrl: string
|
|
75
|
+
futureDownloadUrl: string
|
|
76
|
+
imagePreviewUrl: string
|
|
77
|
+
fileNameWithExtension: string
|
|
78
|
+
}
|
|
79
|
+
|
|
66
80
|
export type ParsedMessage = {
|
|
67
81
|
from: string
|
|
68
82
|
id: string
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { useQuery } from '@tanstack/react-query'
|
|
1
|
+
import { queryOptions, useQuery } from '@tanstack/react-query'
|
|
2
2
|
|
|
3
3
|
import { ProfileEndpoints } from '../../constants'
|
|
4
4
|
import ProfileService from '../../service'
|
|
5
5
|
|
|
6
|
-
export const getProfileQuery = (enabled: boolean) =>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
export const getProfileQuery = (enabled: boolean = true) =>
|
|
7
|
+
queryOptions({
|
|
8
|
+
queryKey: [ProfileEndpoints.getProfile()],
|
|
9
|
+
queryFn: () => ProfileService.getProfile(),
|
|
10
|
+
enabled
|
|
11
|
+
})
|
|
11
12
|
|
|
12
13
|
export function useGetProfile(enabled: boolean = true) {
|
|
13
14
|
return useQuery(getProfileQuery(enabled))
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import ICursorUpdateBuilder from '../../cursor/__tests__/icursor-update.builder'
|
|
2
|
-
import type { IMessageWithSenderData } from '../../messages'
|
|
3
2
|
import IMessageWithSenderDataMock from '../../messages/__tests__/imessage-with-sender-data.mock'
|
|
4
3
|
|
|
5
4
|
export const SparkieMessageServiceMock = {
|
|
6
|
-
getAll: vi
|
|
7
|
-
.fn()
|
|
8
|
-
.mockReturnValue(new IMessageWithSenderDataMock().getMany(10) as IMessageWithSenderData[]),
|
|
5
|
+
getAll: vi.fn().mockReturnValue(new IMessageWithSenderDataMock().getMany(10)),
|
|
9
6
|
post: vi.fn(),
|
|
10
7
|
postDirect: vi.fn(),
|
|
11
8
|
remove: vi.fn()
|
|
@@ -1,39 +1,45 @@
|
|
|
1
|
-
import { useCallback, useEffect
|
|
1
|
+
import { useCallback, useEffect } from 'react'
|
|
2
|
+
import type { useStore } from 'jotai'
|
|
2
3
|
|
|
3
|
-
import { useWidgetSettingsAtomValue } from '@/src/modules/widget'
|
|
4
4
|
import { SparkieService } from '../..'
|
|
5
|
+
import { sparkieStateAtom } from '../../store'
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
export type UseInitSparkieProps = {
|
|
8
|
+
hotmartToken: string
|
|
9
|
+
store?: ReturnType<typeof useStore>
|
|
10
|
+
}
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
function useInitSparkie({ hotmartToken, store }: UseInitSparkieProps) {
|
|
13
|
+
const sparkieState = store?.get(sparkieStateAtom)
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
15
|
+
const init = useCallback(async () => {
|
|
16
|
+
if (hotmartToken) {
|
|
17
|
+
try {
|
|
18
|
+
store?.set(sparkieStateAtom, 'initializing')
|
|
19
|
+
await SparkieService.initSparkie({
|
|
20
|
+
token: hotmartToken,
|
|
21
|
+
skipPresenceSetup: true,
|
|
22
|
+
retryOptions: {
|
|
23
|
+
maxRetries: 5,
|
|
24
|
+
retryDelay: 1000,
|
|
25
|
+
backoffMultiplier: 1.5
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
await SparkieService.ensureInitialized()
|
|
29
|
+
store?.set(sparkieStateAtom, 'initialized')
|
|
30
|
+
} catch {
|
|
31
|
+
store?.set(sparkieStateAtom, 'failed')
|
|
32
|
+
}
|
|
29
33
|
}
|
|
30
|
-
}, [
|
|
34
|
+
}, [hotmartToken, store])
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}, [init])
|
|
36
|
+
const checkState = useCallback(() => {
|
|
37
|
+
if (sparkieState === 'idle') return init()
|
|
38
|
+
}, [init, sparkieState])
|
|
35
39
|
|
|
36
|
-
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
void checkState()
|
|
42
|
+
}, [checkState])
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
export default useInitSparkie
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './sparkie-state.atom'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { atom, useAtom, useAtomValue } from 'jotai'
|
|
2
|
+
|
|
3
|
+
import type { InitializationState } from '../types'
|
|
4
|
+
|
|
5
|
+
export const sparkieStateAtom = atom<InitializationState>('idle')
|
|
6
|
+
|
|
7
|
+
const setSparkieStateAtom = atom(
|
|
8
|
+
(get) => get(sparkieStateAtom),
|
|
9
|
+
(_, set, sparkieState: InitializationState) => set(sparkieStateAtom, sparkieState)
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
export const useSparkieStateAtom = () => useAtom(setSparkieStateAtom)
|
|
13
|
+
export const useSparkieStateAtomValue = () => useAtomValue(sparkieStateAtom)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ILanguages } from '@/src/config/i18n'
|
|
2
2
|
import { chance } from '@/src/config/tests'
|
|
3
|
-
import type { User, WidgetSettingProps } from '@/src/types'
|
|
3
|
+
import type { Theme, User, WidgetSettingProps } from '@/src/types'
|
|
4
4
|
|
|
5
5
|
class WidgetSettingPropsBuilder implements WidgetSettingProps {
|
|
6
6
|
hotmartToken: string
|
|
@@ -21,6 +21,19 @@ class WidgetSettingPropsBuilder implements WidgetSettingProps {
|
|
|
21
21
|
classHashId?: string
|
|
22
22
|
owner_id?: string
|
|
23
23
|
current_media_codes?: string
|
|
24
|
+
config?:
|
|
25
|
+
| {
|
|
26
|
+
theme?: Theme
|
|
27
|
+
metadata?: {
|
|
28
|
+
parent?: 'AGENT' | 'TUTOR'
|
|
29
|
+
agentProductId?: number
|
|
30
|
+
agentName?: string
|
|
31
|
+
courseName?: string
|
|
32
|
+
source?: string
|
|
33
|
+
promptId?: string
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
| undefined
|
|
24
37
|
|
|
25
38
|
constructor() {
|
|
26
39
|
this.hotmartToken = chance.guid()
|
|
@@ -144,6 +157,12 @@ class WidgetSettingPropsBuilder implements WidgetSettingProps {
|
|
|
144
157
|
|
|
145
158
|
return this
|
|
146
159
|
}
|
|
160
|
+
|
|
161
|
+
withTheme(theme: Theme) {
|
|
162
|
+
this.config = { ...(this.config ?? {}), theme }
|
|
163
|
+
|
|
164
|
+
return this
|
|
165
|
+
}
|
|
147
166
|
}
|
|
148
167
|
|
|
149
168
|
export default WidgetSettingPropsBuilder
|