app-tutor-ai-consumer 1.32.3 → 1.33.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 +12 -0
- package/config/vitest/__mocks__/sparkie.tsx +1 -1
- package/package.json +2 -2
- package/src/@types/index.d.ts +2 -3
- package/src/config/tanstack/query-provider.tsx +3 -7
- package/src/index.tsx +10 -63
- package/src/lib/components/icons/icon-names.d.ts +1 -0
- package/src/lib/components/icons/tutor-logo.svg +9 -0
- package/src/lib/components/markdownrenderer/markdownrenderer.tsx +1 -1
- package/src/modules/global-providers/global-providers.tsx +6 -1
- package/src/modules/messages/__tests__/imessage-with-sender-data.builder.ts +1 -1
- package/src/modules/messages/components/chat-input/chat-input.tsx +1 -1
- package/src/modules/messages/components/message-item/message-item.tsx +1 -2
- package/src/modules/messages/hooks/use-subscribe-message-received-event/use-subscribe-message-received-event.tsx +54 -119
- package/src/modules/messages/service.direct.ts +1 -1
- package/src/modules/messages/service.ts +2 -3
- package/src/modules/messages/types.ts +1 -1
- package/src/modules/messages/utils/set-messages-cache/utils.ts +1 -1
- package/src/modules/sparkie/service.ts +3 -2
- package/src/modules/widget/components/ai-disclaimer/ai-disclaimer.tsx +19 -0
- package/src/modules/widget/components/ai-disclaimer/index.ts +1 -0
- package/src/modules/widget/components/chat-page/chat-page.tsx +15 -8
- package/src/modules/widget/components/container/container.tsx +14 -0
- package/src/modules/widget/components/greetings-card/greetings-card.tsx +9 -2
- package/src/modules/widget/components/starter-page/starter-page.tsx +14 -7
- package/src/types.ts +0 -9
- package/src/index.backup.tsx +0 -61
- package/src/lib/hooks/use-response-timeout/index.ts +0 -1
- package/src/lib/hooks/use-response-timeout/use-response-timeout.tsx +0 -42
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [1.33.1](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.33.0...v1.33.1) (2025-10-22)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
- markdownrenderer ([ae8c08a](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/ae8c08a0620cd95a54a787ac00ddab78640d3f45))
|
|
6
|
+
|
|
7
|
+
# [1.33.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.32.3...v1.33.0) (2025-10-16)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- change sparkie call to messages history page ([ec5aa19](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/ec5aa19ee913204881491d729d7fd979e05bf337))
|
|
12
|
+
|
|
1
13
|
## [1.32.3](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.32.2...v1.32.3) (2025-10-13)
|
|
2
14
|
|
|
3
15
|
## [1.32.2](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.32.1...v1.32.2) (2025-10-08)
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
SparkieMessageServiceMock,
|
|
5
5
|
SparkieCursorServiceMock
|
|
6
6
|
} from '@/src/modules/sparkie/__tests__/sparkie.mock'
|
|
7
|
-
import MessageService from '@hotmart
|
|
7
|
+
import MessageService from '@hotmart/sparkie/dist/MessageService'
|
|
8
8
|
|
|
9
9
|
vi.mock('@hotmart/sparkie', () => ({ default: SparkieMock }))
|
|
10
10
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "app-tutor-ai-consumer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.33.1",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "rspack serve --env=development --config config/rspack/rspack.config.js",
|
|
@@ -108,8 +108,8 @@
|
|
|
108
108
|
"dependencies": {
|
|
109
109
|
"@hot-observability-js/react": "~1.1.0",
|
|
110
110
|
"@hotmart-org-ca/hot-observability-js": "~1.1.0",
|
|
111
|
-
"@hotmart-org-ca/sparkie": "~5.1.4",
|
|
112
111
|
"@hotmart/event-agent-js": "~1.1.2",
|
|
112
|
+
"@hotmart/sparkie": "~5.1.0",
|
|
113
113
|
"@optimizely/react-sdk": "~3.2.4",
|
|
114
114
|
"@tanstack/query-sync-storage-persister": "~5.80.7",
|
|
115
115
|
"@tanstack/react-query": "~5.80.6",
|
package/src/@types/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { StartTutorWidgetProps
|
|
1
|
+
import type { StartTutorWidgetProps } from '@/src/types'
|
|
2
2
|
|
|
3
3
|
export {}
|
|
4
4
|
|
|
@@ -13,8 +13,7 @@ declare global {
|
|
|
13
13
|
elementId: StartTutorWidgetProps['elementId'],
|
|
14
14
|
settings: StartTutorWidgetProps['settings']
|
|
15
15
|
) => Promise<void>
|
|
16
|
-
closeChatWidget: () =>
|
|
17
|
-
__CHAT_WIDGET_INSTANCE__?: WidgetInstance
|
|
16
|
+
closeChatWidget: () => void
|
|
18
17
|
TOKEN: string
|
|
19
18
|
}
|
|
20
19
|
}
|
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
import type { QueryClient } from '@tanstack/react-query'
|
|
2
1
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
|
3
2
|
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
|
|
4
3
|
import type { PropsWithChildren } from 'react'
|
|
5
4
|
|
|
6
|
-
import { persister } from './query-client'
|
|
5
|
+
import { persister, queryClient } from './query-client'
|
|
7
6
|
|
|
8
|
-
export type QueryProviderProps = PropsWithChildren<{
|
|
9
|
-
showDevTools?: boolean
|
|
10
|
-
queryClient: QueryClient
|
|
11
|
-
}>
|
|
7
|
+
export type QueryProviderProps = PropsWithChildren<{ showDevTools?: boolean }>
|
|
12
8
|
|
|
13
|
-
function QueryProvider({ children,
|
|
9
|
+
function QueryProvider({ children, showDevTools = true }: QueryProviderProps) {
|
|
14
10
|
return (
|
|
15
11
|
<PersistQueryClientProvider client={queryClient} persistOptions={{ persister }}>
|
|
16
12
|
{children}
|
package/src/index.tsx
CHANGED
|
@@ -4,14 +4,13 @@ import './config/styles/index.css'
|
|
|
4
4
|
import { StrictMode } from 'react'
|
|
5
5
|
import { createRoot } from 'react-dom/client'
|
|
6
6
|
|
|
7
|
-
import { queryClient, QueryProvider } from '@/src/config/tanstack'
|
|
8
7
|
import { initTheme } from '@/src/config/theme'
|
|
9
8
|
import { version } from '../package.json'
|
|
10
9
|
|
|
11
10
|
import { initLanguage } from './config/i18n'
|
|
12
11
|
import { devMode, productionMode } from './lib/utils'
|
|
13
12
|
import { Main } from './main'
|
|
14
|
-
import {
|
|
13
|
+
import { TutorWidgetEvents } from './modules/widget'
|
|
15
14
|
import type { Theme, WidgetSettingProps } from './types'
|
|
16
15
|
|
|
17
16
|
const loadMainStyles = () => {
|
|
@@ -34,80 +33,28 @@ window.startChatWidget = async (
|
|
|
34
33
|
elementId = 'tutor-chat-app-widget',
|
|
35
34
|
settings: WidgetSettingProps
|
|
36
35
|
) => {
|
|
37
|
-
if (window.__CHAT_WIDGET_INSTANCE__) {
|
|
38
|
-
await window.closeChatWidget()
|
|
39
|
-
}
|
|
40
|
-
|
|
41
36
|
if (!devMode) {
|
|
42
37
|
loadMainStyles()
|
|
43
38
|
}
|
|
44
39
|
|
|
45
|
-
const
|
|
40
|
+
const rootElement = document.getElementById(elementId) as HTMLElement
|
|
46
41
|
|
|
47
|
-
if (!
|
|
42
|
+
if (!rootElement) return
|
|
48
43
|
|
|
49
|
-
|
|
50
|
-
const theme = (
|
|
44
|
+
rootElement.setAttribute('id', 'hotmart-app-tutor-ai-consumer-root')
|
|
45
|
+
const theme = (rootElement.getAttribute('data-theme') ?? 'dark') as Theme
|
|
46
|
+
const root = createRoot(rootElement)
|
|
51
47
|
|
|
52
|
-
|
|
48
|
+
await initLanguage(settings.locale)
|
|
49
|
+
initTheme(theme)
|
|
53
50
|
|
|
54
51
|
if (root) {
|
|
55
|
-
initTheme(theme)
|
|
56
|
-
await initLanguage(settings.locale)
|
|
57
|
-
|
|
58
52
|
root.render(
|
|
59
53
|
<StrictMode>
|
|
60
|
-
<
|
|
61
|
-
<Main settings={{ ...settings, config: { ...settings.config, theme } }} />
|
|
62
|
-
</QueryProvider>
|
|
54
|
+
<Main settings={{ ...settings, config: { ...settings.config, theme } }} />
|
|
63
55
|
</StrictMode>
|
|
64
56
|
)
|
|
65
|
-
|
|
66
|
-
window.__CHAT_WIDGET_INSTANCE__ = { root, container, queryClient }
|
|
67
57
|
}
|
|
68
58
|
}
|
|
69
59
|
|
|
70
|
-
window.closeChatWidget =
|
|
71
|
-
const chatWidgetInstance = window.__CHAT_WIDGET_INSTANCE__
|
|
72
|
-
|
|
73
|
-
if (!chatWidgetInstance) return
|
|
74
|
-
|
|
75
|
-
const { root, container, queryClient } = chatWidgetInstance
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
await queryClient.cancelQueries()
|
|
79
|
-
} catch (err) {
|
|
80
|
-
console.error('Error cancelling queries on widget close', err)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
try {
|
|
84
|
-
root.unmount()
|
|
85
|
-
} catch (err) {
|
|
86
|
-
console.warn('Error unmounting widget root', err)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (typeof queryClient.clear === 'function') {
|
|
90
|
-
try {
|
|
91
|
-
queryClient.clear()
|
|
92
|
-
} catch {
|
|
93
|
-
queryClient.getQueryCache().clear()
|
|
94
|
-
queryClient.getMutationCache().clear()
|
|
95
|
-
}
|
|
96
|
-
} else {
|
|
97
|
-
queryClient.getQueryCache().clear()
|
|
98
|
-
queryClient.getMutationCache().clear()
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
container.remove()
|
|
103
|
-
} catch {
|
|
104
|
-
// ignore
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
await SparkieService.destroySparkie()
|
|
109
|
-
window.__CHAT_WIDGET_INSTANCE__ = undefined
|
|
110
|
-
} catch (err) {
|
|
111
|
-
console.error('Error destroying Sparkie instance', err)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
60
|
+
window.closeChatWidget = () => TutorWidgetEvents['c3po-app-widget-close'].dispatch()
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path d="M0.896914 6.12971C0.779441 6.10837 0.779442 5.89161 0.896914 5.87028C5.07103 5.11209 5.67461 2.64887 5.90592 0.812118C5.92066 0.695019 6.07934 0.695018 6.09408 0.812117C6.32539 2.64887 6.92897 5.11209 11.1031 5.87028C11.2206 5.89161 11.2206 6.10837 11.1031 6.12971C6.92897 6.8879 6.32539 9.35112 6.09408 11.1879C6.07934 11.305 5.92066 11.305 5.90592 11.1879C5.67461 9.35112 5.07103 6.8879 0.896914 6.12971Z" fill="url(#paint0_linear_7672_1716)"/>
|
|
3
|
+
<defs>
|
|
4
|
+
<linearGradient id="paint0_linear_7672_1716" x1="1.125" y1="-0.0649467" x2="10.634" y2="12.0859" gradientUnits="userSpaceOnUse">
|
|
5
|
+
<stop stop-color="#44D0FF"/>
|
|
6
|
+
<stop offset="1" stop-color="#B48EFF"/>
|
|
7
|
+
</linearGradient>
|
|
8
|
+
</defs>
|
|
9
|
+
</svg>
|
|
@@ -79,7 +79,7 @@ const mdComponents: Partial<Components> = {
|
|
|
79
79
|
)
|
|
80
80
|
},
|
|
81
81
|
p({ children }) {
|
|
82
|
-
return <span className='
|
|
82
|
+
return <span className='block [&:not(:only-child)]:my-3'>{children}</span>
|
|
83
83
|
},
|
|
84
84
|
ul({ children }) {
|
|
85
85
|
return <ul className='my-3 list-inside list-disc'>{children}</ul>
|
|
@@ -2,6 +2,7 @@ import { type PropsWithChildren, useEffect } from 'react'
|
|
|
2
2
|
import { v4 } from 'uuid'
|
|
3
3
|
|
|
4
4
|
import { OptimizelyProvider } from '@/src/config/optimizely'
|
|
5
|
+
import { QueryProvider } from '@/src/config/tanstack'
|
|
5
6
|
import { useWidgetSettingsAtom } from '@/src/modules/widget'
|
|
6
7
|
import type { WidgetSettingProps } from '@/src/types'
|
|
7
8
|
|
|
@@ -20,7 +21,11 @@ function GlobalProviders({ children, settings }: GlobalProvidersProps) {
|
|
|
20
21
|
setWidgetSettings(settings)
|
|
21
22
|
}, [setWidgetSettings, settings])
|
|
22
23
|
|
|
23
|
-
return
|
|
24
|
+
return (
|
|
25
|
+
<OptimizelyProvider settings={settings}>
|
|
26
|
+
<QueryProvider>{children}</QueryProvider>
|
|
27
|
+
</OptimizelyProvider>
|
|
28
|
+
)
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
export default GlobalProviders
|
|
@@ -57,7 +57,7 @@ const ChatInput = forwardRef<HTMLTextAreaElement, ChatInputProps>(
|
|
|
57
57
|
),
|
|
58
58
|
{ 'cursor-not-allowed opacity-40': inputDisabled }
|
|
59
59
|
)}
|
|
60
|
-
placeholder={t('send_message.field.
|
|
60
|
+
placeholder={t('send_message.field.placeholder_v2')}
|
|
61
61
|
value={value}
|
|
62
62
|
onChange={handleChange}
|
|
63
63
|
onKeyDown={handleKeyDown}
|
|
@@ -22,8 +22,7 @@ function MessageItem({ message }: { message: ParsedMessage }) {
|
|
|
22
22
|
<div
|
|
23
23
|
data-test='messages-item'
|
|
24
24
|
className={clsx('w-full overflow-x-hidden rounded-lg p-3', {
|
|
25
|
-
'max-w-max bg-[rgb(from_var(--hc-color-neutral-300)_r_g_b_/_0.8)]': messageFromUser
|
|
26
|
-
'bg-neutral-200': messageFromAi
|
|
25
|
+
'max-w-max bg-[rgb(from_var(--hc-color-neutral-300)_r_g_b_/_0.8)]': messageFromUser
|
|
27
26
|
})}>
|
|
28
27
|
<MessageContentTypeRenderer message={message} />
|
|
29
28
|
</div>
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo } from 'react'
|
|
2
|
-
import type { InfiniteData
|
|
2
|
+
import type { InfiniteData } from '@tanstack/react-query'
|
|
3
3
|
import { useQueryClient } from '@tanstack/react-query'
|
|
4
4
|
import { produce } from 'immer'
|
|
5
5
|
|
|
6
6
|
import { ComponentSource, DataHubService } from '@/src/config/datahub'
|
|
7
7
|
import { ViewTutorAnswerMessageSchema } from '@/src/config/datahub/schemas/tutor'
|
|
8
|
-
import { useResponseTimeout } from '@/src/lib/hooks/use-response-timeout'
|
|
9
8
|
import { useUpdateCursor } from '@/src/modules/cursor/hooks'
|
|
10
9
|
import { useGetProfile } from '@/src/modules/profile'
|
|
11
10
|
import { SparkieService } from '@/src/modules/sparkie'
|
|
@@ -18,59 +17,6 @@ import { useMessagesMaxCount, useUnreadMessagesSetAtom } from '../../store'
|
|
|
18
17
|
import type { FetchMessagesResponse, IMessageWithSenderData } from '../../types'
|
|
19
18
|
import { getAllMessagesQuery } from '../use-infinite-get-messages'
|
|
20
19
|
|
|
21
|
-
export type MessageReceivedProps = {
|
|
22
|
-
data: IMessageWithSenderData
|
|
23
|
-
queryClient: QueryClient
|
|
24
|
-
conversationId: string
|
|
25
|
-
profileId: string
|
|
26
|
-
limit: number
|
|
27
|
-
messageIsMineCallback?: (data: IMessageWithSenderData) => void
|
|
28
|
-
messageIsNotMineCallback?: (data: IMessageWithSenderData) => void
|
|
29
|
-
errorCallback?: (error: unknown) => void
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const messageReceivedUtil = ({
|
|
33
|
-
queryClient,
|
|
34
|
-
data,
|
|
35
|
-
conversationId,
|
|
36
|
-
profileId,
|
|
37
|
-
limit,
|
|
38
|
-
messageIsMineCallback,
|
|
39
|
-
messageIsNotMineCallback,
|
|
40
|
-
errorCallback
|
|
41
|
-
}: MessageReceivedProps) => {
|
|
42
|
-
try {
|
|
43
|
-
const messagesQueryConfig = getAllMessagesQuery({ conversationId, profileId, limit })
|
|
44
|
-
const queryData = queryClient.getQueryData<InfiniteData<FetchMessagesResponse>>(
|
|
45
|
-
messagesQueryConfig.queryKey
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
const idsList = new Set(
|
|
49
|
-
queryData?.pages.flatMap((items) => items.messages.map((msg) => msg.id))
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
if (idsList.has(data.id)) return
|
|
53
|
-
|
|
54
|
-
queryClient.setQueryData<InfiniteData<FetchMessagesResponse>>(
|
|
55
|
-
messagesQueryConfig.queryKey,
|
|
56
|
-
(oldData) =>
|
|
57
|
-
produce(oldData, (draft) => {
|
|
58
|
-
draft?.pages.at(-1)?.messages?.push(data)
|
|
59
|
-
return draft
|
|
60
|
-
})
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
const isMine = data.contactId === profileId
|
|
64
|
-
|
|
65
|
-
if (isMine) {
|
|
66
|
-
return messageIsMineCallback?.(data)
|
|
67
|
-
}
|
|
68
|
-
messageIsNotMineCallback?.(data)
|
|
69
|
-
} catch (error) {
|
|
70
|
-
errorCallback?.(error)
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
20
|
const useSubscribeMessageReceivedEvent = () => {
|
|
75
21
|
const [settings] = useWidgetSettingsAtom()
|
|
76
22
|
const [, setWidgetLoading] = useWidgetLoadingAtom()
|
|
@@ -80,78 +26,75 @@ const useSubscribeMessageReceivedEvent = () => {
|
|
|
80
26
|
const useUpdateCursorMutation = useUpdateCursor()
|
|
81
27
|
const limit = useMessagesMaxCount()
|
|
82
28
|
const isAgentMode = useIsAgentParentAtomValue()
|
|
83
|
-
const { reset: resetLoadingTimeout, startTimeout } = useResponseTimeout()
|
|
84
29
|
|
|
85
30
|
const conversationId = useMemo(() => String(settings?.conversationId), [settings?.conversationId])
|
|
86
31
|
const profileId = useMemo(() => String(profileQuery?.data?.id), [profileQuery?.data?.id])
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
32
|
+
const messagesQueryConfig = useMemo(
|
|
33
|
+
() =>
|
|
34
|
+
getAllMessagesQuery({
|
|
35
|
+
conversationId,
|
|
36
|
+
profileId,
|
|
37
|
+
limit
|
|
38
|
+
}),
|
|
39
|
+
[conversationId, limit, profileId]
|
|
94
40
|
)
|
|
95
41
|
|
|
96
|
-
const
|
|
42
|
+
const messageReceived = useCallback(
|
|
97
43
|
(data: IMessageWithSenderData) => {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
44
|
+
if (data.conversationId !== conversationId) return
|
|
45
|
+
|
|
46
|
+
const queryData = queryClient.getQueryData<InfiniteData<FetchMessagesResponse>>(
|
|
47
|
+
messagesQueryConfig.queryKey
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
const idsList = new Set(
|
|
51
|
+
queryData?.pages.flatMap((items) => items.messages.map((msg) => msg.id))
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if (idsList.has(data.id)) return
|
|
55
|
+
|
|
56
|
+
queryClient.setQueryData<InfiniteData<FetchMessagesResponse>>(
|
|
57
|
+
messagesQueryConfig.queryKey,
|
|
58
|
+
(oldData) => {
|
|
59
|
+
return produce(oldData, (draft) => {
|
|
60
|
+
draft?.pages.at(-1)?.messages?.push(data)
|
|
61
|
+
return draft
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
const isMine = data.contactId === profileId
|
|
67
|
+
|
|
68
|
+
if (!isMine) {
|
|
69
|
+
DataHubService.sendEvent({
|
|
70
|
+
schema: new ViewTutorAnswerMessageSchema({
|
|
71
|
+
correlationId: data.metadata.correlationId,
|
|
72
|
+
messageId: data.id,
|
|
73
|
+
componentSource: isAgentMode
|
|
74
|
+
? ComponentSource.PRODUCT_AGENT
|
|
75
|
+
: ComponentSource.HOTMART_TUTOR
|
|
76
|
+
})
|
|
107
77
|
})
|
|
108
|
-
})
|
|
109
78
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const errorCallback = useCallback(
|
|
116
|
-
(error: unknown) => {
|
|
117
|
-
if (error) {
|
|
118
|
-
setWidgetLoading(false)
|
|
119
|
-
console.error(error)
|
|
120
|
-
throw new Error('Error getting realtime messages')
|
|
79
|
+
addUnreadMessagesToSet({ itemId: data.id })
|
|
80
|
+
setTimeout(() => setWidgetLoading(false), 100)
|
|
81
|
+
} else {
|
|
82
|
+
// The cursor should update only with my messages
|
|
83
|
+
useUpdateCursorMutation.mutate(data.conversationId)
|
|
121
84
|
}
|
|
122
85
|
},
|
|
123
|
-
[setWidgetLoading]
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
const messageReceived = useCallback(
|
|
127
|
-
(data: IMessageWithSenderData) => {
|
|
128
|
-
if (data.conversationId === conversationId)
|
|
129
|
-
messageReceivedUtil({
|
|
130
|
-
queryClient,
|
|
131
|
-
data,
|
|
132
|
-
conversationId,
|
|
133
|
-
profileId,
|
|
134
|
-
limit,
|
|
135
|
-
messageIsMineCallback,
|
|
136
|
-
messageIsNotMineCallback,
|
|
137
|
-
errorCallback
|
|
138
|
-
})
|
|
139
|
-
},
|
|
140
86
|
[
|
|
87
|
+
addUnreadMessagesToSet,
|
|
141
88
|
conversationId,
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
messageIsMineCallback,
|
|
145
|
-
messageIsNotMineCallback,
|
|
89
|
+
isAgentMode,
|
|
90
|
+
messagesQueryConfig.queryKey,
|
|
146
91
|
profileId,
|
|
147
|
-
queryClient
|
|
92
|
+
queryClient,
|
|
93
|
+
setWidgetLoading,
|
|
94
|
+
useUpdateCursorMutation
|
|
148
95
|
]
|
|
149
96
|
)
|
|
150
97
|
|
|
151
|
-
const startLoadingTimeout = useCallback(() => {
|
|
152
|
-
startTimeout(() => setWidgetLoading(false))
|
|
153
|
-
}, [setWidgetLoading, startTimeout])
|
|
154
|
-
|
|
155
98
|
useEffect(() => {
|
|
156
99
|
SparkieService.subscribeEvents({ messageReceived })
|
|
157
100
|
|
|
@@ -159,14 +102,6 @@ const useSubscribeMessageReceivedEvent = () => {
|
|
|
159
102
|
SparkieService.removeEventSubscription({ messageReceived })
|
|
160
103
|
}
|
|
161
104
|
}, [messageReceived])
|
|
162
|
-
|
|
163
|
-
useEffect(() => {
|
|
164
|
-
startLoadingTimeout()
|
|
165
|
-
|
|
166
|
-
return () => {
|
|
167
|
-
resetLoadingTimeout()
|
|
168
|
-
}
|
|
169
|
-
}, [resetLoadingTimeout, startLoadingTimeout])
|
|
170
105
|
}
|
|
171
106
|
|
|
172
107
|
export default useSubscribeMessageReceivedEvent
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { Message } from '@hotmart
|
|
2
|
-
import type MessageService from '@hotmart-org-ca/sparkie/dist/MessageService'
|
|
1
|
+
import type { Message } from '@hotmart/sparkie/dist/MessageService'
|
|
3
2
|
|
|
4
3
|
import { ApiError } from '@/src/config/request'
|
|
5
4
|
import { HttpCodes } from '@/src/lib/utils'
|
|
@@ -16,7 +15,7 @@ import type {
|
|
|
16
15
|
} from './types'
|
|
17
16
|
|
|
18
17
|
class MessagesService {
|
|
19
|
-
async getSparkieMessageService()
|
|
18
|
+
async getSparkieMessageService() {
|
|
20
19
|
try {
|
|
21
20
|
const messageService = await SparkieService.getMessageService()
|
|
22
21
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import Sparkie from '@hotmart
|
|
1
|
+
import Sparkie from '@hotmart/sparkie'
|
|
2
2
|
|
|
3
3
|
import { MSG_MAX_COUNT } from '../messages'
|
|
4
4
|
|
|
@@ -110,7 +110,8 @@ class SparkieService {
|
|
|
110
110
|
|
|
111
111
|
while (attempt <= maxRetries) {
|
|
112
112
|
try {
|
|
113
|
-
if (!token
|
|
113
|
+
if (!token || !token.trim())
|
|
114
|
+
throw new Error('Invalid or missing token for Sparkie initialization')
|
|
114
115
|
|
|
115
116
|
const sparkie = this.sparkieInstance
|
|
116
117
|
const service = await sparkie.init(token, { skipPresenceSetup })
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useTranslation } from 'react-i18next'
|
|
2
|
+
|
|
3
|
+
import { Icon } from '@/src/lib/components/icons'
|
|
4
|
+
|
|
5
|
+
const AIDisclaimer = () => {
|
|
6
|
+
const { t } = useTranslation()
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<div className='mt-4 flex w-full items-center gap-1 text-xs text-neutral-500'>
|
|
10
|
+
<p className='mb-0'>{t('ai_disclaimer.technology')}</p>
|
|
11
|
+
|
|
12
|
+
<Icon name='tutor-logo' className='inline-flex h-3 w-3 align-middle' />
|
|
13
|
+
|
|
14
|
+
<p className='mb-0'>{t('ai_disclaimer.hotmart_ai')}</p>
|
|
15
|
+
</div>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default AIDisclaimer
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as AIDisclaimer } from './ai-disclaimer'
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
useWidgetTabsValueAtom
|
|
18
18
|
} from '../../store'
|
|
19
19
|
import { testQuestionRegex } from '../../utils'
|
|
20
|
+
import { AIDisclaimer } from '../ai-disclaimer'
|
|
20
21
|
import { WidgetHeader } from '../header'
|
|
21
22
|
import { PageLayout } from '../page-layout'
|
|
22
23
|
|
|
@@ -94,14 +95,20 @@ function ChatPage() {
|
|
|
94
95
|
return (
|
|
95
96
|
<PageLayout
|
|
96
97
|
asideChild={
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
<>
|
|
99
|
+
<ChatInput
|
|
100
|
+
name='new-chat-msg-input'
|
|
101
|
+
ref={chatInputRef}
|
|
102
|
+
onSend={widgetTabs.currentTab === 'chat' ? handleSendMessage : undefined}
|
|
103
|
+
loading={sendTextMessageMutation.isPending}
|
|
104
|
+
inputDisabled={messagesQuery?.isLoading}
|
|
105
|
+
buttonDisabled={widgetLoading || messagesQuery?.isLoading || !value.trim()}
|
|
106
|
+
/>
|
|
107
|
+
|
|
108
|
+
<div className='mx-auto w-fit'>
|
|
109
|
+
<AIDisclaimer />
|
|
110
|
+
</div>
|
|
111
|
+
</>
|
|
105
112
|
}>
|
|
106
113
|
<div className='max-md:px-[1.125rem] max-md:pt-[1.125rem] md:px-5 md:pt-5'>
|
|
107
114
|
<WidgetHeader
|
|
@@ -1,9 +1,21 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
|
|
1
3
|
import { useSubscribeMessageReceivedEvent } from '@/src/modules/messages/hooks'
|
|
2
4
|
import { useSubscribeThreadClosedEvent } from '@/src/modules/thread/hooks'
|
|
3
5
|
import { useListenToVisibilityEvents } from '../../hooks'
|
|
4
6
|
import { useWidgetTabsAtom } from '../../store'
|
|
5
7
|
import { WIDGET_TABS } from '../constants'
|
|
6
8
|
|
|
9
|
+
// TODO: REMOVE
|
|
10
|
+
const hotmartRumKey = 'app-tutor-ai-consumer::hotmart-rum::activate'
|
|
11
|
+
const useSentryDebugger = () => {
|
|
12
|
+
const [logError] = useState(() => Boolean(window?.localStorage?.getItem?.(hotmartRumKey)))
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (logError) throw new Error(hotmartRumKey)
|
|
16
|
+
}, [logError])
|
|
17
|
+
}
|
|
18
|
+
|
|
7
19
|
function WidgetContainer() {
|
|
8
20
|
const [widgetTabs] = useWidgetTabsAtom()
|
|
9
21
|
|
|
@@ -11,6 +23,8 @@ function WidgetContainer() {
|
|
|
11
23
|
useSubscribeThreadClosedEvent()
|
|
12
24
|
useListenToVisibilityEvents()
|
|
13
25
|
|
|
26
|
+
useSentryDebugger()
|
|
27
|
+
|
|
14
28
|
return (
|
|
15
29
|
<div className='flex h-full flex-col items-center justify-stretch overflow-hidden'>
|
|
16
30
|
{WIDGET_TABS[widgetTabs.currentTab]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import clsx from 'clsx'
|
|
2
2
|
import { useTranslation } from 'react-i18next'
|
|
3
3
|
|
|
4
|
+
import { useIsAgentParentAtomValue } from '../../store'
|
|
4
5
|
import { AIAvatar } from '../ai-avatar'
|
|
5
6
|
|
|
6
7
|
export type GreetingsCardProps = {
|
|
@@ -11,6 +12,7 @@ export type GreetingsCardProps = {
|
|
|
11
12
|
|
|
12
13
|
function GreetingsCard({ author, tutorName, isDarkTheme = false }: GreetingsCardProps) {
|
|
13
14
|
const { t } = useTranslation()
|
|
15
|
+
const isAgentMode = useIsAgentParentAtomValue()
|
|
14
16
|
|
|
15
17
|
return (
|
|
16
18
|
<div className='flex flex-col items-center justify-center'>
|
|
@@ -31,7 +33,12 @@ function GreetingsCard({ author, tutorName, isDarkTheme = false }: GreetingsCard
|
|
|
31
33
|
'text-white': isDarkTheme,
|
|
32
34
|
'text-gray-900': !isDarkTheme
|
|
33
35
|
})}>
|
|
34
|
-
{t(
|
|
36
|
+
{t(
|
|
37
|
+
isAgentMode
|
|
38
|
+
? 'general.greetings.agentFirstMessage'
|
|
39
|
+
: 'general.greetings.firstMessage',
|
|
40
|
+
{ tutorName }
|
|
41
|
+
)}
|
|
35
42
|
</h3>
|
|
36
43
|
</div>
|
|
37
44
|
<p
|
|
@@ -39,7 +46,7 @@ function GreetingsCard({ author, tutorName, isDarkTheme = false }: GreetingsCard
|
|
|
39
46
|
'text-gray-400': isDarkTheme,
|
|
40
47
|
'text-neutral-600': !isDarkTheme
|
|
41
48
|
})}>
|
|
42
|
-
{t('general.greetings.description')}
|
|
49
|
+
{t(isAgentMode ? 'general.greetings.agentDescription' : 'general.greetings.description')}
|
|
43
50
|
</p>
|
|
44
51
|
</div>
|
|
45
52
|
</div>
|
|
@@ -12,6 +12,7 @@ import { useInitSparkie } from '@/src/modules/sparkie/hooks/use-init-sparkie'
|
|
|
12
12
|
import { TutorWidgetEvents } from '../../events'
|
|
13
13
|
import { useWidgetLoadingAtomValue, useWidgetSettingsAtom, useWidgetTabsAtom } from '../../store'
|
|
14
14
|
import { testQuestionRegex } from '../../utils'
|
|
15
|
+
import { AIDisclaimer } from '../ai-disclaimer'
|
|
15
16
|
import { GreetingsCard } from '../greetings-card'
|
|
16
17
|
import { WidgetHeader } from '../header'
|
|
17
18
|
import { PageLayout } from '../page-layout'
|
|
@@ -133,13 +134,19 @@ function WidgetStarterPage() {
|
|
|
133
134
|
return (
|
|
134
135
|
<PageLayout
|
|
135
136
|
asideChild={
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
137
|
+
<>
|
|
138
|
+
<ChatInput
|
|
139
|
+
name='new-chat-msg-input'
|
|
140
|
+
ref={chatInputRef}
|
|
141
|
+
onSend={handleSend}
|
|
142
|
+
buttonDisabled={widgetLoading || !chatInputValue.trim()}
|
|
143
|
+
loading={!isSparkieReady}
|
|
144
|
+
/>
|
|
145
|
+
|
|
146
|
+
<div className='mx-auto w-fit'>
|
|
147
|
+
<AIDisclaimer />
|
|
148
|
+
</div>
|
|
149
|
+
</>
|
|
143
150
|
}>
|
|
144
151
|
<div className='grid-areas-[a_b] grid h-full grid-cols-1 grid-rows-[1fr_auto]'>
|
|
145
152
|
<div className='grid-area-[a] flex min-h-0 flex-col max-md:px-[1.125rem] max-md:pt-[1.125rem] md:px-5 md:pt-5'>
|
package/src/types.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import type { QueryClient } from '@tanstack/react-query'
|
|
2
|
-
import type { Root } from 'react-dom/client'
|
|
3
|
-
|
|
4
1
|
import type { ILanguages } from './config/i18n'
|
|
5
2
|
|
|
6
3
|
export type StartTutorWidgetProps = {
|
|
@@ -71,9 +68,3 @@ export interface ICustomEvent<T = object> {
|
|
|
71
68
|
handler: (listener: EventListenerOrEventListenerObject) => () => void | Promise<void>
|
|
72
69
|
dispatch: <D = unknown>(detail?: D) => void
|
|
73
70
|
}
|
|
74
|
-
|
|
75
|
-
export type WidgetInstance = {
|
|
76
|
-
root: Root
|
|
77
|
-
container: HTMLElement
|
|
78
|
-
queryClient: QueryClient
|
|
79
|
-
}
|
package/src/index.backup.tsx
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import './config/styles/global.css'
|
|
2
|
-
import './config/styles/index.css'
|
|
3
|
-
|
|
4
|
-
import { StrictMode } from 'react'
|
|
5
|
-
import { createRoot } from 'react-dom/client'
|
|
6
|
-
|
|
7
|
-
import { initTheme } from '@/src/config/theme'
|
|
8
|
-
import { version } from '../package.json'
|
|
9
|
-
|
|
10
|
-
import { initLanguage } from './config/i18n'
|
|
11
|
-
import { devMode, productionMode } from './lib/utils'
|
|
12
|
-
import { Main } from './main'
|
|
13
|
-
import { TutorWidgetEvents } from './modules/widget'
|
|
14
|
-
import type { Theme, WidgetSettingProps } from './types'
|
|
15
|
-
|
|
16
|
-
const loadMainStyles = () => {
|
|
17
|
-
const isProduction = productionMode
|
|
18
|
-
const bundlePath = !isProduction
|
|
19
|
-
? `${process.env.BUNDLE_PATH}/`
|
|
20
|
-
: `${process.env.BUNDLE_PATH}/${process.env.APP_NAME}/_current/`
|
|
21
|
-
|
|
22
|
-
const cssPath = `${bundlePath}app-tutor-ai-consumer.css?v=${version}`
|
|
23
|
-
|
|
24
|
-
if (!document.querySelector(`link[href="${cssPath}"]`)) {
|
|
25
|
-
const linkElement = document.createElement('link')
|
|
26
|
-
linkElement.rel = 'stylesheet'
|
|
27
|
-
linkElement.href = cssPath
|
|
28
|
-
document.head.appendChild(linkElement)
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
window.startChatWidget = async (
|
|
33
|
-
elementId = 'tutor-chat-app-widget',
|
|
34
|
-
settings: WidgetSettingProps
|
|
35
|
-
) => {
|
|
36
|
-
if (!devMode) {
|
|
37
|
-
loadMainStyles()
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const rootElement = document.getElementById(elementId) as HTMLElement
|
|
41
|
-
|
|
42
|
-
if (!rootElement) return
|
|
43
|
-
|
|
44
|
-
rootElement.setAttribute('id', 'hotmart-app-tutor-ai-consumer-root')
|
|
45
|
-
const theme = (rootElement.getAttribute('data-theme') ?? 'dark') as Theme
|
|
46
|
-
const root = createRoot(rootElement)
|
|
47
|
-
|
|
48
|
-
await initLanguage(settings.locale)
|
|
49
|
-
initTheme(theme)
|
|
50
|
-
|
|
51
|
-
if (root) {
|
|
52
|
-
root.render(
|
|
53
|
-
<StrictMode>
|
|
54
|
-
<Main settings={{ ...settings, config: { ...settings.config, theme } }} />
|
|
55
|
-
</StrictMode>
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
window.closeChatWidget = () =>
|
|
61
|
-
Promise.resolve(TutorWidgetEvents['c3po-app-widget-close'].dispatch())
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default as useResponseTimeout } from './use-response-timeout'
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { useCallback, useRef } from 'react'
|
|
2
|
-
|
|
3
|
-
const FIVE_MINUTES = 5 * 60 * 1000
|
|
4
|
-
|
|
5
|
-
function useResponseTimeout() {
|
|
6
|
-
const timeoutRef = useRef<NodeJS.Timeout>(null)
|
|
7
|
-
const isWaitingRef = useRef(false)
|
|
8
|
-
|
|
9
|
-
const startTimeout = useCallback((callback: () => void) => {
|
|
10
|
-
if (timeoutRef.current) clearTimeout(timeoutRef.current)
|
|
11
|
-
|
|
12
|
-
isWaitingRef.current = true
|
|
13
|
-
|
|
14
|
-
timeoutRef.current = setTimeout(() => {
|
|
15
|
-
callback()
|
|
16
|
-
isWaitingRef.current = false
|
|
17
|
-
}, FIVE_MINUTES)
|
|
18
|
-
}, [])
|
|
19
|
-
|
|
20
|
-
const reset = useCallback(() => {
|
|
21
|
-
if (timeoutRef.current) {
|
|
22
|
-
clearTimeout(timeoutRef.current)
|
|
23
|
-
timeoutRef.current = null
|
|
24
|
-
}
|
|
25
|
-
isWaitingRef.current = false
|
|
26
|
-
}, [])
|
|
27
|
-
|
|
28
|
-
const cleanup = useCallback(() => {
|
|
29
|
-
if (timeoutRef.current) {
|
|
30
|
-
clearTimeout(timeoutRef.current)
|
|
31
|
-
}
|
|
32
|
-
}, [])
|
|
33
|
-
|
|
34
|
-
return {
|
|
35
|
-
startTimeout,
|
|
36
|
-
cleanup,
|
|
37
|
-
reset,
|
|
38
|
-
isWaiting: isWaitingRef.current
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export default useResponseTimeout
|