app-tutor-ai-consumer 1.34.0 → 1.35.1
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 -3
- package/src/config/tanstack/query-client.ts +0 -5
- package/src/config/tanstack/query-provider.tsx +3 -5
- package/src/lib/components/errors/generic/generic-error.tsx +8 -2
- package/src/main/hooks/use-initial-tab/use-initial-tab.tsx +2 -38
- package/src/modules/widget/components/chat-page/chat-page.tsx +58 -0
- package/src/modules/widget/components/error-page/error-page.tsx +3 -1
- package/src/modules/widget/hooks/index.ts +1 -0
- 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/index.ts +1 -0
- package/src/modules/widget/store/widget-last-user-message.atom.ts +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## [1.35.1](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.35.0...v1.35.1) (2025-11-05)
|
|
2
|
+
|
|
3
|
+
# [1.35.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.34.0...v1.35.0) (2025-11-05)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- add retry question ([7b75aba](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/7b75aba715ade6d5a7fec04237f8f13e643d47f5))
|
|
8
|
+
|
|
1
9
|
# [1.34.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.33.1...v1.34.0) (2025-11-04)
|
|
2
10
|
|
|
3
11
|
### Bug Fixes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "app-tutor-ai-consumer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.35.1",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "rspack serve --env=development --config config/rspack/rspack.config.js",
|
|
@@ -111,9 +111,7 @@
|
|
|
111
111
|
"@hotmart-org-ca/sparkie": "~5.1.4",
|
|
112
112
|
"@hotmart/event-agent-js": "~1.1.2",
|
|
113
113
|
"@optimizely/react-sdk": "~3.2.4",
|
|
114
|
-
"@tanstack/query-sync-storage-persister": "~5.80.7",
|
|
115
114
|
"@tanstack/react-query": "~5.80.6",
|
|
116
|
-
"@tanstack/react-query-persist-client": "~5.80.7",
|
|
117
115
|
"clsx": "~2.1.1",
|
|
118
116
|
"dayjs": "~1.11.13",
|
|
119
117
|
"i18next": "~25.2.1",
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister'
|
|
2
1
|
import { QueryClient } from '@tanstack/react-query'
|
|
3
2
|
|
|
4
3
|
import { DEFAULT_STALE_TIME, HttpCodes } from '@/src/lib/utils'
|
|
@@ -26,7 +25,3 @@ export const queryClient = new QueryClient({
|
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
27
|
})
|
|
29
|
-
|
|
30
|
-
export const persister = createSyncStoragePersister({
|
|
31
|
-
storage: window.localStorage
|
|
32
|
-
})
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import type { QueryClient } from '@tanstack/react-query'
|
|
2
|
+
import { QueryClientProvider } from '@tanstack/react-query'
|
|
2
3
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
|
3
|
-
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
|
|
4
4
|
import type { PropsWithChildren } from 'react'
|
|
5
5
|
|
|
6
|
-
import { persister } from './query-client'
|
|
7
|
-
|
|
8
6
|
export type QueryProviderProps = PropsWithChildren<{
|
|
9
7
|
showDevTools?: boolean
|
|
10
8
|
queryClient: QueryClient
|
|
@@ -12,10 +10,10 @@ export type QueryProviderProps = PropsWithChildren<{
|
|
|
12
10
|
|
|
13
11
|
function QueryProvider({ children, queryClient, showDevTools = true }: QueryProviderProps) {
|
|
14
12
|
return (
|
|
15
|
-
<
|
|
13
|
+
<QueryClientProvider client={queryClient}>
|
|
16
14
|
{children}
|
|
17
15
|
{showDevTools && <ReactQueryDevtools buttonPosition='top-right' />}
|
|
18
|
-
</
|
|
16
|
+
</QueryClientProvider>
|
|
19
17
|
)
|
|
20
18
|
}
|
|
21
19
|
|
|
@@ -6,7 +6,13 @@ import { Button } from '@/src/lib/components'
|
|
|
6
6
|
import { PageLayout, TutorWidgetEvents, useIsAgentParentAtomValue } from '@/src/modules/widget'
|
|
7
7
|
import { WidgetHeader } from '@/src/modules/widget/components/header'
|
|
8
8
|
|
|
9
|
-
function GenericError({
|
|
9
|
+
function GenericError({
|
|
10
|
+
isDarkMode = false,
|
|
11
|
+
onRetry
|
|
12
|
+
}: {
|
|
13
|
+
isDarkMode?: boolean
|
|
14
|
+
onRetry?: () => void
|
|
15
|
+
}) {
|
|
10
16
|
const { t } = useTranslation()
|
|
11
17
|
const isAgentMode = useIsAgentParentAtomValue()
|
|
12
18
|
|
|
@@ -32,7 +38,7 @@ function GenericError({ isDarkMode = false }: { isDarkMode?: boolean }) {
|
|
|
32
38
|
<Button
|
|
33
39
|
variant='brand'
|
|
34
40
|
className='mx-auto w-full max-w-max rounded-lg !px-9 py-2 !font-light'
|
|
35
|
-
onClick={() => window.location.reload()}
|
|
41
|
+
onClick={onRetry ? () => onRetry() : () => window.location.reload()}
|
|
36
42
|
aria-label='Retry Button'>
|
|
37
43
|
{t('general.buttons.try_again')}
|
|
38
44
|
</Button>
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import { useEffect, useMemo
|
|
1
|
+
import { useEffect, useMemo } from 'react'
|
|
2
2
|
|
|
3
3
|
import { useSuspenseMessages } from '@/src/modules/messages/hooks/use-suspense-messages'
|
|
4
4
|
import { useSparkieStateAtomValue } from '@/src/modules/sparkie/store'
|
|
5
|
-
import {
|
|
5
|
+
import { useWidgetTabsAtom } from '@/src/modules/widget'
|
|
6
6
|
import type { MainProps } from '../../types'
|
|
7
7
|
|
|
8
8
|
function useInitialTab({ settings }: MainProps) {
|
|
9
9
|
const [, setTab] = useWidgetTabsAtom()
|
|
10
|
-
const [, setWidgetLoading] = useWidgetLoadingAtom()
|
|
11
10
|
const sparkieState = useSparkieStateAtomValue()
|
|
12
11
|
|
|
13
12
|
const suspenseMessagesQuery = useSuspenseMessages({ conversationId: settings.conversationId })
|
|
@@ -22,16 +21,6 @@ function useInitialTab({ settings }: MainProps) {
|
|
|
22
21
|
[suspenseMessagesQuery.data.size]
|
|
23
22
|
)
|
|
24
23
|
|
|
25
|
-
const hasUserMessageWithoutResponse = useMemo(() => {
|
|
26
|
-
if (!isAgentMode || !suspenseMessagesQuery.data || msgCount === 0) return false
|
|
27
|
-
|
|
28
|
-
const allMessages = Array.from(suspenseMessagesQuery.data.values()).flat()
|
|
29
|
-
const userMessages = allMessages.filter((msg) => msg.metadata.author === 'user')
|
|
30
|
-
const aiMessages = allMessages.filter((msg) => msg.metadata.author !== 'user')
|
|
31
|
-
|
|
32
|
-
return userMessages.length > aiMessages.length && aiMessages.length === 0
|
|
33
|
-
}, [isAgentMode, suspenseMessagesQuery.data, msgCount])
|
|
34
|
-
|
|
35
24
|
const showStarterPage = useMemo(
|
|
36
25
|
() => !isAgentMode && sparkieState === 'initialized' && msgCount === 0,
|
|
37
26
|
[isAgentMode, sparkieState, msgCount]
|
|
@@ -54,31 +43,6 @@ function useInitialTab({ settings }: MainProps) {
|
|
|
54
43
|
|
|
55
44
|
if (showChatPage) return setTab('chat')
|
|
56
45
|
}, [setTab, showChatPage, showErrorPage, showStarterPage])
|
|
57
|
-
|
|
58
|
-
const loadingTimeoutRef = useRef<NodeJS.Timeout | null>(null)
|
|
59
|
-
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
if (hasUserMessageWithoutResponse && showChatPage) {
|
|
62
|
-
setWidgetLoading(true)
|
|
63
|
-
|
|
64
|
-
loadingTimeoutRef.current = setTimeout(() => {
|
|
65
|
-
setWidgetLoading(false)
|
|
66
|
-
setTab('error')
|
|
67
|
-
}, 60000)
|
|
68
|
-
} else {
|
|
69
|
-
if (loadingTimeoutRef.current) {
|
|
70
|
-
clearTimeout(loadingTimeoutRef.current)
|
|
71
|
-
loadingTimeoutRef.current = null
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return () => {
|
|
76
|
-
if (loadingTimeoutRef.current) {
|
|
77
|
-
clearTimeout(loadingTimeoutRef.current)
|
|
78
|
-
loadingTimeoutRef.current = null
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}, [hasUserMessageWithoutResponse, showChatPage, setWidgetLoading, setTab])
|
|
82
46
|
}
|
|
83
47
|
|
|
84
48
|
export default useInitialTab
|
|
@@ -12,8 +12,10 @@ import { useGetProfile } from '@/src/modules/profile'
|
|
|
12
12
|
import { TutorWidgetEvents } from '../../events'
|
|
13
13
|
import { useSendViewTutorEvent } from '../../hooks/use-send-view-tutor-event'
|
|
14
14
|
import {
|
|
15
|
+
useWidgetLastUserMessageAtom,
|
|
15
16
|
useWidgetLoadingAtom,
|
|
16
17
|
useWidgetSettingsAtomValue,
|
|
18
|
+
useWidgetTabsAtom,
|
|
17
19
|
useWidgetTabsValueAtom
|
|
18
20
|
} from '../../store'
|
|
19
21
|
import { testQuestionRegex } from '../../utils'
|
|
@@ -24,13 +26,16 @@ import { PageLayout } from '../page-layout'
|
|
|
24
26
|
function ChatPage() {
|
|
25
27
|
const chatInputRef = useRef<HTMLTextAreaElement>(null)
|
|
26
28
|
const scrollerRef = useRef<HTMLDivElement>(null)
|
|
29
|
+
const loadingTimeoutRef = useRef<NodeJS.Timeout | null>(null)
|
|
27
30
|
const settings = useWidgetSettingsAtomValue()
|
|
28
31
|
const profileQuery = useGetProfile()
|
|
29
32
|
const widgetTabs = useWidgetTabsValueAtom()
|
|
33
|
+
const [, setTab] = useWidgetTabsAtom()
|
|
30
34
|
const sendTextMessageMutation = useSendTextMessage()
|
|
31
35
|
const limit = useMessagesMaxCount()
|
|
32
36
|
const [value, setValue] = useChatInputValueAtom()
|
|
33
37
|
const [widgetLoading, setWidgetLoading] = useWidgetLoadingAtom()
|
|
38
|
+
const [, setLastUserMessage] = useWidgetLastUserMessageAtom()
|
|
34
39
|
const isMobile = useMediaQuery({ maxSize: 'md' })
|
|
35
40
|
const hasSentInitialMessage = useRef(false)
|
|
36
41
|
const [lexTutorInitialMessageFF] = useDecision('lex_tutor_new_widget_initial_message')
|
|
@@ -49,6 +54,36 @@ function ChatPage() {
|
|
|
49
54
|
|
|
50
55
|
const messagesQuery = useInfiniteQuery(messagesQueryConfig)
|
|
51
56
|
|
|
57
|
+
const isAgentMode = useMemo(
|
|
58
|
+
() => settings?.config?.metadata?.parent === 'AGENT',
|
|
59
|
+
[settings?.config?.metadata?.parent]
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
const msgCount = useMemo(() => {
|
|
63
|
+
if (!messagesQuery.data) return 0
|
|
64
|
+
return Array.from(messagesQuery.data.values()).reduce(
|
|
65
|
+
(total: number, messages) => total + messages.length,
|
|
66
|
+
0
|
|
67
|
+
)
|
|
68
|
+
}, [messagesQuery.data])
|
|
69
|
+
|
|
70
|
+
const hasUserMessageWithoutResponse = useMemo(() => {
|
|
71
|
+
if (!isAgentMode || !messagesQuery.data || msgCount === 0) return false
|
|
72
|
+
|
|
73
|
+
const allMessages = Array.from(messagesQuery.data.values()).flat()
|
|
74
|
+
const userMessages = allMessages.filter((msg) => msg?.metadata?.author === 'user')
|
|
75
|
+
const aiMessages = allMessages.filter((msg) => msg?.metadata?.author !== 'user')
|
|
76
|
+
|
|
77
|
+
if (userMessages.length > aiMessages.length && aiMessages.length === 0) {
|
|
78
|
+
const lastUserMsg = userMessages[userMessages.length - 1]
|
|
79
|
+
if (lastUserMsg?.text) {
|
|
80
|
+
setLastUserMessage(lastUserMsg.text)
|
|
81
|
+
}
|
|
82
|
+
return true
|
|
83
|
+
}
|
|
84
|
+
return false
|
|
85
|
+
}, [isAgentMode, messagesQuery.data, msgCount, setLastUserMessage])
|
|
86
|
+
|
|
52
87
|
useSendViewTutorEvent()
|
|
53
88
|
|
|
54
89
|
const handleSendMessage = () => {
|
|
@@ -92,6 +127,29 @@ function ChatPage() {
|
|
|
92
127
|
}
|
|
93
128
|
}, [messagesQuery.isError, setWidgetLoading])
|
|
94
129
|
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (hasUserMessageWithoutResponse) {
|
|
132
|
+
setWidgetLoading(true)
|
|
133
|
+
|
|
134
|
+
loadingTimeoutRef.current = setTimeout(() => {
|
|
135
|
+
setWidgetLoading(false)
|
|
136
|
+
setTab('error')
|
|
137
|
+
}, 60000)
|
|
138
|
+
} else {
|
|
139
|
+
if (loadingTimeoutRef.current) {
|
|
140
|
+
clearTimeout(loadingTimeoutRef.current)
|
|
141
|
+
loadingTimeoutRef.current = null
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return () => {
|
|
146
|
+
if (loadingTimeoutRef.current) {
|
|
147
|
+
clearTimeout(loadingTimeoutRef.current)
|
|
148
|
+
loadingTimeoutRef.current = null
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}, [hasUserMessageWithoutResponse, setWidgetLoading, setTab])
|
|
152
|
+
|
|
95
153
|
return (
|
|
96
154
|
<PageLayout
|
|
97
155
|
asideChild={
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { GenericError } from '@/src/lib/components'
|
|
2
|
+
import { useRetryLastMessage } from '../../hooks'
|
|
2
3
|
import { useWidgetSettingsAtomValue } from '../../store'
|
|
3
4
|
|
|
4
5
|
function WidgetErrorPage() {
|
|
5
6
|
const settings = useWidgetSettingsAtomValue()
|
|
7
|
+
const handleRetry = useRetryLastMessage()
|
|
6
8
|
|
|
7
|
-
return <GenericError isDarkMode={settings?.config?.theme === 'dark'} />
|
|
9
|
+
return <GenericError isDarkMode={settings?.config?.theme === 'dark'} onRetry={handleRetry} />
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
export default WidgetErrorPage
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as useRetryLastMessage } from './use-retry-last-message'
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
|
|
3
|
+
import { useSendTextMessage } from '@/src/modules/messages/hooks'
|
|
4
|
+
import {
|
|
5
|
+
useWidgetLastUserMessageAtom,
|
|
6
|
+
useWidgetLastUserMessageAtomValue,
|
|
7
|
+
useWidgetSettingsAtomValue,
|
|
8
|
+
useWidgetTabsAtom
|
|
9
|
+
} from '../../store'
|
|
10
|
+
|
|
11
|
+
function useRetryLastMessage() {
|
|
12
|
+
const settings = useWidgetSettingsAtomValue()
|
|
13
|
+
const lastUserMessage = useWidgetLastUserMessageAtomValue()
|
|
14
|
+
const [, setLastUserMessage] = useWidgetLastUserMessageAtom()
|
|
15
|
+
const [, setTab] = useWidgetTabsAtom()
|
|
16
|
+
const sendTextMessageMutation = useSendTextMessage()
|
|
17
|
+
|
|
18
|
+
const isAgentMode = useMemo(
|
|
19
|
+
() => settings?.config?.metadata?.parent === 'AGENT',
|
|
20
|
+
[settings?.config?.metadata?.parent]
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
const retryLastMessage = () => {
|
|
24
|
+
if (isAgentMode && lastUserMessage) {
|
|
25
|
+
sendTextMessageMutation.mutate(lastUserMessage, {
|
|
26
|
+
onSuccess() {
|
|
27
|
+
setLastUserMessage('')
|
|
28
|
+
setTab('chat')
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return retryLastMessage
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default useRetryLastMessage
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { atom, useAtom, useAtomValue } from 'jotai'
|
|
2
|
+
|
|
3
|
+
export const widgetLastUserMessageAtom = atom<string | null>(null)
|
|
4
|
+
|
|
5
|
+
export const setWidgetLastUserMessageAtom = atom(
|
|
6
|
+
(get) => get(widgetLastUserMessageAtom),
|
|
7
|
+
(_, set, lastMessage: string) => set(widgetLastUserMessageAtom, lastMessage)
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
export const useWidgetLastUserMessageAtom = () => useAtom(setWidgetLastUserMessageAtom)
|
|
11
|
+
|
|
12
|
+
export const useWidgetLastUserMessageAtomValue = () => useAtomValue(setWidgetLastUserMessageAtom)
|