app-tutor-ai-consumer 1.21.2 → 1.22.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 +7 -0
- package/config/vitest/__mocks__/animation-avatar.tsx +3 -0
- package/config/vitest/vitest.config.mts +1 -0
- package/package.json +1 -1
- package/src/config/styles/index.css +26 -0
- package/src/lib/components/button/button-default.tsx +35 -0
- package/src/lib/components/button/button.tsx +23 -32
- package/src/main/main.tsx +2 -2
- package/src/modules/messages/constants.ts +2 -0
- package/src/modules/messages/hooks/use-send-text-message/use-send-text-message.spec.tsx +30 -28
- package/src/modules/messages/hooks/use-send-text-message/use-send-text-message.tsx +30 -28
- package/src/modules/messages/index.ts +1 -0
- package/src/modules/messages/service.direct.ts +41 -0
- package/src/modules/messages/types.ts +22 -1
- package/src/modules/sparkie/hooks/index.ts +1 -0
- package/src/modules/sparkie/hooks/use-init-sparkie/index.ts +2 -0
- package/src/modules/sparkie/hooks/use-init-sparkie/use-init-sparkie.tsx +39 -0
- package/src/modules/widget/components/container/container.tsx +2 -10
- package/src/modules/widget/components/starter-page/starter-page.spec.tsx +4 -0
- package/src/modules/widget/components/starter-page/starter-page.tsx +20 -5
- package/src/modules/widget/hooks/use-init-widget/use-init-widget.tsx +0 -11
- package/src/modules/widget/store/widget-tabs.atom.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [1.22.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.21.2...v1.22.0) (2025-08-04)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- add button default ([e10c478](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/e10c47844c68815df58a942250fef1fcd1d0f676))
|
|
6
|
+
- add useInitSparkie ([f3dd5f9](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/f3dd5f9d9542f755126e4fcc2af8e8a500703753))
|
|
7
|
+
|
|
1
8
|
## [1.21.2](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.21.1...v1.21.2) (2025-08-01)
|
|
2
9
|
|
|
3
10
|
## [1.21.1](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.21.0...v1.21.1) (2025-07-31)
|
|
@@ -13,6 +13,7 @@ export default defineConfig({
|
|
|
13
13
|
'./config/vitest/__mocks__/i18n.tsx',
|
|
14
14
|
'./config/vitest/__mocks__/sparkie.tsx',
|
|
15
15
|
'./config/vitest/__mocks__/icons.tsx',
|
|
16
|
+
'./config/vitest/__mocks__/animation-avatar.tsx',
|
|
16
17
|
'./config/vitest/__mocks__/intersection-observer.ts',
|
|
17
18
|
'./config/vitest/polyfills/global.js'
|
|
18
19
|
],
|
package/package.json
CHANGED
|
@@ -3,3 +3,29 @@
|
|
|
3
3
|
@tailwind base;
|
|
4
4
|
@tailwind components;
|
|
5
5
|
@tailwind utilities;
|
|
6
|
+
|
|
7
|
+
@layer components {
|
|
8
|
+
.shine-box {
|
|
9
|
+
background-color: var(--shb-bg-color, var(--hc-color-neutral-200));
|
|
10
|
+
background-size: 200% 100%;
|
|
11
|
+
background-image: linear-gradient(
|
|
12
|
+
90deg,
|
|
13
|
+
transparent 0%,
|
|
14
|
+
rgb(from var(--shb-shine-color, var(--hc-color-neutral-900)) r g b / 0.3) 50%,
|
|
15
|
+
transparent 100%
|
|
16
|
+
);
|
|
17
|
+
animation-name: slideShine;
|
|
18
|
+
animation-duration: 1s;
|
|
19
|
+
animation-timing-function: cubic-bezier(0.5, -0.5, 1, 1.5);
|
|
20
|
+
animation-iteration-count: infinite;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@keyframes slideShine {
|
|
24
|
+
0% {
|
|
25
|
+
background-position: 100% 0;
|
|
26
|
+
}
|
|
27
|
+
100% {
|
|
28
|
+
background-position: -100% 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
2
|
+
import type { ButtonHTMLAttributes, PropsWithChildren } from 'react'
|
|
3
|
+
|
|
4
|
+
export type ButtonDefaultProps = PropsWithChildren<
|
|
5
|
+
ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
6
|
+
show?: boolean
|
|
7
|
+
loading?: boolean
|
|
8
|
+
}
|
|
9
|
+
>
|
|
10
|
+
|
|
11
|
+
function ButtonDefault({
|
|
12
|
+
loading,
|
|
13
|
+
className,
|
|
14
|
+
children,
|
|
15
|
+
show = true,
|
|
16
|
+
...props
|
|
17
|
+
}: ButtonDefaultProps) {
|
|
18
|
+
if (!show) return null
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<button
|
|
22
|
+
{...props}
|
|
23
|
+
className={clsx(
|
|
24
|
+
'rounded text-base font-medium outline-none focus-visible:ring-2 focus-visible:ring-blue-500',
|
|
25
|
+
className
|
|
26
|
+
)}
|
|
27
|
+
type={props.type ?? 'button'}
|
|
28
|
+
disabled={props.disabled || loading}
|
|
29
|
+
aria-busy={loading}>
|
|
30
|
+
{children}
|
|
31
|
+
</button>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default ButtonDefault
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import clsx from 'clsx'
|
|
2
|
-
import type {
|
|
2
|
+
import type { PropsWithChildren } from 'react'
|
|
3
3
|
|
|
4
4
|
import { Spinner } from '../spinner'
|
|
5
5
|
|
|
6
|
+
import type { ButtonDefaultProps } from './button-default'
|
|
7
|
+
import ButtonDefault from './button-default'
|
|
8
|
+
|
|
6
9
|
import styles from './styles.module.css'
|
|
7
10
|
|
|
8
11
|
function ButtonContent({
|
|
@@ -25,13 +28,9 @@ function ButtonContent({
|
|
|
25
28
|
)
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
export type ButtonProps =
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
show?: boolean
|
|
32
|
-
loading?: boolean
|
|
33
|
-
}
|
|
34
|
-
>
|
|
31
|
+
export type ButtonProps = ButtonDefaultProps & {
|
|
32
|
+
variant?: 'brand' | 'secondary' | 'primary' | 'tertiary' | 'gradient-outline'
|
|
33
|
+
}
|
|
35
34
|
|
|
36
35
|
function Button({
|
|
37
36
|
children,
|
|
@@ -53,35 +52,28 @@ function Button({
|
|
|
53
52
|
switch (variant) {
|
|
54
53
|
case 'gradient-outline':
|
|
55
54
|
return (
|
|
56
|
-
<
|
|
57
|
-
className={clsx(
|
|
58
|
-
gridClasses,
|
|
59
|
-
defaultClasses,
|
|
60
|
-
'group relative inline-flex items-center justify-center overflow-hidden rounded-lg bg-gradient-to-br from-[var(--ai-color-gradient-primary)] to-[var(--ai-color-gradient-accent)] p-[1px] hover:text-neutral-1000 group-hover:from-[var(--ai-color-gradient-primary)] group-hover:to-[var(--ai-color-gradient-accent)]',
|
|
61
|
-
className
|
|
62
|
-
)}
|
|
55
|
+
<ButtonDefault
|
|
63
56
|
{...props}
|
|
64
|
-
|
|
65
|
-
|
|
57
|
+
loading={loading}
|
|
58
|
+
show={show}
|
|
59
|
+
className={clsx(
|
|
60
|
+
className,
|
|
61
|
+
'group relative inline-flex items-center justify-center overflow-hidden rounded-lg p-[1px]',
|
|
62
|
+
{
|
|
63
|
+
'bg-gradient-to-br from-[var(--ai-color-gradient-primary)] to-[var(--ai-color-gradient-accent)] hover:text-neutral-1000 group-hover:from-[var(--ai-color-gradient-primary)] group-hover:to-[var(--ai-color-gradient-accent)]':
|
|
64
|
+
!loading,
|
|
65
|
+
'shine-box cursor-not-allowed': loading
|
|
66
|
+
}
|
|
67
|
+
)}>
|
|
66
68
|
<span
|
|
67
69
|
data-label='text-content'
|
|
68
70
|
className={clsx(
|
|
69
|
-
'
|
|
70
|
-
'flex flex-nowrap
|
|
71
|
-
{
|
|
72
|
-
visible: !loading,
|
|
73
|
-
invisible: loading
|
|
74
|
-
}
|
|
71
|
+
'flex-1 rounded-lg bg-neutral-100 px-5 py-2.5 text-neutral-1000 transition-all duration-75 ease-in group-hover:bg-transparent',
|
|
72
|
+
'flex flex-nowrap'
|
|
75
73
|
)}>
|
|
76
|
-
{children}
|
|
74
|
+
<span className={clsx({ 'opacity-20': loading })}>{children}</span>
|
|
77
75
|
</span>
|
|
78
|
-
|
|
79
|
-
className={clsx('mx-auto my-auto h-[1em] w-[1em] text-current', '[grid-area:stack]', {
|
|
80
|
-
visible: loading,
|
|
81
|
-
invisible: !loading
|
|
82
|
-
})}
|
|
83
|
-
/>
|
|
84
|
-
</button>
|
|
76
|
+
</ButtonDefault>
|
|
85
77
|
)
|
|
86
78
|
case 'primary':
|
|
87
79
|
return (
|
|
@@ -152,7 +144,6 @@ function Button({
|
|
|
152
144
|
return (
|
|
153
145
|
<button
|
|
154
146
|
className={clsx(
|
|
155
|
-
gridClasses,
|
|
156
147
|
'rounded-full outline-none transition-colors duration-300 ease-in',
|
|
157
148
|
{
|
|
158
149
|
'cursor-pointer ring-primary-500 hover:bg-neutral-300 focus:bg-neutral-300 focus-visible:ring-2':
|
package/src/main/main.tsx
CHANGED
|
@@ -12,14 +12,14 @@ export type MainProps = {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
function Main({ settings }: MainProps) {
|
|
15
|
-
|
|
15
|
+
useInitWidget(settings)
|
|
16
16
|
useAppLang(settings.locale)
|
|
17
17
|
useListenToThemeChangeEvent()
|
|
18
18
|
|
|
19
19
|
return (
|
|
20
20
|
<ErrorBoundary fallback={<GenericError />}>
|
|
21
21
|
<GlobalProviders settings={settings}>
|
|
22
|
-
<WidgetContainer
|
|
22
|
+
<WidgetContainer />
|
|
23
23
|
</GlobalProviders>
|
|
24
24
|
</ErrorBoundary>
|
|
25
25
|
)
|
|
@@ -3,5 +3,7 @@ export const MSG_MAX_PAGES = 20
|
|
|
3
3
|
|
|
4
4
|
export const MessagesEndpoints = {
|
|
5
5
|
getAll: (conversationId: string) =>
|
|
6
|
+
`${process.env.API_CONVERSATION_URL}/v1/conversations/${conversationId}/messages`,
|
|
7
|
+
create: (conversationId: string) =>
|
|
6
8
|
`${process.env.API_CONVERSATION_URL}/v1/conversations/${conversationId}/messages`
|
|
7
9
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { UAParser } from 'ua-parser-js'
|
|
2
2
|
|
|
3
3
|
import { act, chance, renderHook, waitFor } from '@/src/config/tests'
|
|
4
|
-
import { MessagesService } from '@/src/modules/messages'
|
|
4
|
+
import { DirectMessagesService as MessagesService } from '@/src/modules/messages'
|
|
5
5
|
import { useGetProfile } from '@/src/modules/profile'
|
|
6
6
|
import * as Store from '@/src/modules/widget'
|
|
7
7
|
import WidgetSettingPropsBuilder from '@/src/modules/widget/__tests__/widget-settings-props.builder'
|
|
@@ -57,40 +57,42 @@ describe('useSendTextMessage', () => {
|
|
|
57
57
|
const txt = 'question::summary'
|
|
58
58
|
const text = txt.replace('question::', '')
|
|
59
59
|
|
|
60
|
-
vi.spyOn(MessagesService, '
|
|
60
|
+
vi.spyOn(MessagesService, 'create')
|
|
61
61
|
|
|
62
62
|
const { result } = render()
|
|
63
63
|
|
|
64
64
|
await waitFor(() => result.current.mutateAsync(txt))
|
|
65
65
|
|
|
66
|
-
expect(MessagesService.
|
|
67
|
-
expect(MessagesService.
|
|
68
|
-
content: {
|
|
69
|
-
type: 'text/plain',
|
|
70
|
-
text
|
|
71
|
-
},
|
|
66
|
+
expect(MessagesService.create).toHaveBeenCalledTimes(1)
|
|
67
|
+
expect(MessagesService.create).toHaveBeenNthCalledWith(1, {
|
|
72
68
|
conversationId: defaultSettings.conversationId,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
language: defaultSettings.locale,
|
|
78
|
-
clubName: defaultSettings.clubName,
|
|
79
|
-
productName: defaultSettings.productName,
|
|
80
|
-
productId: defaultSettings.productId,
|
|
81
|
-
classHashId: defaultSettings.classHashId,
|
|
82
|
-
owner_id: defaultSettings.owner_id,
|
|
83
|
-
current_media_codes: defaultSettings.current_media_codes,
|
|
84
|
-
question: text,
|
|
85
|
-
router: text,
|
|
86
|
-
osVersion: UAParserMock.browser.version,
|
|
87
|
-
platformDetail: UAParserMock.browser.name,
|
|
88
|
-
ucode: defaultSettings.user?.ucode
|
|
69
|
+
message: {
|
|
70
|
+
content: {
|
|
71
|
+
type: 'text/plain',
|
|
72
|
+
text
|
|
89
73
|
},
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
74
|
+
metadata: {
|
|
75
|
+
author: 'user',
|
|
76
|
+
contactId: defaultSettings.contactId,
|
|
77
|
+
context: {
|
|
78
|
+
language: defaultSettings.locale,
|
|
79
|
+
clubName: defaultSettings.clubName,
|
|
80
|
+
productName: defaultSettings.productName,
|
|
81
|
+
productId: defaultSettings.productId,
|
|
82
|
+
classHashId: defaultSettings.classHashId,
|
|
83
|
+
owner_id: defaultSettings.owner_id,
|
|
84
|
+
current_media_codes: defaultSettings.current_media_codes,
|
|
85
|
+
question: text,
|
|
86
|
+
router: text,
|
|
87
|
+
osVersion: UAParserMock.browser.version,
|
|
88
|
+
platformDetail: UAParserMock.browser.name,
|
|
89
|
+
ucode: defaultSettings.user?.ucode
|
|
90
|
+
},
|
|
91
|
+
externalId: expect.any(String),
|
|
92
|
+
namespace: defaultSettings.namespace,
|
|
93
|
+
sessionId: defaultSettings.sessionId,
|
|
94
|
+
userId: String(getProfileMock.data.userId)
|
|
95
|
+
}
|
|
94
96
|
}
|
|
95
97
|
})
|
|
96
98
|
})
|
|
@@ -3,7 +3,7 @@ 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 { MessagesService } from '@/src/modules/messages'
|
|
6
|
+
import { DirectMessagesService as MessagesService } from '@/src/modules/messages'
|
|
7
7
|
import { useGetProfile } from '@/src/modules/profile'
|
|
8
8
|
import { useWidgetLoadingAtom, useWidgetSettingsAtomValue } from '@/src/modules/widget'
|
|
9
9
|
import { MessagesEvents } from '../../events'
|
|
@@ -47,35 +47,37 @@ function useSendTextMessage() {
|
|
|
47
47
|
questionParam = processedMessage
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
return MessagesService.
|
|
51
|
-
content: {
|
|
52
|
-
type: 'text/plain',
|
|
53
|
-
text: processedMessage
|
|
54
|
-
},
|
|
50
|
+
return MessagesService.create({
|
|
55
51
|
conversationId: settings?.conversationId,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
language: settings.locale,
|
|
61
|
-
clubName: settings.clubName,
|
|
62
|
-
productName: settings.productName,
|
|
63
|
-
productId: settings.productId,
|
|
64
|
-
productType: settings.productType,
|
|
65
|
-
classType: settings.classType,
|
|
66
|
-
classHashId: settings.classHashId,
|
|
67
|
-
owner_id: settings?.owner_id,
|
|
68
|
-
current_media_codes: settings?.current_media_codes,
|
|
69
|
-
question: questionParam,
|
|
70
|
-
router: questionParam ? 'summary' : undefined,
|
|
71
|
-
osVersion: browserInfo.version,
|
|
72
|
-
platformDetail: browserInfo.name,
|
|
73
|
-
ucode: settings.user?.ucode
|
|
52
|
+
message: {
|
|
53
|
+
content: {
|
|
54
|
+
type: 'text/plain',
|
|
55
|
+
text: processedMessage
|
|
74
56
|
},
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
57
|
+
metadata: {
|
|
58
|
+
author: 'user',
|
|
59
|
+
contactId: settings.contactId,
|
|
60
|
+
context: {
|
|
61
|
+
language: settings.locale,
|
|
62
|
+
clubName: settings.clubName,
|
|
63
|
+
productName: settings.productName,
|
|
64
|
+
productId: settings.productId,
|
|
65
|
+
productType: settings.productType,
|
|
66
|
+
classType: settings.classType,
|
|
67
|
+
classHashId: settings.classHashId,
|
|
68
|
+
owner_id: settings?.owner_id,
|
|
69
|
+
current_media_codes: settings?.current_media_codes,
|
|
70
|
+
question: questionParam,
|
|
71
|
+
router: questionParam ? 'summary' : undefined,
|
|
72
|
+
osVersion: browserInfo.version,
|
|
73
|
+
platformDetail: browserInfo.name,
|
|
74
|
+
ucode: settings.user?.ucode
|
|
75
|
+
},
|
|
76
|
+
externalId: v4(),
|
|
77
|
+
namespace: settings.namespace,
|
|
78
|
+
sessionId: settings.sessionId,
|
|
79
|
+
userId
|
|
80
|
+
}
|
|
79
81
|
}
|
|
80
82
|
})
|
|
81
83
|
},
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Message } from '@hotmart/sparkie/dist/MessageService'
|
|
2
|
+
|
|
3
|
+
import { api } from '@/src/config/request'
|
|
4
|
+
|
|
5
|
+
import { MessagesEndpoints } from './constants'
|
|
6
|
+
import type {
|
|
7
|
+
DirectMessagesServiceProps,
|
|
8
|
+
FetchMessagesResponse,
|
|
9
|
+
IMessageWithSenderData
|
|
10
|
+
} from './types'
|
|
11
|
+
|
|
12
|
+
class DirectMessagesService {
|
|
13
|
+
async getAll({ conversationId, before, limit }: DirectMessagesServiceProps['GetAll']) {
|
|
14
|
+
const { data: messages } = await api.get<IMessageWithSenderData[]>(
|
|
15
|
+
MessagesEndpoints.getAll(conversationId),
|
|
16
|
+
{
|
|
17
|
+
params: {
|
|
18
|
+
before,
|
|
19
|
+
limit
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
messages,
|
|
26
|
+
hasMore: messages.length === limit
|
|
27
|
+
} as FetchMessagesResponse
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async create({ conversationId, message }: DirectMessagesServiceProps['Create']) {
|
|
31
|
+
const { data } = await api.post<Message>(MessagesEndpoints.create(conversationId), {
|
|
32
|
+
...message,
|
|
33
|
+
type: 'message',
|
|
34
|
+
channel: 'chat'
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
return data
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default new DirectMessagesService()
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
Message as SparkieMsg,
|
|
3
|
+
MessageContent,
|
|
4
|
+
SenderData
|
|
5
|
+
} from '@hotmart/sparkie/dist/MessageService'
|
|
2
6
|
|
|
3
7
|
export type IMessage = SparkieMsg & {
|
|
4
8
|
metadata: {
|
|
@@ -80,3 +84,20 @@ export type FetchMessagesResponse = {
|
|
|
80
84
|
export type SubmitQuestionEventDetail = {
|
|
81
85
|
timestamp: number
|
|
82
86
|
}
|
|
87
|
+
|
|
88
|
+
export type DirectMessagesServiceProps = {
|
|
89
|
+
GetAll: {
|
|
90
|
+
conversationId: string
|
|
91
|
+
before: number
|
|
92
|
+
limit: number
|
|
93
|
+
}
|
|
94
|
+
Create: {
|
|
95
|
+
conversationId: string
|
|
96
|
+
message: {
|
|
97
|
+
content: MessageContent
|
|
98
|
+
threadId?: string
|
|
99
|
+
parentId?: string
|
|
100
|
+
metadata?: Record<string, unknown>
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './use-init-sparkie'
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { useWidgetSettingsAtomValue } from '@/src/modules/widget'
|
|
4
|
+
import { SparkieService } from '../..'
|
|
5
|
+
|
|
6
|
+
function useInitSparkie() {
|
|
7
|
+
const [isSuccess, setIsSuccess] = useState(false)
|
|
8
|
+
const settings = useWidgetSettingsAtomValue()
|
|
9
|
+
|
|
10
|
+
const init = useCallback(async () => {
|
|
11
|
+
if (!settings?.hotmartToken) return
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
await SparkieService.initSparkie({
|
|
15
|
+
token: settings?.hotmartToken,
|
|
16
|
+
skipPresenceSetup: true,
|
|
17
|
+
retryOptions: {
|
|
18
|
+
maxRetries: 5,
|
|
19
|
+
retryDelay: 2000,
|
|
20
|
+
backoffMultiplier: 1.5
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
await SparkieService.ensureInitialized()
|
|
24
|
+
setIsSuccess(true)
|
|
25
|
+
} catch {
|
|
26
|
+
setIsSuccess(false)
|
|
27
|
+
// TODO: Create Error PAGE and setTab
|
|
28
|
+
// setTab('information')
|
|
29
|
+
}
|
|
30
|
+
}, [settings?.hotmartToken])
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
void init()
|
|
34
|
+
}, [init])
|
|
35
|
+
|
|
36
|
+
return isSuccess
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default useInitSparkie
|
|
@@ -1,24 +1,16 @@
|
|
|
1
|
-
import { useEffect } from 'react'
|
|
2
|
-
|
|
3
1
|
import { useSubscribeMessageReceivedEvent } from '@/src/modules/messages/hooks'
|
|
4
2
|
import { useSubscribeThreadClosedEvent } from '@/src/modules/thread/hooks'
|
|
5
3
|
import { useListenToVisibilityEvents } from '../../hooks'
|
|
6
4
|
import { useWidgetTabsAtom } from '../../store'
|
|
7
5
|
import { WIDGET_TABS } from '../constants'
|
|
8
6
|
|
|
9
|
-
function WidgetContainer(
|
|
10
|
-
const [widgetTabs
|
|
7
|
+
function WidgetContainer() {
|
|
8
|
+
const [widgetTabs] = useWidgetTabsAtom()
|
|
11
9
|
|
|
12
10
|
useSubscribeMessageReceivedEvent()
|
|
13
11
|
useSubscribeThreadClosedEvent()
|
|
14
12
|
useListenToVisibilityEvents()
|
|
15
13
|
|
|
16
|
-
useEffect(() => {
|
|
17
|
-
if (completeSetup) {
|
|
18
|
-
setTab('starter')
|
|
19
|
-
}
|
|
20
|
-
}, [completeSetup, setTab])
|
|
21
|
-
|
|
22
14
|
return (
|
|
23
15
|
<div className='flex h-full flex-col items-center justify-stretch overflow-hidden'>
|
|
24
16
|
{WIDGET_TABS[widgetTabs.currentTab]}
|
|
@@ -8,6 +8,10 @@ vi.mock('@/src/modules/messages/hooks', () => ({
|
|
|
8
8
|
useSendTextMessage: vi.fn()
|
|
9
9
|
}))
|
|
10
10
|
|
|
11
|
+
vi.mock('@/src/modules/sparkie/hooks/use-init-sparkie', () => ({
|
|
12
|
+
useInitSparkie: vi.fn(() => true)
|
|
13
|
+
}))
|
|
14
|
+
|
|
11
15
|
describe('WidgetStarterPage', () => {
|
|
12
16
|
const useSendTextMessageMock = { mutate: vi.fn() }
|
|
13
17
|
|
|
@@ -5,11 +5,13 @@ import type { MouseEventHandler } from 'react'
|
|
|
5
5
|
import { useTranslation } from 'react-i18next'
|
|
6
6
|
|
|
7
7
|
import { Button, HorizontalDraggableScroll } from '@/src/lib/components'
|
|
8
|
+
import type { ValidIconNames } from '@/src/lib/components/icons/icon-names'
|
|
8
9
|
import { useRefEventListener } from '@/src/lib/hooks'
|
|
9
10
|
import { ChatInput, useChatInputValueAtom } from '@/src/modules/messages/components'
|
|
10
11
|
import { getAllMessagesQuery, useSendTextMessage } from '@/src/modules/messages/hooks'
|
|
11
12
|
import { useMessagesMaxCount } from '@/src/modules/messages/store'
|
|
12
13
|
import { useGetProfile } from '@/src/modules/profile'
|
|
14
|
+
import { useInitSparkie } from '@/src/modules/sparkie/hooks/use-init-sparkie'
|
|
13
15
|
import { useWidgetSettingsAtom, useWidgetTabsAtom } from '../../store'
|
|
14
16
|
import { GreetingsCard } from '../greetings-card'
|
|
15
17
|
import { WidgetHeader } from '../header'
|
|
@@ -28,6 +30,7 @@ function WidgetStarterPage() {
|
|
|
28
30
|
const limit = useMessagesMaxCount()
|
|
29
31
|
const queryClient = useQueryClient()
|
|
30
32
|
const name = settings?.tutorName ?? t('general.name')
|
|
33
|
+
const isSparkieReady = useInitSparkie()
|
|
31
34
|
|
|
32
35
|
useRefEventListener<HTMLTextAreaElement>({
|
|
33
36
|
config: {
|
|
@@ -74,9 +77,19 @@ function WidgetStarterPage() {
|
|
|
74
77
|
[conversationId, limit, profileId]
|
|
75
78
|
)
|
|
76
79
|
|
|
80
|
+
const headerBtns = useMemo(() => {
|
|
81
|
+
const btnList = ['info', 'close']
|
|
82
|
+
|
|
83
|
+
if (!isSparkieReady) return btnList
|
|
84
|
+
|
|
85
|
+
return btnList.concat(['archive'])
|
|
86
|
+
}, [isSparkieReady]) as ValidIconNames[]
|
|
87
|
+
|
|
77
88
|
useEffect(() => {
|
|
89
|
+
if (!conversationId || !profileId) return
|
|
90
|
+
|
|
78
91
|
void queryClient.prefetchInfiniteQuery(messagesQueryConfig)
|
|
79
|
-
}, [messagesQueryConfig, queryClient])
|
|
92
|
+
}, [conversationId, messagesQueryConfig, profileId, queryClient])
|
|
80
93
|
|
|
81
94
|
return (
|
|
82
95
|
<PageLayout
|
|
@@ -85,7 +98,7 @@ function WidgetStarterPage() {
|
|
|
85
98
|
name='new-chat-msg-input'
|
|
86
99
|
ref={chatInputRef}
|
|
87
100
|
onSend={handleSend}
|
|
88
|
-
buttonDisabled={!chatInputValue.trim()}
|
|
101
|
+
buttonDisabled={!chatInputValue.trim() || !isSparkieReady}
|
|
89
102
|
/>
|
|
90
103
|
}>
|
|
91
104
|
<div className='grid-areas-[a_b] grid h-full grid-cols-1 grid-rows-[1fr_auto]'>
|
|
@@ -93,7 +106,7 @@ function WidgetStarterPage() {
|
|
|
93
106
|
className={clsx('grid-area-[a] flex min-h-0 flex-col max-md:p-[1.125rem] md:p-5', {
|
|
94
107
|
[styles.bg]: settings?.config?.theme === 'dark'
|
|
95
108
|
})}>
|
|
96
|
-
<WidgetHeader enabledButtons={
|
|
109
|
+
<WidgetHeader enabledButtons={headerBtns} tutorName={name} />
|
|
97
110
|
|
|
98
111
|
<div className='my-auto'>
|
|
99
112
|
<GreetingsCard
|
|
@@ -107,14 +120,16 @@ function WidgetStarterPage() {
|
|
|
107
120
|
<Button
|
|
108
121
|
variant='gradient-outline'
|
|
109
122
|
className='shrink-0 snap-end text-sm'
|
|
110
|
-
onClick={handleAskQuestion}
|
|
123
|
+
onClick={handleAskQuestion}
|
|
124
|
+
loading={!isSparkieReady}>
|
|
111
125
|
<span>🤖 </span>
|
|
112
126
|
{t('starter_page.what_does_tutor_do')}
|
|
113
127
|
</Button>
|
|
114
128
|
<Button
|
|
115
129
|
variant='gradient-outline'
|
|
116
130
|
className='shrink-0 snap-end text-sm'
|
|
117
|
-
onClick={handleAskQuestion}
|
|
131
|
+
onClick={handleAskQuestion}
|
|
132
|
+
loading={!isSparkieReady}>
|
|
118
133
|
<span>📝 </span>
|
|
119
134
|
{t('starter_page.wanna_summary')}
|
|
120
135
|
</Button>
|
|
@@ -3,7 +3,6 @@ import { useEffect, useState } from 'react'
|
|
|
3
3
|
import { DataHubStore } from '@/src/config/datahub'
|
|
4
4
|
import { initDayjs } from '@/src/config/dayjs'
|
|
5
5
|
import { initAxios } from '@/src/config/request/api'
|
|
6
|
-
import { SparkieService } from '@/src/modules/sparkie'
|
|
7
6
|
import type { WidgetSettingProps } from '@/src/types'
|
|
8
7
|
import { TutorWidgetEvents } from '../../events'
|
|
9
8
|
|
|
@@ -18,16 +17,6 @@ const init = async (settings: WidgetSettingProps) => {
|
|
|
18
17
|
sessionId: settings.sessionId,
|
|
19
18
|
userId: Number(settings.userId)
|
|
20
19
|
})
|
|
21
|
-
await SparkieService.initSparkie({
|
|
22
|
-
token: settings?.hotmartToken,
|
|
23
|
-
skipPresenceSetup: true,
|
|
24
|
-
retryOptions: {
|
|
25
|
-
maxRetries: 5,
|
|
26
|
-
retryDelay: 2000,
|
|
27
|
-
backoffMultiplier: 1.5
|
|
28
|
-
}
|
|
29
|
-
})
|
|
30
|
-
await SparkieService.ensureInitialized()
|
|
31
20
|
TutorWidgetEvents['tutor-app-widget-loaded'].dispatch()
|
|
32
21
|
} catch (error) {
|
|
33
22
|
console.error(error)
|
|
@@ -8,8 +8,8 @@ export type WidgetTabsProps = {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
const INITIAL_PROPS: WidgetTabsProps = {
|
|
11
|
-
currentTab: '
|
|
12
|
-
history: new Set(['
|
|
11
|
+
currentTab: 'starter',
|
|
12
|
+
history: new Set(['starter'])
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export const widgetTabsAtom = atom<WidgetTabsProps>(INITIAL_PROPS)
|