app-tutor-ai-consumer 1.12.0 → 1.14.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/environments/.env.production +2 -2
- package/package.json +1 -1
- package/src/config/styles/global.css +2 -0
- package/src/lib/components/button/button.tsx +10 -3
- package/src/modules/messages/hooks/use-send-text-message/use-send-text-message.spec.tsx +11 -1
- package/src/modules/messages/hooks/use-send-text-message/use-send-text-message.tsx +10 -1
- package/src/modules/widget/components/container/container.tsx +1 -1
- package/src/modules/widget/components/starter-page/starter-page.spec.tsx +41 -0
- package/src/modules/widget/components/starter-page/starter-page.tsx +47 -2
- package/src/modules/widget/components/starter-page/styles.module.css +3 -0
- package/src/modules/widget/hooks/use-init-widget/use-init-widget.tsx +10 -2
- package/src/types.ts +12 -0
- package/tailwind.config.js +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
# [1.14.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.13.0...v1.14.0) (2025-07-21)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- add tutor initial state page UI ([f9bc12e](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/f9bc12eb0ac2d5e0fc408795ac2a1837514d14ee))
|
|
6
|
+
|
|
7
|
+
# [1.13.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.12.0...v1.13.0) (2025-07-21)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- adding env values to production ([ec891e2](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/ec891e2692769a44e54e83ebeb6cb024c0f99cd1))
|
|
12
|
+
|
|
1
13
|
# [1.12.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.11.0...v1.12.0) (2025-07-21)
|
|
2
14
|
|
|
3
15
|
### Features
|
|
@@ -27,8 +27,8 @@ FIREBASE_PROJECT_ID=omnichat-cad8c
|
|
|
27
27
|
FIREBASE_STORAGE_BUCKET=omnichat-cad8c.appspot.com
|
|
28
28
|
|
|
29
29
|
# C3PO
|
|
30
|
-
API_CONVERSATION_URL=
|
|
31
|
-
API_AUTH_URL=
|
|
30
|
+
API_CONVERSATION_URL=https://c3po-api-conversations.vulcano.rocks
|
|
31
|
+
API_AUTH_URL=https://c3po-api-auth.vulcano.rocks/v1
|
|
32
32
|
|
|
33
33
|
# VLC
|
|
34
34
|
USER_VLC=
|
package/package.json
CHANGED
|
@@ -9,7 +9,8 @@ export type ButtonProps = PropsWithChildren<
|
|
|
9
9
|
|
|
10
10
|
function Button({ children, className, variant = 'brand', ...props }: ButtonProps) {
|
|
11
11
|
const defaultClasses =
|
|
12
|
-
'rounded focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 text-base font-medium
|
|
12
|
+
'rounded focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 text-base font-medium'
|
|
13
|
+
const defaultBorder = 'border border-transparent'
|
|
13
14
|
const defaultPadding = 'px-4 py-2'
|
|
14
15
|
|
|
15
16
|
switch (variant) {
|
|
@@ -18,11 +19,13 @@ function Button({ children, className, variant = 'brand', ...props }: ButtonProp
|
|
|
18
19
|
<button
|
|
19
20
|
className={clsx(
|
|
20
21
|
defaultClasses,
|
|
21
|
-
'group relative inline-flex items-center justify-center overflow-hidden bg-gradient-to-br from-
|
|
22
|
+
'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-0 group-hover:from-[var(--ai-color-gradient-primary)] group-hover:to-[var(--ai-color-gradient-accent)]',
|
|
22
23
|
className
|
|
23
24
|
)}
|
|
24
25
|
{...props}>
|
|
25
|
-
<span
|
|
26
|
+
<span
|
|
27
|
+
data-label='text-content'
|
|
28
|
+
className='relative flex-1 rounded-lg bg-neutral-900 px-5 py-2.5 text-neutral-0 transition-all duration-75 ease-in group-hover:bg-transparent'>
|
|
26
29
|
{children}
|
|
27
30
|
</span>
|
|
28
31
|
</button>
|
|
@@ -33,6 +36,7 @@ function Button({ children, className, variant = 'brand', ...props }: ButtonProp
|
|
|
33
36
|
className={clsx(
|
|
34
37
|
defaultPadding,
|
|
35
38
|
defaultClasses,
|
|
39
|
+
defaultBorder,
|
|
36
40
|
'bg-primary-500 text-neutral-0 hover:bg-primary-600 focus:outline-none',
|
|
37
41
|
className
|
|
38
42
|
)}
|
|
@@ -46,6 +50,7 @@ function Button({ children, className, variant = 'brand', ...props }: ButtonProp
|
|
|
46
50
|
className={clsx(
|
|
47
51
|
defaultPadding,
|
|
48
52
|
defaultClasses,
|
|
53
|
+
defaultBorder,
|
|
49
54
|
'border-neutral-100 bg-transparent text-neutral-100 hover:bg-neutral-100 hover:text-neutral-900',
|
|
50
55
|
className
|
|
51
56
|
)}
|
|
@@ -59,6 +64,7 @@ function Button({ children, className, variant = 'brand', ...props }: ButtonProp
|
|
|
59
64
|
className={clsx(
|
|
60
65
|
defaultPadding,
|
|
61
66
|
defaultClasses,
|
|
67
|
+
defaultBorder,
|
|
62
68
|
'bg-transparent text-neutral-0 hover:bg-neutral-900',
|
|
63
69
|
className
|
|
64
70
|
)}
|
|
@@ -73,6 +79,7 @@ function Button({ children, className, variant = 'brand', ...props }: ButtonProp
|
|
|
73
79
|
className={clsx(
|
|
74
80
|
defaultPadding,
|
|
75
81
|
defaultClasses,
|
|
82
|
+
defaultBorder,
|
|
76
83
|
'rounded bg-neutral-100 text-neutral-900 outline-none hover:bg-neutral-200',
|
|
77
84
|
className
|
|
78
85
|
)}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { UAParser } from 'ua-parser-js'
|
|
2
|
+
|
|
1
3
|
import { act, chance, renderHook, waitFor } from '@/src/config/tests'
|
|
2
4
|
import { MessagesService } from '@/src/modules/messages'
|
|
3
5
|
import { useGetProfile } from '@/src/modules/profile'
|
|
@@ -11,6 +13,8 @@ vi.mock('@/src/modules/profile', () => ({
|
|
|
11
13
|
useGetProfile: vi.fn()
|
|
12
14
|
}))
|
|
13
15
|
|
|
16
|
+
vi.mock('ua-parser-js', () => ({ UAParser: vi.fn() }))
|
|
17
|
+
|
|
14
18
|
describe('useSendTextMessage', () => {
|
|
15
19
|
const message = chance.animal()
|
|
16
20
|
const getProfileMock = { data: { userId: chance.integer() } }
|
|
@@ -18,12 +22,15 @@ describe('useSendTextMessage', () => {
|
|
|
18
22
|
.withClassHashId(chance.guid())
|
|
19
23
|
.withOwner_id(chance.guid())
|
|
20
24
|
.withCurrent_media_codes(chance.profession())
|
|
25
|
+
.withUser({ id: chance.guid(), ucode: chance.guid() })
|
|
26
|
+
const UAParserMock = { browser: { version: chance.android_id(), name: chance.name_prefix() } }
|
|
21
27
|
|
|
22
28
|
const render = () => renderHook(useSendTextMessage)
|
|
23
29
|
|
|
24
30
|
beforeEach(() => {
|
|
25
31
|
vi.mocked(useGetProfile).mockReturnValue(getProfileMock as never)
|
|
26
32
|
vi.spyOn(Store, 'useWidgetSettingsAtomValue').mockReturnValue(defaultSettings)
|
|
33
|
+
vi.mocked(UAParser).mockReturnValue(UAParserMock as never)
|
|
27
34
|
})
|
|
28
35
|
|
|
29
36
|
it('should throw when conversationId is not defined', async () => {
|
|
@@ -75,7 +82,10 @@ describe('useSendTextMessage', () => {
|
|
|
75
82
|
owner_id: defaultSettings.owner_id,
|
|
76
83
|
current_media_codes: defaultSettings.current_media_codes,
|
|
77
84
|
question: text,
|
|
78
|
-
router: text
|
|
85
|
+
router: text,
|
|
86
|
+
osVersion: UAParserMock.browser.version,
|
|
87
|
+
platformDetail: UAParserMock.browser.name,
|
|
88
|
+
ucode: defaultSettings.user?.ucode
|
|
79
89
|
},
|
|
80
90
|
externalId: expect.any(String),
|
|
81
91
|
namespace: defaultSettings.namespace,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useMemo } from 'react'
|
|
2
2
|
import { useMutation } from '@tanstack/react-query'
|
|
3
|
+
import { UAParser } from 'ua-parser-js'
|
|
3
4
|
import { v4 } from 'uuid'
|
|
4
5
|
|
|
5
6
|
import { MessagesService } from '@/src/modules/messages'
|
|
@@ -16,6 +17,9 @@ function useSendTextMessage() {
|
|
|
16
17
|
|
|
17
18
|
return useMutation({
|
|
18
19
|
mutationFn(message: string) {
|
|
20
|
+
const userAgentParser = UAParser(navigator.userAgent)
|
|
21
|
+
const browserInfo = userAgentParser.browser
|
|
22
|
+
|
|
19
23
|
let processedMessage = message
|
|
20
24
|
|
|
21
25
|
if (!settings?.conversationId) throw new Error('Conversation Id must be defined')
|
|
@@ -42,11 +46,16 @@ function useSendTextMessage() {
|
|
|
42
46
|
clubName: settings.clubName,
|
|
43
47
|
productName: settings.productName,
|
|
44
48
|
productId: settings.productId,
|
|
49
|
+
productType: settings.productType,
|
|
50
|
+
classType: settings.classType,
|
|
45
51
|
classHashId: settings.classHashId,
|
|
46
52
|
owner_id: settings?.owner_id,
|
|
47
53
|
current_media_codes: settings?.current_media_codes,
|
|
48
54
|
question: questionParam,
|
|
49
|
-
router: questionParam ? 'summary' : undefined
|
|
55
|
+
router: questionParam ? 'summary' : undefined,
|
|
56
|
+
osVersion: browserInfo.version,
|
|
57
|
+
platformDetail: browserInfo.name,
|
|
58
|
+
ucode: settings.user?.ucode
|
|
50
59
|
},
|
|
51
60
|
externalId: v4(),
|
|
52
61
|
namespace: settings.namespace,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { render, screen } from '@/src/config/tests'
|
|
2
|
+
import { useSendTextMessage } from '@/src/modules/messages/hooks'
|
|
3
|
+
|
|
4
|
+
import WidgetStarterPage from './starter-page'
|
|
5
|
+
|
|
6
|
+
vi.mock('@/src/modules/messages/hooks', () => ({ useSendTextMessage: vi.fn() }))
|
|
7
|
+
|
|
8
|
+
describe('WidgetStarterPage', () => {
|
|
9
|
+
const useSendTextMessageMock = { mutate: vi.fn() }
|
|
10
|
+
|
|
11
|
+
const renderComponent = () => render(<WidgetStarterPage />)
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
vi.mocked(useSendTextMessage).mockReturnValue(useSendTextMessageMock as never)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
it('should render without errors', () => {
|
|
18
|
+
renderComponent()
|
|
19
|
+
|
|
20
|
+
expect(
|
|
21
|
+
screen.getByRole('button', { name: /starter_page.what_does_tutor_do/i })
|
|
22
|
+
).toBeInTheDocument()
|
|
23
|
+
expect(screen.getByRole('button', { name: /starter_page.wanna_summary/i })).toBeInTheDocument()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should post the slider button text content to the backend', async () => {
|
|
27
|
+
const { user } = renderComponent()
|
|
28
|
+
|
|
29
|
+
const button = screen.getByRole('button', { name: /starter_page.what_does_tutor_do/i })
|
|
30
|
+
|
|
31
|
+
await user.click(button)
|
|
32
|
+
|
|
33
|
+
expect(useSendTextMessageMock.mutate).toHaveBeenNthCalledWith(
|
|
34
|
+
1,
|
|
35
|
+
'🤖 starter_page.what_does_tutor_do',
|
|
36
|
+
{
|
|
37
|
+
onSuccess: expect.any(Function)
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
})
|
|
41
|
+
})
|
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import { useRef } from 'react'
|
|
2
|
+
import clsx from 'clsx'
|
|
3
|
+
import type { MouseEventHandler } from 'react'
|
|
4
|
+
import { useTranslation } from 'react-i18next'
|
|
2
5
|
|
|
6
|
+
import { Button } from '@/src/lib/components'
|
|
3
7
|
import { useRefEventListener } from '@/src/lib/hooks'
|
|
4
8
|
import { ChatInput, useChatInputValueAtom } from '@/src/modules/messages/components'
|
|
9
|
+
import { useSendTextMessage } from '@/src/modules/messages/hooks'
|
|
5
10
|
import { useWidgetSettingsAtom, useWidgetTabsAtom } from '../../store'
|
|
6
11
|
import { GreetingsCard } from '../greetings-card'
|
|
7
12
|
import { PageLayout } from '../page-layout'
|
|
8
13
|
|
|
14
|
+
import styles from './styles.module.css'
|
|
15
|
+
|
|
9
16
|
function WidgetStarterPage() {
|
|
17
|
+
const { t } = useTranslation()
|
|
10
18
|
const [settings] = useWidgetSettingsAtom()
|
|
11
19
|
const chatInputRef = useRef<HTMLTextAreaElement>(null)
|
|
12
20
|
const [, setChatInputValue] = useChatInputValueAtom()
|
|
13
21
|
const [, setWidgetTabs] = useWidgetTabsAtom()
|
|
22
|
+
const sendTextMessageMutation = useSendTextMessage()
|
|
14
23
|
|
|
15
24
|
useRefEventListener<HTMLTextAreaElement>({
|
|
16
25
|
config: {
|
|
@@ -23,6 +32,20 @@ function WidgetStarterPage() {
|
|
|
23
32
|
}
|
|
24
33
|
})
|
|
25
34
|
|
|
35
|
+
const handleAskQuestion: MouseEventHandler<HTMLButtonElement> = (e) => {
|
|
36
|
+
const textContent = e?.currentTarget?.querySelector(
|
|
37
|
+
'span[data-label=text-content]'
|
|
38
|
+
)?.textContent
|
|
39
|
+
|
|
40
|
+
if (!textContent) return
|
|
41
|
+
|
|
42
|
+
sendTextMessageMutation.mutate(textContent, {
|
|
43
|
+
onSuccess() {
|
|
44
|
+
setWidgetTabs('chat')
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
26
49
|
return (
|
|
27
50
|
<PageLayout
|
|
28
51
|
asideChild={
|
|
@@ -32,8 +55,30 @@ function WidgetStarterPage() {
|
|
|
32
55
|
onSend={() => setWidgetTabs('chat')}
|
|
33
56
|
/>
|
|
34
57
|
}>
|
|
35
|
-
<div className='
|
|
36
|
-
<
|
|
58
|
+
<div className='grid-areas-[a_b] grid h-full grid-cols-1 grid-rows-[1fr_auto]'>
|
|
59
|
+
<div
|
|
60
|
+
className={clsx(
|
|
61
|
+
'grid-area-[a] flex min-h-0 flex-col justify-center px-5 py-4',
|
|
62
|
+
styles.bg
|
|
63
|
+
)}>
|
|
64
|
+
<GreetingsCard author={settings?.author ?? ''} tutorName={settings?.tutorName ?? ''} />
|
|
65
|
+
</div>
|
|
66
|
+
<div className='grid-area-[b] mx-5 my-6 flex flex-shrink-0 snap-x snap-mandatory gap-2 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden'>
|
|
67
|
+
<Button
|
|
68
|
+
variant='gradient-outline'
|
|
69
|
+
className='shrink-0 snap-end text-sm'
|
|
70
|
+
onClick={handleAskQuestion}>
|
|
71
|
+
<span>🤖 </span>
|
|
72
|
+
{t('starter_page.what_does_tutor_do')}
|
|
73
|
+
</Button>
|
|
74
|
+
<Button
|
|
75
|
+
variant='gradient-outline'
|
|
76
|
+
className='shrink-0 snap-end text-sm'
|
|
77
|
+
onClick={handleAskQuestion}>
|
|
78
|
+
<span>📝 </span>
|
|
79
|
+
{t('starter_page.wanna_summary')}
|
|
80
|
+
</Button>
|
|
81
|
+
</div>
|
|
37
82
|
</div>
|
|
38
83
|
</PageLayout>
|
|
39
84
|
)
|
|
@@ -32,13 +32,21 @@ function useInitWidget(settings: WidgetSettingProps) {
|
|
|
32
32
|
const [error, setError] = useState<unknown>(null)
|
|
33
33
|
|
|
34
34
|
useEffect(() => {
|
|
35
|
+
let isMounted = true
|
|
36
|
+
|
|
35
37
|
if (completeSetup) return
|
|
36
38
|
|
|
37
39
|
init(settings)
|
|
38
40
|
.then(() => {
|
|
39
|
-
setCompleteSetup(true)
|
|
41
|
+
if (isMounted) setCompleteSetup(true)
|
|
42
|
+
})
|
|
43
|
+
.catch((e) => {
|
|
44
|
+
if (isMounted) setError(e)
|
|
40
45
|
})
|
|
41
|
-
|
|
46
|
+
|
|
47
|
+
return () => {
|
|
48
|
+
isMounted = false
|
|
49
|
+
}
|
|
42
50
|
}, [completeSetup, settings])
|
|
43
51
|
|
|
44
52
|
return { completeSetup, error }
|
package/src/types.ts
CHANGED
|
@@ -16,6 +16,16 @@ export type User = {
|
|
|
16
16
|
sessionId?: string
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
export enum ClassTypes {
|
|
20
|
+
Html = 'HTML',
|
|
21
|
+
Media = 'MEDIA',
|
|
22
|
+
Advertisement = 'ADVERTISEMENT',
|
|
23
|
+
QuizExercise = 'QUIZ_EXERCISE',
|
|
24
|
+
QuizProfile = 'QUIZ_PROFILE',
|
|
25
|
+
Webinar = 'WEBINAR',
|
|
26
|
+
Other = 'OTHER'
|
|
27
|
+
}
|
|
28
|
+
|
|
19
29
|
export type WidgetSettingProps = {
|
|
20
30
|
hotmartToken: string
|
|
21
31
|
locale: ILanguages
|
|
@@ -35,6 +45,8 @@ export type WidgetSettingProps = {
|
|
|
35
45
|
classHashId?: string
|
|
36
46
|
owner_id?: string
|
|
37
47
|
current_media_codes?: string
|
|
48
|
+
productType?: string
|
|
49
|
+
classType?: ClassTypes
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
export interface ICustomEvent<T = object> {
|
package/tailwind.config.js
CHANGED
|
@@ -93,7 +93,9 @@ module.exports = {
|
|
|
93
93
|
primary: 'var(--ai-color-primary)',
|
|
94
94
|
secondary: 'var(--ai-color-secondary)',
|
|
95
95
|
dark: 'var(--ai-color-dark)',
|
|
96
|
-
'chat-response': 'var(--ai-color-chat-response)'
|
|
96
|
+
'chat-response': 'var(--ai-color-chat-response)',
|
|
97
|
+
'gradient-primary': 'var(--ai-color-gradient-primary)',
|
|
98
|
+
'gradient-accent': 'var(--ai-color-gradient-accent)'
|
|
97
99
|
}
|
|
98
100
|
}
|
|
99
101
|
}
|