app-tutor-ai-consumer 1.18.1 → 1.19.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 +8 -0
- package/package.json +1 -1
- package/src/config/tests/handlers.ts +12 -0
- package/src/development-bootstrap.tsx +5 -2
- package/src/index.tsx +3 -0
- package/src/lib/components/button/button.tsx +105 -14
- package/src/lib/components/button/styles.module.css +9 -0
- package/src/lib/components/icons/arrow-up.svg +5 -0
- package/src/lib/components/icons/copy.svg +5 -0
- package/src/lib/components/icons/gallery.svg +3 -0
- package/src/lib/components/icons/icon-names.d.ts +4 -1
- package/src/lib/components/icons/interrogation.svg +2 -8
- package/src/lib/components/icons/like.svg +5 -0
- package/src/modules/messages/components/message-actions/index.ts +2 -0
- package/src/modules/messages/components/message-actions/message-actions.tsx +49 -0
- package/src/modules/messages/components/message-item/message-item.tsx +21 -5
- package/src/modules/messages/components/message-item-error/message-item-error.tsx +16 -9
- package/src/modules/messages/components/message-skeleton/message-skeleton.tsx +1 -4
- package/src/modules/messages/components/messages-container/index.ts +2 -0
- package/src/modules/messages/components/messages-container/messages-container.tsx +91 -0
- package/src/modules/messages/components/messages-list/messages-list.tsx +9 -82
- package/src/modules/messages/constants.ts +5 -0
- package/src/modules/messages/events.ts +12 -4
- package/src/modules/messages/hooks/index.ts +1 -0
- package/src/modules/messages/hooks/use-all-messages/use-all-messages.tsx +1 -2
- package/src/modules/messages/hooks/use-infinite-get-messages/use-infinite-get-messages.spec.tsx +18 -19
- package/src/modules/messages/hooks/use-infinite-get-messages/use-infinite-get-messages.tsx +41 -35
- package/src/modules/messages/hooks/use-scroller/index.ts +2 -0
- package/src/modules/messages/hooks/use-scroller/use-scroller.tsx +50 -0
- package/src/modules/messages/hooks/use-send-text-message/use-send-text-message.tsx +31 -2
- package/src/modules/messages/hooks/use-subscribe-message-received-event/use-subscribe-message-received-event.tsx +47 -64
- package/src/modules/messages/store/index.ts +1 -0
- package/src/modules/messages/store/messages-max-count.atom.ts +13 -0
- package/src/modules/messages/utils/index.ts +2 -0
- package/src/modules/messages/utils/set-messages-cache/index.ts +1 -0
- package/src/modules/messages/utils/set-messages-cache/utils.ts +53 -0
- package/src/modules/widget/components/chat-page/chat-page.spec.tsx +23 -7
- package/src/modules/widget/components/chat-page/chat-page.tsx +70 -14
- package/src/modules/widget/components/greetings-card/greetings-card.tsx +19 -15
- package/src/modules/widget/components/header/header.tsx +6 -4
- package/src/modules/widget/components/information-page/constants.ts +6 -2
- package/src/modules/widget/components/information-page/information-card/information-card.tsx +22 -5
- package/src/modules/widget/components/information-page/information-page.tsx +16 -3
- package/src/modules/widget/components/starter-page/starter-page.spec.tsx +4 -1
- package/src/modules/widget/components/starter-page/starter-page.tsx +30 -5
- package/src/modules/widget/components/greetings-card/styles.module.css +0 -3
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Message } from '@hotmart/sparkie/dist/MessageService'
|
|
2
|
+
import type { InfiniteData, QueryClient } from '@tanstack/react-query'
|
|
3
|
+
import { produce } from 'immer'
|
|
4
|
+
|
|
5
|
+
import type { FetchMessagesResponse, IMessageWithSenderData } from '../../types'
|
|
6
|
+
|
|
7
|
+
const placeholderID = 'remove::placeholder::id'
|
|
8
|
+
|
|
9
|
+
export type SetMessageCacheParams = {
|
|
10
|
+
queryKey: readonly unknown[]
|
|
11
|
+
queryClient: QueryClient
|
|
12
|
+
data: Partial<Message>
|
|
13
|
+
sending?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const setMessagesCache =
|
|
17
|
+
({ queryClient, queryKey, data, sending }: SetMessageCacheParams) =>
|
|
18
|
+
() => {
|
|
19
|
+
queryClient.setQueryData<InfiniteData<FetchMessagesResponse>>(queryKey, (oldData) => {
|
|
20
|
+
return produce(oldData, (draft) => {
|
|
21
|
+
const lastPageMessages = draft?.pages?.at(-1)?.messages
|
|
22
|
+
const lastMessage = lastPageMessages?.at?.(-1)
|
|
23
|
+
|
|
24
|
+
const placeholderMsgIndex = Number(
|
|
25
|
+
lastPageMessages?.findIndex((msg) => msg.id === placeholderID)
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
const msgIndex = Number(
|
|
29
|
+
!isNaN(placeholderMsgIndex) && placeholderMsgIndex !== -1
|
|
30
|
+
? placeholderMsgIndex
|
|
31
|
+
: lastPageMessages?.length
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
if (isNaN(msgIndex)) return draft
|
|
35
|
+
|
|
36
|
+
const newMessage = {
|
|
37
|
+
...lastMessage,
|
|
38
|
+
sentAt: Date.now(),
|
|
39
|
+
updatedAt: Date.now(),
|
|
40
|
+
metadata: {
|
|
41
|
+
...lastMessage?.metadata,
|
|
42
|
+
author: 'user'
|
|
43
|
+
},
|
|
44
|
+
...data,
|
|
45
|
+
...(sending ? { id: placeholderID } : {})
|
|
46
|
+
} as IMessageWithSenderData
|
|
47
|
+
|
|
48
|
+
lastPageMessages?.splice(msgIndex, 1, newMessage)
|
|
49
|
+
|
|
50
|
+
return draft
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
}
|
|
@@ -1,27 +1,43 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import
|
|
1
|
+
import { createRef } from 'react'
|
|
2
|
+
|
|
3
|
+
import { chance, render, screen, waitFor } from '@/src/config/tests'
|
|
4
|
+
import { useScroller } from '@/src/modules/messages/hooks/use-scroller'
|
|
5
|
+
import { useGetProfile } from '@/src/modules/profile'
|
|
4
6
|
import WidgetSettingPropsBuilder from '../../__tests__/widget-settings-props.builder'
|
|
5
7
|
import * as Store from '../../store'
|
|
6
8
|
|
|
7
9
|
import ChatPage from './chat-page'
|
|
8
10
|
|
|
11
|
+
vi.mock('@/src/modules/profile', () => ({ useGetProfile: vi.fn() }))
|
|
12
|
+
|
|
13
|
+
vi.mock('@/src/modules/messages/hooks/use-scroller', () => ({
|
|
14
|
+
useScroller: vi.fn()
|
|
15
|
+
}))
|
|
16
|
+
|
|
9
17
|
describe('ChatPage', () => {
|
|
10
18
|
const defaultSettings = new WidgetSettingPropsBuilder()
|
|
11
|
-
const
|
|
19
|
+
const scrollerRef = createRef<HTMLDivElement>()
|
|
20
|
+
const scrollToButtonRef = createRef<HTMLButtonElement>()
|
|
21
|
+
|
|
22
|
+
const useScrollerMock = {
|
|
23
|
+
scrollerRef,
|
|
24
|
+
scrollToButtonRef,
|
|
25
|
+
scrollToBottom: vi.fn(),
|
|
26
|
+
showScrollButton: false
|
|
27
|
+
}
|
|
12
28
|
|
|
13
29
|
const renderComponent = () => render(<ChatPage />)
|
|
14
30
|
|
|
15
31
|
beforeEach(() => {
|
|
16
32
|
vi.spyOn(Store, 'useWidgetSettingsAtomValue').mockReturnValue(defaultSettings)
|
|
33
|
+
vi.mocked(useGetProfile).mockReturnValue({ data: { id: chance.guid() } } as never)
|
|
34
|
+
vi.mocked(useScroller).mockReturnValue(useScrollerMock)
|
|
17
35
|
})
|
|
18
36
|
|
|
19
37
|
it('should render each fetched message item from API', async () => {
|
|
20
38
|
renderComponent()
|
|
21
39
|
|
|
22
|
-
await waitFor(() =>
|
|
23
|
-
expect(screen.getAllByTestId('messages-item')).toHaveLength(getMessagesMock.length)
|
|
24
|
-
)
|
|
40
|
+
await waitFor(() => expect(screen.getAllByTestId('messages-item')).toHaveLength(2))
|
|
25
41
|
|
|
26
42
|
expect(screen.getByPlaceholderText(/send_message.field.placeholder/)).toBeInTheDocument()
|
|
27
43
|
})
|
|
@@ -1,24 +1,55 @@
|
|
|
1
|
-
import { useRef } from 'react'
|
|
1
|
+
import { lazy, useEffect, useMemo, useRef } from 'react'
|
|
2
|
+
import { useInfiniteQuery } from '@tanstack/react-query'
|
|
2
3
|
|
|
3
4
|
import { isTextEmpty } from '@/src/lib/utils/is-text-empty'
|
|
4
5
|
import { ChatInput, MessagesList, useChatInputValueAtom } from '@/src/modules/messages/components'
|
|
5
|
-
import {
|
|
6
|
+
import { MessagesContainer } from '@/src/modules/messages/components/messages-container'
|
|
7
|
+
import { getAllMessagesQuery, useSendTextMessage } from '@/src/modules/messages/hooks'
|
|
8
|
+
import { useMessagesMaxCount } from '@/src/modules/messages/store'
|
|
9
|
+
import { useGetProfile } from '@/src/modules/profile'
|
|
6
10
|
import {
|
|
7
|
-
|
|
11
|
+
useWidgetLoadingAtom,
|
|
8
12
|
useWidgetSettingsAtomValue,
|
|
9
13
|
useWidgetTabsValueAtom
|
|
10
14
|
} from '../../store'
|
|
11
15
|
import { WidgetHeader } from '../header'
|
|
12
16
|
import { PageLayout } from '../page-layout'
|
|
13
17
|
|
|
18
|
+
const MessageItemError = lazy(
|
|
19
|
+
() => import('@/src/modules/messages/components/message-item-error/message-item-error')
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
const MessageItemEndOfScroll = lazy(
|
|
23
|
+
() =>
|
|
24
|
+
import(
|
|
25
|
+
'@/src/modules/messages/components/message-item-end-of-scroll/message-item-end-of-scroll'
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
|
|
14
29
|
function ChatPage() {
|
|
15
|
-
const widgetTabs = useWidgetTabsValueAtom()
|
|
16
30
|
const chatInputRef = useRef<HTMLTextAreaElement>(null)
|
|
31
|
+
const scrollerRef = useRef<HTMLDivElement>(null)
|
|
32
|
+
const settings = useWidgetSettingsAtomValue()
|
|
33
|
+
const profileQuery = useGetProfile()
|
|
34
|
+
const widgetTabs = useWidgetTabsValueAtom()
|
|
17
35
|
const sendTextMessageMutation = useSendTextMessage()
|
|
18
|
-
const
|
|
19
|
-
const widgetLoading = useWidgetLoadingAtomValue()
|
|
36
|
+
const limit = useMessagesMaxCount()
|
|
20
37
|
const [value, setValue] = useChatInputValueAtom()
|
|
21
|
-
const
|
|
38
|
+
const [, setWidgetLoading] = useWidgetLoadingAtom()
|
|
39
|
+
|
|
40
|
+
const conversationId = useMemo(() => settings?.conversationId, [settings?.conversationId])
|
|
41
|
+
const profileId = useMemo(() => profileQuery.data?.id, [profileQuery.data?.id])
|
|
42
|
+
const messagesQueryConfig = useMemo(
|
|
43
|
+
() =>
|
|
44
|
+
getAllMessagesQuery({
|
|
45
|
+
conversationId,
|
|
46
|
+
profileId,
|
|
47
|
+
limit
|
|
48
|
+
}),
|
|
49
|
+
[conversationId, limit, profileId]
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
const messagesQuery = useInfiniteQuery(messagesQueryConfig)
|
|
22
53
|
|
|
23
54
|
const handleSendMessage = () => {
|
|
24
55
|
const text = chatInputRef.current?.value ?? ''
|
|
@@ -33,6 +64,12 @@ function ChatPage() {
|
|
|
33
64
|
})
|
|
34
65
|
}
|
|
35
66
|
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (messagesQuery.isError) {
|
|
69
|
+
setWidgetLoading(false)
|
|
70
|
+
}
|
|
71
|
+
}, [messagesQuery.isError, setWidgetLoading])
|
|
72
|
+
|
|
36
73
|
return (
|
|
37
74
|
<PageLayout
|
|
38
75
|
asideChild={
|
|
@@ -40,18 +77,37 @@ function ChatPage() {
|
|
|
40
77
|
name='new-chat-msg-input'
|
|
41
78
|
ref={chatInputRef}
|
|
42
79
|
onSend={widgetTabs.currentTab === 'chat' ? handleSendMessage : undefined}
|
|
43
|
-
loading={
|
|
80
|
+
loading={sendTextMessageMutation.isPending}
|
|
44
81
|
inputDisabled={messagesQuery?.isLoading}
|
|
45
82
|
buttonDisabled={messagesQuery?.isLoading || !value.trim()}
|
|
46
83
|
/>
|
|
47
84
|
}>
|
|
48
|
-
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
85
|
+
<div className='mt-4 px-6 py-4'>
|
|
86
|
+
<WidgetHeader enabledButtons={['info', 'close']} tutorName={settings?.tutorName} />
|
|
87
|
+
</div>
|
|
88
|
+
<MessagesContainer
|
|
89
|
+
ref={scrollerRef}
|
|
90
|
+
handleShowMore={async () => {
|
|
91
|
+
await messagesQuery.fetchNextPage()
|
|
92
|
+
}}
|
|
93
|
+
showButton={messagesQuery.hasNextPage}
|
|
94
|
+
loading={messagesQuery.isFetchingNextPage}>
|
|
95
|
+
<MessageItemEndOfScroll
|
|
96
|
+
show={
|
|
97
|
+
!messagesQuery.isFetching &&
|
|
98
|
+
!messagesQuery.hasNextPage &&
|
|
99
|
+
Number(messagesQuery.data?.size) > 0
|
|
100
|
+
}
|
|
101
|
+
/>
|
|
102
|
+
{messagesQuery.data && <MessagesList messagesMap={messagesQuery.data} />}
|
|
103
|
+
<MessageItemError
|
|
104
|
+
show={messagesQuery.isError}
|
|
105
|
+
message={`❌ Error loading messages: ${messagesQuery.error?.message ?? ''}`}
|
|
106
|
+
retry={() => void messagesQuery.refetch()}
|
|
107
|
+
/>
|
|
108
|
+
</MessagesContainer>
|
|
54
109
|
</PageLayout>
|
|
55
110
|
)
|
|
56
111
|
}
|
|
112
|
+
|
|
57
113
|
export default ChatPage
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import clsx from 'clsx'
|
|
2
2
|
import { useTranslation } from 'react-i18next'
|
|
3
3
|
|
|
4
|
-
import { AIAvatar } from '../ai-avatar'
|
|
5
|
-
|
|
6
|
-
import styles from './styles.module.css'
|
|
7
|
-
|
|
8
4
|
export type GreetingsCardProps = {
|
|
9
5
|
tutorName: string
|
|
10
|
-
author
|
|
6
|
+
author?: string
|
|
11
7
|
isDarkTheme?: boolean
|
|
12
8
|
}
|
|
13
9
|
|
|
@@ -15,23 +11,31 @@ function GreetingsCard({ author, tutorName, isDarkTheme = false }: GreetingsCard
|
|
|
15
11
|
const { t } = useTranslation()
|
|
16
12
|
|
|
17
13
|
return (
|
|
18
|
-
<div className='flex flex-col items-center justify-center
|
|
14
|
+
<div className='flex flex-col items-center justify-center'>
|
|
19
15
|
<div className='flex flex-col items-center justify-center gap-4 text-center'>
|
|
20
|
-
<AIAvatar
|
|
21
|
-
className={clsx('rounded-full border-4 border-neutral-100', {
|
|
22
|
-
'bg-[#E7EDF2]/80': !isDarkTheme,
|
|
23
|
-
'bg-neutral-200': isDarkTheme
|
|
24
|
-
})}
|
|
25
|
-
/>
|
|
26
16
|
<div className='flex flex-col gap-2'>
|
|
27
|
-
<span
|
|
17
|
+
<span
|
|
18
|
+
className={clsx('text-base font-light', {
|
|
19
|
+
'text-white': isDarkTheme,
|
|
20
|
+
'text-gray-900': !isDarkTheme
|
|
21
|
+
})}>
|
|
28
22
|
{t('general.greetings.hello', { name: author })}
|
|
29
23
|
</span>
|
|
30
|
-
<h3
|
|
24
|
+
<h3
|
|
25
|
+
className={clsx('text-xl font-bold', {
|
|
26
|
+
'text-white': isDarkTheme,
|
|
27
|
+
'text-gray-900': !isDarkTheme
|
|
28
|
+
})}>
|
|
31
29
|
{t('general.greetings.firstMessage', { tutorName })}
|
|
32
30
|
</h3>
|
|
33
31
|
</div>
|
|
34
|
-
<p
|
|
32
|
+
<p
|
|
33
|
+
className={clsx('text-sm font-normal', {
|
|
34
|
+
'text-gray-400': isDarkTheme,
|
|
35
|
+
'text-neutral-600': !isDarkTheme
|
|
36
|
+
})}>
|
|
37
|
+
{t('general.greetings.description')}
|
|
38
|
+
</p>
|
|
35
39
|
</div>
|
|
36
40
|
</div>
|
|
37
41
|
)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { useTranslation } from 'react-i18next'
|
|
2
|
+
|
|
1
3
|
import { Button, Icon } from '@/src/lib/components'
|
|
2
4
|
import { TutorWidgetEvents } from '../../events'
|
|
3
5
|
import { useWidgetTabsAtom } from '../../store'
|
|
@@ -35,7 +37,9 @@ function WidgetHeader({
|
|
|
35
37
|
showContentWithoutMeta,
|
|
36
38
|
showContent = true
|
|
37
39
|
}: WidgetHeaderProps) {
|
|
40
|
+
const { t } = useTranslation()
|
|
38
41
|
const [, setTab] = useWidgetTabsAtom()
|
|
42
|
+
const name = tutorName ?? t('general.name')
|
|
39
43
|
|
|
40
44
|
const handleClickArchive = () => {
|
|
41
45
|
setTab('chat')
|
|
@@ -48,10 +52,8 @@ function WidgetHeader({
|
|
|
48
52
|
return (
|
|
49
53
|
<div className='grid-areas-[a_b] mt-0.5 grid grid-cols-[1fr_auto] items-center text-neutral-1000'>
|
|
50
54
|
<div className='grid-area-[a]'>
|
|
51
|
-
{showContent && !showContentWithoutMeta && <WidgetHeaderContent tutorName={
|
|
52
|
-
{showContentWithoutMeta && !showContent &&
|
|
53
|
-
<WidgetHeaderContentWithoutMeta name={tutorName} />
|
|
54
|
-
)}
|
|
55
|
+
{showContent && !showContentWithoutMeta && <WidgetHeaderContent tutorName={name} />}
|
|
56
|
+
{showContentWithoutMeta && !showContent && <WidgetHeaderContentWithoutMeta name={name} />}
|
|
55
57
|
</div>
|
|
56
58
|
<div className='shrink-0'>
|
|
57
59
|
<div className='grid-area-[b] ml-auto flex max-w-max gap-3 text-neutral-700'>
|
|
@@ -12,6 +12,10 @@ export const infoItems: InfoItem[] = [
|
|
|
12
12
|
titleKey: 'info.what_it_does_question',
|
|
13
13
|
descKey: 'info.what_it_does_answer'
|
|
14
14
|
},
|
|
15
|
-
{
|
|
16
|
-
|
|
15
|
+
{
|
|
16
|
+
icon: 'gallery',
|
|
17
|
+
titleKey: 'info.how_it_learns_question',
|
|
18
|
+
descKey: 'info.how_it_learns_answer'
|
|
19
|
+
},
|
|
20
|
+
{ icon: 'info', titleKey: 'info.limitations_question', descKey: 'info.limitations_answer' }
|
|
17
21
|
]
|
package/src/modules/widget/components/information-page/information-card/information-card.tsx
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
2
|
+
|
|
1
3
|
import { Icon } from '@/src/lib/components'
|
|
2
4
|
import type { ValidIconNames } from '@/src/lib/components/icons/icon-names'
|
|
3
5
|
|
|
@@ -5,18 +7,33 @@ export type InformationCardProps = {
|
|
|
5
7
|
icon: ValidIconNames
|
|
6
8
|
title: string
|
|
7
9
|
description: string
|
|
10
|
+
isDarkMode: boolean
|
|
8
11
|
}
|
|
9
12
|
|
|
10
|
-
function InformationCard({ icon, title, description }: InformationCardProps) {
|
|
13
|
+
function InformationCard({ icon, title, description, isDarkMode }: InformationCardProps) {
|
|
11
14
|
return (
|
|
12
|
-
<div
|
|
13
|
-
|
|
15
|
+
<div
|
|
16
|
+
className={clsx('flex justify-center gap-3 border-b pb-5 last:border-none', {
|
|
17
|
+
'border-white/10': isDarkMode,
|
|
18
|
+
'border-black/10': !isDarkMode
|
|
19
|
+
})}>
|
|
20
|
+
<div
|
|
21
|
+
className={clsx('flex h-5 w-5 items-start justify-center', {
|
|
22
|
+
'text-white': isDarkMode,
|
|
23
|
+
'text-neutral-900': !isDarkMode
|
|
24
|
+
})}>
|
|
14
25
|
<Icon name={icon} width={16} height={16} />
|
|
15
26
|
</div>
|
|
16
27
|
|
|
17
28
|
<div className='flex flex-col gap-1'>
|
|
18
|
-
<p className='text-sm font-bold'>{title}</p>
|
|
19
|
-
<p
|
|
29
|
+
<p className='text-sm font-bold text-neutral-900'>{title}</p>
|
|
30
|
+
<p
|
|
31
|
+
className={clsx('text-xs', {
|
|
32
|
+
'text-gray-300': isDarkMode,
|
|
33
|
+
'text-gray-500': !isDarkMode
|
|
34
|
+
})}>
|
|
35
|
+
{description}
|
|
36
|
+
</p>
|
|
20
37
|
</div>
|
|
21
38
|
</div>
|
|
22
39
|
)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
1
2
|
import { useTranslation } from 'react-i18next'
|
|
2
3
|
|
|
3
4
|
import { Icon } from '@/src/lib/components'
|
|
@@ -12,10 +13,15 @@ function WidgetInformationPage() {
|
|
|
12
13
|
const { t } = useTranslation()
|
|
13
14
|
const [, setWidgetTabs] = useWidgetTabsAtom()
|
|
14
15
|
const [settings] = useWidgetSettingsAtom()
|
|
16
|
+
const isDarkMode = settings?.config?.theme === 'dark'
|
|
15
17
|
|
|
16
18
|
return (
|
|
17
19
|
<PageLayout className='p-5 text-white'>
|
|
18
|
-
<div
|
|
20
|
+
<div
|
|
21
|
+
className={clsx('relative mb-8 flex h-12 items-center justify-center', {
|
|
22
|
+
'text-white': isDarkMode,
|
|
23
|
+
'text-neutral-700': !isDarkMode
|
|
24
|
+
})}>
|
|
19
25
|
<button
|
|
20
26
|
className='absolute left-0'
|
|
21
27
|
aria-label='Return Button'
|
|
@@ -25,11 +31,17 @@ function WidgetInformationPage() {
|
|
|
25
31
|
<h1 className='mx-auto font-bold'>{t('info.title')}</h1>
|
|
26
32
|
</div>
|
|
27
33
|
|
|
28
|
-
<div className='mb-
|
|
34
|
+
<div className='mb-10 flex justify-center'>
|
|
29
35
|
<div className='flex flex-col items-center gap-2'>
|
|
30
36
|
<AIAvatar />
|
|
31
37
|
|
|
32
|
-
<h3
|
|
38
|
+
<h3
|
|
39
|
+
className={clsx('font-bold', {
|
|
40
|
+
'text-white': isDarkMode,
|
|
41
|
+
'text-neutral-700': !isDarkMode
|
|
42
|
+
})}>
|
|
43
|
+
{settings?.tutorName ?? t('general.name')}
|
|
44
|
+
</h3>
|
|
33
45
|
</div>
|
|
34
46
|
</div>
|
|
35
47
|
|
|
@@ -40,6 +52,7 @@ function WidgetInformationPage() {
|
|
|
40
52
|
icon={item.icon}
|
|
41
53
|
title={t(item.titleKey)}
|
|
42
54
|
description={t(item.descKey)}
|
|
55
|
+
isDarkMode={isDarkMode}
|
|
43
56
|
/>
|
|
44
57
|
))}
|
|
45
58
|
</div>
|
|
@@ -3,7 +3,10 @@ import { useSendTextMessage } from '@/src/modules/messages/hooks'
|
|
|
3
3
|
|
|
4
4
|
import WidgetStarterPage from './starter-page'
|
|
5
5
|
|
|
6
|
-
vi.mock('@/src/modules/messages/hooks', () => ({
|
|
6
|
+
vi.mock('@/src/modules/messages/hooks', () => ({
|
|
7
|
+
getAllMessagesQuery: vi.fn(() => ({ pages: [], queryKey: ['any'] })),
|
|
8
|
+
useSendTextMessage: vi.fn()
|
|
9
|
+
}))
|
|
7
10
|
|
|
8
11
|
describe('WidgetStarterPage', () => {
|
|
9
12
|
const useSendTextMessageMock = { mutate: vi.fn() }
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { useRef } from 'react'
|
|
1
|
+
import { useEffect, useMemo, useRef } from 'react'
|
|
2
|
+
import { useQueryClient } from '@tanstack/react-query'
|
|
2
3
|
import clsx from 'clsx'
|
|
3
4
|
import type { MouseEventHandler } from 'react'
|
|
4
5
|
import { useTranslation } from 'react-i18next'
|
|
@@ -6,7 +7,9 @@ import { useTranslation } from 'react-i18next'
|
|
|
6
7
|
import { Button } from '@/src/lib/components'
|
|
7
8
|
import { useRefEventListener } from '@/src/lib/hooks'
|
|
8
9
|
import { ChatInput, useChatInputValueAtom } from '@/src/modules/messages/components'
|
|
9
|
-
import { useSendTextMessage } from '@/src/modules/messages/hooks'
|
|
10
|
+
import { getAllMessagesQuery, useSendTextMessage } from '@/src/modules/messages/hooks'
|
|
11
|
+
import { useMessagesMaxCount } from '@/src/modules/messages/store'
|
|
12
|
+
import { useGetProfile } from '@/src/modules/profile'
|
|
10
13
|
import { useWidgetSettingsAtom, useWidgetTabsAtom } from '../../store'
|
|
11
14
|
import { GreetingsCard } from '../greetings-card'
|
|
12
15
|
import { WidgetHeader } from '../header'
|
|
@@ -21,6 +24,10 @@ function WidgetStarterPage() {
|
|
|
21
24
|
const [chatInputValue, setChatInputValue] = useChatInputValueAtom()
|
|
22
25
|
const [, setWidgetTabs] = useWidgetTabsAtom()
|
|
23
26
|
const sendTextMessageMutation = useSendTextMessage()
|
|
27
|
+
const profileQuery = useGetProfile()
|
|
28
|
+
const limit = useMessagesMaxCount()
|
|
29
|
+
const queryClient = useQueryClient()
|
|
30
|
+
const name = settings?.tutorName ?? t('general.name')
|
|
24
31
|
|
|
25
32
|
useRefEventListener<HTMLTextAreaElement>({
|
|
26
33
|
config: {
|
|
@@ -53,6 +60,24 @@ function WidgetStarterPage() {
|
|
|
53
60
|
sendText(chatInputRef.current?.value)
|
|
54
61
|
}
|
|
55
62
|
|
|
63
|
+
const conversationId = useMemo(() => settings?.conversationId, [settings?.conversationId])
|
|
64
|
+
|
|
65
|
+
const profileId = useMemo(() => profileQuery.data?.id, [profileQuery.data?.id])
|
|
66
|
+
|
|
67
|
+
const messagesQueryConfig = useMemo(
|
|
68
|
+
() =>
|
|
69
|
+
getAllMessagesQuery({
|
|
70
|
+
conversationId,
|
|
71
|
+
profileId,
|
|
72
|
+
limit
|
|
73
|
+
}),
|
|
74
|
+
[conversationId, limit, profileId]
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
void queryClient.prefetchInfiniteQuery(messagesQueryConfig)
|
|
79
|
+
}, [messagesQueryConfig, queryClient])
|
|
80
|
+
|
|
56
81
|
return (
|
|
57
82
|
<PageLayout
|
|
58
83
|
asideChild={
|
|
@@ -70,14 +95,14 @@ function WidgetStarterPage() {
|
|
|
70
95
|
})}>
|
|
71
96
|
<WidgetHeader
|
|
72
97
|
enabledButtons={['archive', 'info', 'close']}
|
|
73
|
-
tutorName={
|
|
98
|
+
tutorName={name}
|
|
74
99
|
showContent={false}
|
|
75
100
|
/>
|
|
76
101
|
|
|
77
102
|
<div className='my-auto'>
|
|
78
103
|
<GreetingsCard
|
|
79
|
-
author={settings?.
|
|
80
|
-
tutorName={
|
|
104
|
+
author={settings?.user?.name}
|
|
105
|
+
tutorName={name}
|
|
81
106
|
isDarkTheme={settings?.config?.theme === 'dark'}
|
|
82
107
|
/>
|
|
83
108
|
</div>
|