app-tutor-ai-consumer 1.9.0 → 1.11.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 +12 -0
- package/package.json +1 -1
- package/src/index.tsx +1 -31
- package/src/lib/components/icons/ai-color.svg +8 -16
- package/src/main/main.spec.tsx +1 -1
- package/src/main/main.tsx +5 -17
- package/src/modules/messages/components/chat-input/chat-input.tsx +9 -3
- package/src/modules/messages/hooks/use-send-text-message/use-send-text-message.tsx +4 -1
- package/src/modules/widget/components/constants.tsx +3 -1
- package/src/modules/widget/components/container/container.tsx +14 -3
- package/src/modules/widget/components/index.ts +1 -0
- package/src/modules/widget/components/loading-page/index.ts +1 -0
- package/src/modules/widget/components/loading-page/loading-page.tsx +41 -0
- package/src/modules/widget/hooks/index.ts +1 -0
- package/src/modules/widget/hooks/use-init-widget/index.ts +1 -0
- package/src/modules/widget/hooks/use-init-widget/use-init-widget.tsx +47 -0
- package/src/modules/widget/store/widget-tabs.atom.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
# [1.11.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.10.0...v1.11.0) (2025-07-17)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- adding new tutor icon ([a5f883f](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/a5f883fcb97ef4a3774cc9c9c900aa190f743e79))
|
|
6
|
+
|
|
7
|
+
# [1.10.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.9.0...v1.10.0) (2025-07-17)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- add new loading logic ([ddfcfb6](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/ddfcfb6b5018440a6cd4b5fb2a03d53ee949add7))
|
|
12
|
+
|
|
1
13
|
# [1.9.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.8.2...v1.9.0) (2025-07-17)
|
|
2
14
|
|
|
3
15
|
### Features
|
package/package.json
CHANGED
package/src/index.tsx
CHANGED
|
@@ -4,12 +4,9 @@ import './config/styles/index.css'
|
|
|
4
4
|
import { StrictMode } from 'react'
|
|
5
5
|
import { createRoot } from 'react-dom/client'
|
|
6
6
|
|
|
7
|
-
import { initDayjs } from './config/dayjs'
|
|
8
7
|
import { initLanguage } from './config/i18n'
|
|
9
|
-
import { initAxios } from './config/request/api'
|
|
10
8
|
import { devMode, productionMode } from './lib/utils'
|
|
11
9
|
import { Main } from './main'
|
|
12
|
-
import { SparkieService } from './modules/sparkie'
|
|
13
10
|
import { TutorWidgetEvents } from './modules/widget'
|
|
14
11
|
import type { WidgetSettingProps } from './types'
|
|
15
12
|
|
|
@@ -40,39 +37,12 @@ window.startChatWidget = async (
|
|
|
40
37
|
const rootElement = document.getElementById(elementId) as HTMLElement
|
|
41
38
|
const root = createRoot(rootElement)
|
|
42
39
|
|
|
43
|
-
initAxios(settings.hotmartToken)
|
|
44
40
|
await initLanguage(settings.locale)
|
|
45
|
-
await initDayjs(settings.locale)
|
|
46
|
-
|
|
47
|
-
let isLoadingSparkie: boolean = false
|
|
48
|
-
let initSparkieError: unknown = null
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
isLoadingSparkie = true
|
|
52
|
-
await SparkieService.initSparkie({
|
|
53
|
-
token: settings?.hotmartToken,
|
|
54
|
-
skipPresenceSetup: true,
|
|
55
|
-
retryOptions: {
|
|
56
|
-
maxRetries: 5,
|
|
57
|
-
retryDelay: 2000,
|
|
58
|
-
backoffMultiplier: 1.5
|
|
59
|
-
}
|
|
60
|
-
})
|
|
61
|
-
await SparkieService.ensureInitialized()
|
|
62
|
-
TutorWidgetEvents['tutor-app-widget-loaded'].dispatch()
|
|
63
|
-
} catch (error) {
|
|
64
|
-
initSparkieError = error
|
|
65
|
-
console.error(error)
|
|
66
|
-
TutorWidgetEvents['tutor-app-widget-loaded'].dispatch({ detail: { isSuccess: false } })
|
|
67
|
-
} finally {
|
|
68
|
-
isLoadingSparkie = false
|
|
69
|
-
}
|
|
70
41
|
|
|
71
42
|
if (root) {
|
|
72
|
-
TutorWidgetEvents['c3po-app-widget-open'].dispatch()
|
|
73
43
|
root.render(
|
|
74
44
|
<StrictMode>
|
|
75
|
-
<Main settings={settings}
|
|
45
|
+
<Main settings={settings} />
|
|
76
46
|
</StrictMode>
|
|
77
47
|
)
|
|
78
48
|
}
|
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
<svg viewBox="0 0
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
y1="12.1263"
|
|
10
|
-
x2="24.9086"
|
|
11
|
-
y2="12.1263"
|
|
12
|
-
gradientUnits="userSpaceOnUse">
|
|
13
|
-
<stop stop-color="#44D0FF" />
|
|
14
|
-
<stop offset="1" stop-color="#B48EFF" />
|
|
15
|
-
</linearGradient>
|
|
16
|
-
</defs>
|
|
1
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M0.897063 10.1169C0.779007 10.0991 0.779007 9.90091 0.897063 9.88308C8.8355 8.68408 9.54825 3.99021 9.90743 0.811152C9.92068 0.693875 10.0793 0.693875 10.0926 0.811152C10.4517 3.99021 11.1645 8.68408 19.1029 9.88308C19.221 9.90091 19.221 10.0991 19.1029 10.1169C11.1645 11.3159 10.4517 16.0098 10.0926 19.1888C10.0793 19.3061 9.92068 19.3061 9.90743 19.1888C9.54825 16.0098 8.8355 11.3159 0.897063 10.1169Z" fill="url(#paint0_linear_7856_83233)"/>
|
|
3
|
+
<defs>
|
|
4
|
+
<linearGradient id="paint0_linear_7856_83233" x1="1.875" y1="-0.108232" x2="17.7233" y2="20.1432" gradientUnits="userSpaceOnUse">
|
|
5
|
+
<stop stop-color="#44D0FF"/>
|
|
6
|
+
<stop offset="1" stop-color="#B48EFF"/>
|
|
7
|
+
</linearGradient>
|
|
8
|
+
</defs>
|
|
17
9
|
</svg>
|
package/src/main/main.spec.tsx
CHANGED
package/src/main/main.tsx
CHANGED
|
@@ -3,36 +3,24 @@ import '@/config/styles/index.css'
|
|
|
3
3
|
import { ErrorBoundary, GenericError } from '@/src/lib/components/errors'
|
|
4
4
|
import { useDefaultId } from '@/src/lib/hooks'
|
|
5
5
|
import { useAppLang } from '../config/i18n'
|
|
6
|
-
import { Spinner } from '../lib/components'
|
|
7
6
|
import { GlobalProviders } from '../modules/global-providers'
|
|
8
7
|
import { WidgetContainer } from '../modules/widget'
|
|
9
|
-
import {
|
|
8
|
+
import { useInitWidget } from '../modules/widget/hooks'
|
|
10
9
|
import type { WidgetSettingProps } from '../types'
|
|
11
10
|
|
|
12
11
|
export type MainProps = {
|
|
13
12
|
settings: WidgetSettingProps
|
|
14
|
-
metadata?: { isLoadingSparkie: boolean; initSparkieError: unknown }
|
|
15
13
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}: MainProps) {
|
|
14
|
+
|
|
15
|
+
function Main({ settings }: MainProps) {
|
|
16
|
+
const { completeSetup } = useInitWidget(settings)
|
|
20
17
|
useDefaultId()
|
|
21
18
|
useAppLang(settings.locale)
|
|
22
|
-
useListenToVisibilityEvents()
|
|
23
|
-
|
|
24
|
-
if (metadata.isLoadingSparkie) {
|
|
25
|
-
return (
|
|
26
|
-
<div className='flex h-full w-full flex-col items-center justify-center'>
|
|
27
|
-
<Spinner className='h-10 w-10 text-neutral-500' />
|
|
28
|
-
</div>
|
|
29
|
-
)
|
|
30
|
-
}
|
|
31
19
|
|
|
32
20
|
return (
|
|
33
21
|
<ErrorBoundary fallback={<GenericError />}>
|
|
34
22
|
<GlobalProviders settings={settings}>
|
|
35
|
-
<WidgetContainer />
|
|
23
|
+
<WidgetContainer completeSetup={completeSetup} />
|
|
36
24
|
</GlobalProviders>
|
|
37
25
|
</ErrorBoundary>
|
|
38
26
|
)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'
|
|
1
|
+
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef } from 'react'
|
|
2
2
|
import clsx from 'clsx'
|
|
3
3
|
import type { ChangeEvent, KeyboardEvent } from 'react'
|
|
4
4
|
import { useTranslation } from 'react-i18next'
|
|
@@ -40,11 +40,13 @@ const ChatInput = forwardRef<HTMLTextAreaElement, ChatInputProps>(
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
const setInputFocus = useCallback(() => {
|
|
44
44
|
if (inputDisabled) return
|
|
45
45
|
|
|
46
46
|
const input = ref?.current
|
|
47
47
|
|
|
48
|
+
if (input === document.activeElement) return
|
|
49
|
+
|
|
48
50
|
if (input) {
|
|
49
51
|
input.focus()
|
|
50
52
|
|
|
@@ -53,6 +55,10 @@ const ChatInput = forwardRef<HTMLTextAreaElement, ChatInputProps>(
|
|
|
53
55
|
}
|
|
54
56
|
}, [inputDisabled])
|
|
55
57
|
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
setInputFocus()
|
|
60
|
+
}, [setInputFocus])
|
|
61
|
+
|
|
56
62
|
return (
|
|
57
63
|
<div
|
|
58
64
|
className={clsx(
|
|
@@ -68,7 +74,7 @@ const ChatInput = forwardRef<HTMLTextAreaElement, ChatInputProps>(
|
|
|
68
74
|
'max-h-12 w-full resize-none border-none bg-transparent text-neutral-100 outline-none outline-0 placeholder:text-neutral-400',
|
|
69
75
|
styles.textArea
|
|
70
76
|
),
|
|
71
|
-
{ 'cursor-not-allowed
|
|
77
|
+
{ 'cursor-not-allowed opacity-40': inputDisabled }
|
|
72
78
|
)}
|
|
73
79
|
placeholder={t('send_message.field.placeholder')}
|
|
74
80
|
value={value}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
1
2
|
import { useMutation } from '@tanstack/react-query'
|
|
2
3
|
import { v4 } from 'uuid'
|
|
3
4
|
|
|
@@ -11,6 +12,8 @@ function useSendTextMessage() {
|
|
|
11
12
|
const profileQuery = useGetProfile()
|
|
12
13
|
const [, setWidgetLoading] = useWidgetLoadingAtom()
|
|
13
14
|
|
|
15
|
+
const userId = useMemo(() => profileQuery.data?.userId?.toString(), [profileQuery.data?.userId])
|
|
16
|
+
|
|
14
17
|
return useMutation({
|
|
15
18
|
mutationFn(message: string) {
|
|
16
19
|
let processedMessage = message
|
|
@@ -48,7 +51,7 @@ function useSendTextMessage() {
|
|
|
48
51
|
externalId: v4(),
|
|
49
52
|
namespace: settings.namespace,
|
|
50
53
|
sessionId: settings.sessionId,
|
|
51
|
-
userId
|
|
54
|
+
userId
|
|
52
55
|
}
|
|
53
56
|
})
|
|
54
57
|
},
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { ChatPage } from './chat-page'
|
|
2
|
+
import { WidgetLoadingPage } from './loading-page'
|
|
2
3
|
import { WidgetOnboardingPage } from './onboarding-page'
|
|
3
4
|
import { WidgetStarterPage } from './starter-page'
|
|
4
5
|
|
|
5
6
|
export const WIDGET_TABS = {
|
|
6
7
|
onboarding: <WidgetOnboardingPage />,
|
|
7
8
|
starter: <WidgetStarterPage />,
|
|
8
|
-
chat: <ChatPage
|
|
9
|
+
chat: <ChatPage />,
|
|
10
|
+
loading: <WidgetLoadingPage />
|
|
9
11
|
}
|
|
@@ -1,12 +1,23 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
|
|
1
3
|
import { useSubscribeMessageReceivedEvent } from '@/src/modules/messages/hooks'
|
|
2
4
|
import { useSubscribeThreadClosedEvent } from '@/src/modules/thread/hooks'
|
|
3
|
-
import {
|
|
5
|
+
import { useListenToVisibilityEvents } from '../../hooks'
|
|
6
|
+
import { useWidgetTabsAtom } from '../../store'
|
|
4
7
|
import { WIDGET_TABS } from '../constants'
|
|
5
8
|
|
|
6
|
-
function WidgetContainer() {
|
|
7
|
-
const widgetTabs =
|
|
9
|
+
function WidgetContainer({ completeSetup = false }: { completeSetup?: boolean }) {
|
|
10
|
+
const [widgetTabs, setTab] = useWidgetTabsAtom()
|
|
11
|
+
|
|
8
12
|
useSubscribeMessageReceivedEvent()
|
|
9
13
|
useSubscribeThreadClosedEvent()
|
|
14
|
+
useListenToVisibilityEvents()
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (completeSetup) {
|
|
18
|
+
setTab('chat')
|
|
19
|
+
}
|
|
20
|
+
}, [completeSetup, setTab])
|
|
10
21
|
|
|
11
22
|
return (
|
|
12
23
|
<div className='flex h-full flex-col items-center justify-stretch overflow-hidden'>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as WidgetLoadingPage } from './loading-page'
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useCallback, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
import { useRefEventListener } from '@/src/lib/hooks'
|
|
4
|
+
import {
|
|
5
|
+
ChatInput,
|
|
6
|
+
MessageSkeleton,
|
|
7
|
+
useChatInputValueAtom
|
|
8
|
+
} from '@/src/modules/messages/components'
|
|
9
|
+
import { PageLayout } from '../page-layout'
|
|
10
|
+
|
|
11
|
+
function WidgetLoadingPage() {
|
|
12
|
+
const chatInputRef = useRef<HTMLTextAreaElement>(null)
|
|
13
|
+
const [, setChatInputValue] = useChatInputValueAtom()
|
|
14
|
+
|
|
15
|
+
const handler = useCallback(
|
|
16
|
+
(e: Event) => {
|
|
17
|
+
const target = e.target as HTMLTextAreaElement
|
|
18
|
+
setChatInputValue(target.value)
|
|
19
|
+
},
|
|
20
|
+
[setChatInputValue]
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
useRefEventListener<HTMLTextAreaElement>({
|
|
24
|
+
config: {
|
|
25
|
+
ref: chatInputRef,
|
|
26
|
+
eventTypes: ['input', 'change'],
|
|
27
|
+
handler
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<PageLayout
|
|
33
|
+
asideChild={<ChatInput name='new-chat-msg-input' ref={chatInputRef} loading={true} />}>
|
|
34
|
+
<div className='flex h-full flex-col justify-end px-5 py-4'>
|
|
35
|
+
<MessageSkeleton />
|
|
36
|
+
</div>
|
|
37
|
+
</PageLayout>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default WidgetLoadingPage
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as useInitWidget } from './use-init-widget'
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import { initDayjs } from '@/src/config/dayjs'
|
|
4
|
+
import { initAxios } from '@/src/config/request/api'
|
|
5
|
+
import { SparkieService } from '@/src/modules/sparkie'
|
|
6
|
+
import type { WidgetSettingProps } from '@/src/types'
|
|
7
|
+
import { TutorWidgetEvents } from '../../events'
|
|
8
|
+
|
|
9
|
+
const init = async (settings: WidgetSettingProps) => {
|
|
10
|
+
try {
|
|
11
|
+
initAxios(settings.hotmartToken)
|
|
12
|
+
await initDayjs(settings.locale)
|
|
13
|
+
await SparkieService.initSparkie({
|
|
14
|
+
token: settings?.hotmartToken,
|
|
15
|
+
skipPresenceSetup: true,
|
|
16
|
+
retryOptions: {
|
|
17
|
+
maxRetries: 5,
|
|
18
|
+
retryDelay: 2000,
|
|
19
|
+
backoffMultiplier: 1.5
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
await SparkieService.ensureInitialized()
|
|
23
|
+
TutorWidgetEvents['tutor-app-widget-loaded'].dispatch()
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error(error)
|
|
26
|
+
TutorWidgetEvents['tutor-app-widget-loaded'].dispatch({ detail: { isSuccess: false } })
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function useInitWidget(settings: WidgetSettingProps) {
|
|
31
|
+
const [completeSetup, setCompleteSetup] = useState(false)
|
|
32
|
+
const [error, setError] = useState<unknown>(null)
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (completeSetup) return
|
|
36
|
+
|
|
37
|
+
init(settings)
|
|
38
|
+
.then(() => {
|
|
39
|
+
setCompleteSetup(true)
|
|
40
|
+
})
|
|
41
|
+
.catch(setError)
|
|
42
|
+
}, [completeSetup, settings])
|
|
43
|
+
|
|
44
|
+
return { completeSetup, error }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default useInitWidget
|
|
@@ -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: 'loading',
|
|
12
|
+
history: new Set(['loading'])
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export const widgetTabsAtom = atom<WidgetTabsProps>(INITIAL_PROPS)
|