app-tutor-ai-consumer 1.18.2 → 1.20.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/public/assets/svg/error-dark.svg +27 -0
- package/public/assets/svg/error-light.svg +27 -0
- 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/errors/generic/generic-error.tsx +58 -3
- package/src/lib/components/icons/arrow-up.svg +5 -0
- package/src/lib/components/icons/copy.svg +5 -0
- package/src/lib/components/icons/icon-names.d.ts +3 -0
- 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 +1 -1
- package/src/modules/widget/components/header/header.tsx +6 -4
- package/src/modules/widget/components/starter-page/starter-page.spec.tsx +4 -1
- package/src/modules/widget/components/starter-page/starter-page.tsx +31 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
# [1.20.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.19.0...v1.20.0) (2025-07-29)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- add generic error page ([2ac19d0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/2ac19d0d033731508580778dcf679fb9c71ca24f))
|
|
6
|
+
|
|
7
|
+
# [1.19.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.18.2...v1.19.0) (2025-07-29)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- add message actions ([3a8d5dc](https://github.com/Hotmart-Org/app-tutor-ai-consumer/commit/3a8d5dcd1ae75ce40b7cbf6edf8629da921c9308))
|
|
12
|
+
|
|
1
13
|
## [1.18.2](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.18.1...v1.18.2) (2025-07-28)
|
|
2
14
|
|
|
3
15
|
## [1.18.1](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.18.0...v1.18.1) (2025-07-24)
|
package/package.json
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<circle opacity="0.5" cx="80" cy="80" r="80" fill="#282C2F"/>
|
|
3
|
+
<g filter="url(#filter0_d_22198_69476)">
|
|
4
|
+
<path d="M32.8 47.9999C32.8 47.1162 33.5164 46.3999 34.4 46.3999H125.6C126.484 46.3999 127.2 47.1162 127.2 47.9999V55.9999H32.8V47.9999Z" fill="#464B52"/>
|
|
5
|
+
<path d="M32.8 56H127.2V112C127.2 112.884 126.484 113.6 125.6 113.6H34.4001C33.5164 113.6 32.8 112.884 32.8 112V56Z" fill="white"/>
|
|
6
|
+
<rect width="11.2" height="3.2" rx="1.6" transform="matrix(1 0 0 -1 74.3999 91.2)" fill="#C9CED4"/>
|
|
7
|
+
<rect width="4" height="4" rx="2" transform="matrix(1 0 0 -1 68 81.6001)" fill="#C9CED4"/>
|
|
8
|
+
<rect width="4" height="4" rx="2" transform="matrix(1 0 0 -1 88 81.6001)" fill="#C9CED4"/>
|
|
9
|
+
</g>
|
|
10
|
+
<rect width="3.2" height="3.2" rx="1.6" transform="matrix(1 0 0 -1 36 52.8)" fill="#E37570"/>
|
|
11
|
+
<rect width="3.2" height="3.2" rx="1.6" transform="matrix(1 0 0 -1 41.6001 52.8)" fill="#EFBA0F"/>
|
|
12
|
+
<rect width="3.2" height="3.2" rx="1.6" transform="matrix(1 0 0 -1 47.2 52.8)" fill="#4ACC82"/>
|
|
13
|
+
<path d="M141.143 46.1714C141.143 53.9981 134.798 60.3429 126.971 60.3429C119.145 60.3429 112.8 53.9981 112.8 46.1714C112.8 38.3447 119.145 32 126.971 32C134.798 32 141.143 38.3447 141.143 46.1714Z" fill="#5981E3"/>
|
|
14
|
+
<path d="M128.297 49.5064H125.566L124.993 38.562H128.87L128.297 49.5064ZM124.947 53.3381C124.947 52.6345 125.138 52.145 125.52 51.8697C125.903 51.579 126.369 51.4337 126.92 51.4337C127.455 51.4337 127.914 51.579 128.297 51.8697C128.679 52.145 128.87 52.6345 128.87 53.3381C128.87 54.0111 128.679 54.5006 128.297 54.8065C127.914 55.0971 127.455 55.2425 126.92 55.2425C126.369 55.2425 125.903 55.0971 125.52 54.8065C125.138 54.5006 124.947 54.0111 124.947 53.3381Z" fill="white"/>
|
|
15
|
+
<defs>
|
|
16
|
+
<filter id="filter0_d_22198_69476" x="24.8" y="44.7999" width="110.4" height="83.2002" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
17
|
+
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
18
|
+
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
|
19
|
+
<feMorphology radius="4.8" operator="erode" in="SourceAlpha" result="effect1_dropShadow_22198_69476"/>
|
|
20
|
+
<feOffset dy="6.4"/>
|
|
21
|
+
<feGaussianBlur stdDeviation="6.4"/>
|
|
22
|
+
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.12 0"/>
|
|
23
|
+
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_22198_69476"/>
|
|
24
|
+
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_22198_69476" result="shape"/>
|
|
25
|
+
</filter>
|
|
26
|
+
</defs>
|
|
27
|
+
</svg>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<circle opacity="0.5" cx="80" cy="80" r="80" fill="#E6E9ED"/>
|
|
3
|
+
<g filter="url(#filter0_d_22198_92633)">
|
|
4
|
+
<path d="M32.8 47.9999C32.8 47.1162 33.5164 46.3999 34.4 46.3999H125.6C126.484 46.3999 127.2 47.1162 127.2 47.9999V55.9999H32.8V47.9999Z" fill="#464B52"/>
|
|
5
|
+
<path d="M32.8 56H127.2V112C127.2 112.884 126.484 113.6 125.6 113.6H34.4001C33.5164 113.6 32.8 112.884 32.8 112V56Z" fill="white"/>
|
|
6
|
+
<rect width="11.2" height="3.2" rx="1.6" transform="matrix(1 0 0 -1 74.3999 91.2002)" fill="#C9CED4"/>
|
|
7
|
+
<rect width="4" height="4" rx="2" transform="matrix(1 0 0 -1 68 81.6001)" fill="#C9CED4"/>
|
|
8
|
+
<rect width="4" height="4" rx="2" transform="matrix(1 0 0 -1 88 81.6001)" fill="#C9CED4"/>
|
|
9
|
+
</g>
|
|
10
|
+
<rect width="3.2" height="3.2" rx="1.6" transform="matrix(1 0 0 -1 36 52.7998)" fill="#E37570"/>
|
|
11
|
+
<rect width="3.2" height="3.2" rx="1.6" transform="matrix(1 0 0 -1 41.6001 52.7998)" fill="#EFBA0F"/>
|
|
12
|
+
<rect width="3.2" height="3.2" rx="1.6" transform="matrix(1 0 0 -1 47.2 52.7998)" fill="#4ACC82"/>
|
|
13
|
+
<path d="M141.143 46.1714C141.143 53.9981 134.798 60.3429 126.971 60.3429C119.145 60.3429 112.8 53.9981 112.8 46.1714C112.8 38.3447 119.145 32 126.971 32C134.798 32 141.143 38.3447 141.143 46.1714Z" fill="#5981E3"/>
|
|
14
|
+
<path d="M128.297 49.5064H125.566L124.993 38.562H128.87L128.297 49.5064ZM124.947 53.3381C124.947 52.6345 125.138 52.145 125.52 51.8697C125.903 51.579 126.369 51.4337 126.92 51.4337C127.455 51.4337 127.914 51.579 128.297 51.8697C128.679 52.145 128.87 52.6345 128.87 53.3381C128.87 54.0111 128.679 54.5006 128.297 54.8065C127.914 55.0971 127.455 55.2425 126.92 55.2425C126.369 55.2425 125.903 55.0971 125.52 54.8065C125.138 54.5006 124.947 54.0111 124.947 53.3381Z" fill="white"/>
|
|
15
|
+
<defs>
|
|
16
|
+
<filter id="filter0_d_22198_92633" x="24.8" y="44.7999" width="110.4" height="83.2002" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
17
|
+
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
18
|
+
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
|
19
|
+
<feMorphology radius="4.8" operator="erode" in="SourceAlpha" result="effect1_dropShadow_22198_92633"/>
|
|
20
|
+
<feOffset dy="6.4"/>
|
|
21
|
+
<feGaussianBlur stdDeviation="6.4"/>
|
|
22
|
+
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.12 0"/>
|
|
23
|
+
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_22198_92633"/>
|
|
24
|
+
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_22198_92633" result="shape"/>
|
|
25
|
+
</filter>
|
|
26
|
+
</defs>
|
|
27
|
+
</svg>
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { http, HttpResponse } from 'msw'
|
|
2
2
|
|
|
3
|
+
import type { IMessageWithSenderData } from '@/src/modules/messages'
|
|
4
|
+
import { MessagesEndpoints, MSG_MAX_COUNT } from '@/src/modules/messages'
|
|
5
|
+
import IMessageWithSenderDataMock from '@/src/modules/messages/__tests__/imessage-with-sender-data.mock'
|
|
3
6
|
import { ProfileEndpoints } from '@/src/modules/profile'
|
|
4
7
|
import ProfileAPIPropsBuilder from '@/src/modules/profile/__tests__/profile-api-props.builder'
|
|
5
8
|
|
|
@@ -12,5 +15,14 @@ export const handlers = [
|
|
|
12
15
|
}),
|
|
13
16
|
http.all(ProfileEndpoints.getProfile(), () => {
|
|
14
17
|
return HttpResponse.json(new ProfileAPIPropsBuilder())
|
|
18
|
+
}),
|
|
19
|
+
http.all(MessagesEndpoints.getAll(':conversationId'), ({ request }) => {
|
|
20
|
+
const limit = Number(new URL(request.url)?.searchParams?.get?.('limit'))
|
|
21
|
+
|
|
22
|
+
return HttpResponse.json(
|
|
23
|
+
new IMessageWithSenderDataMock().getMany(
|
|
24
|
+
isNaN(limit) ? MSG_MAX_COUNT : limit
|
|
25
|
+
) as IMessageWithSenderData[]
|
|
26
|
+
)
|
|
15
27
|
})
|
|
16
28
|
]
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import './index'
|
|
2
2
|
|
|
3
|
+
import Chance from 'chance'
|
|
3
4
|
import { v4 } from 'uuid'
|
|
4
5
|
|
|
5
6
|
import { LANGUAGES } from './config/i18n'
|
|
6
7
|
import { devMode } from './lib/utils'
|
|
7
8
|
|
|
9
|
+
const chance = new Chance()
|
|
8
10
|
const rootId = 'app-tutor-ai-widget'
|
|
9
11
|
|
|
10
12
|
if (devMode) {
|
|
@@ -36,8 +38,9 @@ if (devMode) {
|
|
|
36
38
|
productName: 'Curso de Assinatura',
|
|
37
39
|
productId: 4266504,
|
|
38
40
|
sessionId: v4(),
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
user: {
|
|
42
|
+
id: v4(),
|
|
43
|
+
name: chance.name()
|
|
41
44
|
}
|
|
42
45
|
})
|
|
43
46
|
})()
|
package/src/index.tsx
CHANGED
|
@@ -37,6 +37,9 @@ window.startChatWidget = async (
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
const rootElement = document.getElementById(elementId) as HTMLElement
|
|
40
|
+
|
|
41
|
+
if (!rootElement) return
|
|
42
|
+
|
|
40
43
|
rootElement.setAttribute('id', 'hotmart-app-tutor-ai-consumer-root')
|
|
41
44
|
const theme = (rootElement.getAttribute('data-theme') ?? 'dark') as Theme
|
|
42
45
|
const root = createRoot(rootElement)
|
|
@@ -3,6 +3,8 @@ import type { ButtonHTMLAttributes, PropsWithChildren } from 'react'
|
|
|
3
3
|
|
|
4
4
|
import { Spinner } from '../spinner'
|
|
5
5
|
|
|
6
|
+
import styles from './styles.module.css'
|
|
7
|
+
|
|
6
8
|
export type ButtonProps = PropsWithChildren<
|
|
7
9
|
ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
8
10
|
variant?: 'brand' | 'secondary' | 'primary' | 'tertiary' | 'gradient-outline'
|
|
@@ -24,8 +26,7 @@ function Button({
|
|
|
24
26
|
const defaultBorder = 'border border-transparent'
|
|
25
27
|
const defaultPadding = 'px-4 py-2'
|
|
26
28
|
const disabledClasses = 'cursor-not-allowed'
|
|
27
|
-
|
|
28
|
-
const content = loading ? <Spinner className='h-full w-full text-current' /> : children
|
|
29
|
+
const gridClasses = 'grid [grid-template-areas:stack]'
|
|
29
30
|
|
|
30
31
|
if (!show) return null
|
|
31
32
|
|
|
@@ -34,6 +35,7 @@ function Button({
|
|
|
34
35
|
return (
|
|
35
36
|
<button
|
|
36
37
|
className={clsx(
|
|
38
|
+
gridClasses,
|
|
37
39
|
defaultClasses,
|
|
38
40
|
'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-1000 group-hover:from-[var(--ai-color-gradient-primary)] group-hover:to-[var(--ai-color-gradient-accent)]',
|
|
39
41
|
className
|
|
@@ -43,15 +45,29 @@ function Button({
|
|
|
43
45
|
aria-busy={loading}>
|
|
44
46
|
<span
|
|
45
47
|
data-label='text-content'
|
|
46
|
-
className=
|
|
47
|
-
|
|
48
|
+
className={clsx(
|
|
49
|
+
'relative flex-1 rounded-lg bg-neutral-100 px-5 py-2.5 text-neutral-1000 transition-all duration-75 ease-in group-hover:bg-transparent',
|
|
50
|
+
'flex flex-nowrap gap-2 [grid-area:stack]',
|
|
51
|
+
{
|
|
52
|
+
visible: !loading,
|
|
53
|
+
invisible: loading
|
|
54
|
+
}
|
|
55
|
+
)}>
|
|
56
|
+
{children}
|
|
48
57
|
</span>
|
|
58
|
+
<Spinner
|
|
59
|
+
className={clsx('mx-auto my-auto h-[1em] w-[1em] text-current', '[grid-area:stack]', {
|
|
60
|
+
visible: loading,
|
|
61
|
+
invisible: !loading
|
|
62
|
+
})}
|
|
63
|
+
/>
|
|
49
64
|
</button>
|
|
50
65
|
)
|
|
51
66
|
case 'primary':
|
|
52
67
|
return (
|
|
53
68
|
<button
|
|
54
69
|
className={clsx(
|
|
70
|
+
gridClasses,
|
|
55
71
|
defaultPadding,
|
|
56
72
|
defaultClasses,
|
|
57
73
|
defaultBorder,
|
|
@@ -61,13 +77,27 @@ function Button({
|
|
|
61
77
|
{...props}
|
|
62
78
|
disabled={props.disabled || loading}
|
|
63
79
|
aria-busy={loading}>
|
|
64
|
-
|
|
80
|
+
<span
|
|
81
|
+
data-label='text-content'
|
|
82
|
+
className={clsx('flex flex-nowrap gap-2 [grid-area:stack]', {
|
|
83
|
+
visible: !loading,
|
|
84
|
+
invisible: loading
|
|
85
|
+
})}>
|
|
86
|
+
{children}
|
|
87
|
+
</span>
|
|
88
|
+
<Spinner
|
|
89
|
+
className={clsx('mx-auto my-auto h-[1em] w-[1em] text-current', '[grid-area:stack]', {
|
|
90
|
+
visible: loading,
|
|
91
|
+
invisible: !loading
|
|
92
|
+
})}
|
|
93
|
+
/>
|
|
65
94
|
</button>
|
|
66
95
|
)
|
|
67
96
|
case 'secondary':
|
|
68
97
|
return (
|
|
69
98
|
<button
|
|
70
99
|
className={clsx(
|
|
100
|
+
gridClasses,
|
|
71
101
|
defaultPadding,
|
|
72
102
|
defaultClasses,
|
|
73
103
|
defaultBorder,
|
|
@@ -77,29 +107,62 @@ function Button({
|
|
|
77
107
|
{...props}
|
|
78
108
|
disabled={props.disabled || loading}
|
|
79
109
|
aria-busy={loading}>
|
|
80
|
-
|
|
110
|
+
<span
|
|
111
|
+
data-label='text-content'
|
|
112
|
+
className={clsx('flex flex-nowrap gap-2 [grid-area:stack]', {
|
|
113
|
+
visible: !loading,
|
|
114
|
+
invisible: loading
|
|
115
|
+
})}>
|
|
116
|
+
{children}
|
|
117
|
+
</span>
|
|
118
|
+
<Spinner
|
|
119
|
+
className={clsx('mx-auto my-auto h-[1em] w-[1em] text-current', '[grid-area:stack]', {
|
|
120
|
+
visible: loading,
|
|
121
|
+
invisible: !loading
|
|
122
|
+
})}
|
|
123
|
+
/>
|
|
81
124
|
</button>
|
|
82
125
|
)
|
|
83
126
|
case 'tertiary':
|
|
84
127
|
return (
|
|
85
128
|
<button
|
|
86
129
|
className={clsx(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
defaultBorder,
|
|
90
|
-
'text-1000 bg-transparent hover:bg-neutral-900',
|
|
130
|
+
`relative grid place-items-center rounded bg-transparent`,
|
|
131
|
+
styles.tertiary,
|
|
91
132
|
className
|
|
92
133
|
)}
|
|
93
134
|
{...props}
|
|
94
135
|
disabled={props.disabled || loading}
|
|
95
136
|
aria-busy={loading}>
|
|
96
|
-
|
|
137
|
+
<span
|
|
138
|
+
data-label='text-content'
|
|
139
|
+
className={clsx(`col-start-1 row-start-1 flex flex-nowrap gap-2`, {
|
|
140
|
+
invisible: loading,
|
|
141
|
+
visible: !loading
|
|
142
|
+
})}>
|
|
143
|
+
{children}
|
|
144
|
+
</span>
|
|
145
|
+
|
|
146
|
+
{loading && (
|
|
147
|
+
<div className='col-start-1 row-start-1'>
|
|
148
|
+
<Spinner
|
|
149
|
+
className={clsx(
|
|
150
|
+
'col-start-1 row-start-1 mx-auto my-auto h-[1em] w-[1em] text-current',
|
|
151
|
+
{
|
|
152
|
+
visible: loading,
|
|
153
|
+
invisible: !loading
|
|
154
|
+
}
|
|
155
|
+
)}
|
|
156
|
+
/>
|
|
157
|
+
</div>
|
|
158
|
+
)}
|
|
97
159
|
</button>
|
|
98
160
|
)
|
|
99
161
|
case 'brand':
|
|
100
162
|
return (
|
|
101
163
|
<button
|
|
102
164
|
className={clsx(
|
|
165
|
+
gridClasses,
|
|
103
166
|
defaultPadding,
|
|
104
167
|
defaultClasses,
|
|
105
168
|
defaultBorder,
|
|
@@ -109,25 +172,53 @@ function Button({
|
|
|
109
172
|
{...props}
|
|
110
173
|
disabled={props.disabled || loading}
|
|
111
174
|
aria-busy={loading}>
|
|
112
|
-
|
|
175
|
+
<span
|
|
176
|
+
data-label='text-content'
|
|
177
|
+
className={clsx('flex flex-nowrap gap-2 [grid-area:stack]', {
|
|
178
|
+
visible: !loading,
|
|
179
|
+
invisible: loading
|
|
180
|
+
})}>
|
|
181
|
+
{children}
|
|
182
|
+
</span>
|
|
183
|
+
<Spinner
|
|
184
|
+
className={clsx('mx-auto my-auto h-[1em] w-[1em] text-current', '[grid-area:stack]', {
|
|
185
|
+
visible: loading,
|
|
186
|
+
invisible: !loading
|
|
187
|
+
})}
|
|
188
|
+
/>
|
|
113
189
|
</button>
|
|
114
190
|
)
|
|
115
191
|
default:
|
|
116
192
|
return (
|
|
117
193
|
<button
|
|
118
194
|
className={clsx(
|
|
119
|
-
|
|
195
|
+
gridClasses,
|
|
196
|
+
'rounded-full outline-none transition-colors duration-300 ease-in',
|
|
120
197
|
{
|
|
121
198
|
'cursor-pointer ring-primary-500 hover:bg-neutral-300 focus:bg-neutral-300 focus-visible:ring-2':
|
|
122
199
|
!props.disabled,
|
|
123
200
|
[disabledClasses]: props.disabled
|
|
124
201
|
},
|
|
202
|
+
styles.defaultButton,
|
|
125
203
|
className
|
|
126
204
|
)}
|
|
127
205
|
{...props}
|
|
128
206
|
disabled={props.disabled || loading}
|
|
129
207
|
aria-busy={loading}>
|
|
130
|
-
|
|
208
|
+
<span
|
|
209
|
+
data-label='text-content'
|
|
210
|
+
className={clsx('flex flex-nowrap gap-2 [grid-area:stack]', {
|
|
211
|
+
visible: !loading,
|
|
212
|
+
invisible: loading
|
|
213
|
+
})}>
|
|
214
|
+
{children}
|
|
215
|
+
</span>
|
|
216
|
+
<Spinner
|
|
217
|
+
className={clsx('mx-auto my-auto h-[1em] w-[1em] text-current', '[grid-area:stack]', {
|
|
218
|
+
visible: loading,
|
|
219
|
+
invisible: !loading
|
|
220
|
+
})}
|
|
221
|
+
/>
|
|
131
222
|
</button>
|
|
132
223
|
)
|
|
133
224
|
}
|
|
@@ -1,12 +1,67 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
1
2
|
import { useTranslation } from 'react-i18next'
|
|
2
3
|
|
|
4
|
+
import ErrorDarkSVG from '@/public/assets/svg/error-dark.svg?url'
|
|
5
|
+
import ErrorLightSVG from '@/public/assets/svg/error-light.svg?url'
|
|
6
|
+
import { Button } from '@/src/lib/components'
|
|
7
|
+
import { PageLayout, TutorWidgetEvents, useWidgetSettingsAtom } from '@/src/modules/widget'
|
|
8
|
+
import { WidgetHeader } from '@/src/modules/widget/components/header'
|
|
9
|
+
|
|
3
10
|
function GenericError() {
|
|
4
11
|
const { t } = useTranslation()
|
|
12
|
+
const [settings] = useWidgetSettingsAtom()
|
|
13
|
+
const isDarkMode = settings?.config?.theme === 'dark'
|
|
5
14
|
|
|
6
15
|
return (
|
|
7
|
-
<
|
|
8
|
-
<
|
|
9
|
-
|
|
16
|
+
<PageLayout className='p-5'>
|
|
17
|
+
<WidgetHeader enabledButtons={['close']} showContent={false} />
|
|
18
|
+
|
|
19
|
+
<div className='flex h-full flex-col items-center justify-center p-10'>
|
|
20
|
+
<div className='mb-8 flex flex-col items-center gap-1 text-center'>
|
|
21
|
+
<img
|
|
22
|
+
alt={t('generic_error.image_alt')}
|
|
23
|
+
className='mb-4'
|
|
24
|
+
src={isDarkMode ? ErrorDarkSVG : ErrorLightSVG}
|
|
25
|
+
width={200}
|
|
26
|
+
height={200}
|
|
27
|
+
/>
|
|
28
|
+
|
|
29
|
+
<h2
|
|
30
|
+
className={clsx('text-xl font-bold', {
|
|
31
|
+
'text-white': isDarkMode,
|
|
32
|
+
'text-gray-800': !isDarkMode
|
|
33
|
+
})}>
|
|
34
|
+
{t('generic_error.title')}
|
|
35
|
+
</h2>
|
|
36
|
+
|
|
37
|
+
<p
|
|
38
|
+
className={clsx('text-sm', {
|
|
39
|
+
'text-gray-400': isDarkMode,
|
|
40
|
+
'text-gray-500': !isDarkMode
|
|
41
|
+
})}>
|
|
42
|
+
{t('generic_error.description')}
|
|
43
|
+
</p>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div className='flex w-full flex-col gap-4'>
|
|
47
|
+
<Button
|
|
48
|
+
variant='brand'
|
|
49
|
+
className='w-full rounded-lg py-2'
|
|
50
|
+
onClick={() => window.location.reload()}
|
|
51
|
+
aria-label='Retry Button'>
|
|
52
|
+
<span className='font-light'>{t('general.buttons.try_again')}</span>
|
|
53
|
+
</Button>
|
|
54
|
+
|
|
55
|
+
<Button
|
|
56
|
+
variant='secondary'
|
|
57
|
+
className='w-full rounded-lg py-2'
|
|
58
|
+
onClick={() => TutorWidgetEvents['c3po-app-widget-hide'].dispatch()}
|
|
59
|
+
aria-label='Close Button'>
|
|
60
|
+
<span className='font-light'>{t('general.buttons.close')}</span>
|
|
61
|
+
</Button>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</PageLayout>
|
|
10
65
|
)
|
|
11
66
|
}
|
|
12
67
|
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg viewBox="0 0 12 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path
|
|
3
|
+
d="M11.863 6.04013C11.7692 6.16499 11.6441 6.22743 11.519 6.22743C11.3939 6.22743 11.2689 6.16499 11.175 6.07135L6.51594 1.70123L6.51594 13.4693C6.51594 13.7503 6.26579 14 6.01563 14C5.76548 14 5.51533 13.7503 5.51533 13.4693L5.51533 1.70123L0.82495 6.07135C0.637335 6.25864 0.324643 6.25864 0.137028 6.04013C-0.0505867 5.82163 -0.0505867 5.50948 0.168298 5.32219L5.67167 0.140468C5.85929 -0.0468227 6.14071 -0.0468227 6.32833 0.140468L11.8317 5.32219C12.0506 5.50948 12.0506 5.82163 11.863 6.04013Z"
|
|
4
|
+
fill="currentColor" />
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path
|
|
3
|
+
d="M7.42886 11.1663C7.19494 11.1663 7.01301 11.3747 7.01301 11.583V11.9997C7.01301 12.4684 6.62314 12.833 6.1813 12.833H2.02275C1.55492 12.833 1.19104 12.4684 1.19104 11.9997V5.33301C1.19104 4.8903 1.55492 4.49967 2.02275 4.49967H4.93373C5.14166 4.49967 5.34959 4.31738 5.34959 4.08301C5.34959 3.87467 5.14166 3.66634 4.93373 3.66634H1.99676C1.08708 3.66634 0.333344 4.42155 0.333344 5.33301L0.359334 11.9997C0.359334 12.9372 1.08708 13.6663 2.02275 13.6663H6.1813C7.09098 13.6663 7.84471 12.9372 7.84471 11.9997V11.583C7.84471 11.3747 7.63679 11.1663 7.42886 11.1663ZM13.4068 2.59863L11.4055 0.593424C11.2495 0.437174 11.0416 0.333008 10.8077 0.333008H7.84471C6.90904 0.333008 6.1813 1.08822 6.1813 1.99967V8.66634C6.1813 9.60384 6.90904 10.333 7.84471 10.333H12.0033C12.9129 10.333 13.6667 9.60384 13.6667 8.66634V3.19759C13.6667 2.96322 13.5627 2.75488 13.4068 2.59863ZM11.1715 1.53092L12.4711 2.83301H11.1715V1.53092ZM12.835 8.66634C12.835 9.13509 12.4451 9.49967 12.0033 9.49967H7.84471C7.37688 9.49967 7.01301 9.13509 7.01301 8.66634V1.99967C7.01301 1.55697 7.37688 1.16634 7.84471 1.16634H10.3398V2.83301C10.3398 3.30176 10.7037 3.66634 11.1715 3.66634H12.835V8.66634Z"
|
|
4
|
+
fill="currentColor" />
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg viewBox="0 0 14 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path
|
|
3
|
+
d="M2.62501 4.28571H1.37501C0.776052 4.28571 0.333344 4.76786 0.333344 5.35714V10.9286C0.333344 11.5446 0.776052 12 1.37501 12H2.62501C3.19793 12 3.66668 11.5446 3.66668 10.9286V5.35714C3.66668 4.76786 3.19793 4.28571 2.62501 4.28571ZM2.83334 10.9286C2.83334 11.0625 2.72918 11.1429 2.62501 11.1429H1.37501C1.2448 11.1429 1.16668 11.0625 1.16668 10.9286V5.35714C1.16668 5.25 1.2448 5.14286 1.37501 5.14286H2.62501C2.72918 5.14286 2.83334 5.25 2.83334 5.35714V10.9286ZM13.6667 5.08929C13.6667 4.17857 12.9375 3.42857 12.0521 3.42857H9.39584C9.6823 2.70536 9.86459 2.00893 9.86459 1.63393C9.86459 0.830357 9.26563 0 8.25001 0C7.1823 0 6.94793 0.803571 6.71355 1.47321C6.01043 3.75 4.50001 4.09821 4.50001 4.71429C4.50001 4.98214 4.6823 5.14286 4.91668 5.14286C5.02084 5.14286 5.12501 5.11607 5.20313 5.03571C6.5573 3.61607 6.94793 3.53571 7.52084 1.74107C7.75522 0.991071 7.83334 0.857143 8.25001 0.857143C8.79688 0.857143 9.03126 1.3125 9.03126 1.63393C9.03126 1.90179 8.79688 2.8125 8.35418 3.66964C8.30209 3.72321 8.30209 3.80357 8.30209 3.85714C8.30209 4.125 8.51043 4.28571 8.71876 4.28571H12.0521C12.4688 4.28571 12.8333 4.66071 12.8333 5.08929C12.8333 5.49107 12.4948 5.83929 12.1042 5.86607C11.8958 5.89286 11.7136 6.08036 11.7136 6.29464C11.7136 6.61607 12.0261 6.64286 12.0261 7.125C12.0261 7.5 11.7656 7.82143 11.4011 7.90179C11.2448 7.92857 11.0625 8.0625 11.0625 8.30357C11.0625 8.54464 11.2448 8.59821 11.2448 8.94643C11.2448 9.77679 10.4115 9.53571 10.4115 10.0982C10.4115 10.2054 10.4636 10.2321 10.4636 10.3661C10.4636 10.7946 10.099 11.1429 9.6823 11.1429H8.22397C6.08855 11.1429 5.41147 9.42857 4.91668 9.42857C4.6823 9.42857 4.50001 9.64286 4.50001 9.85714C4.47397 10.3125 6.16668 12 8.22397 12H9.6823C10.5677 12 11.2969 11.2768 11.2969 10.3661C11.7656 10.0714 12.0781 9.53571 12.0781 8.94643C12.0781 8.8125 12.0521 8.67857 12.0261 8.57143C12.5208 8.27679 12.8594 7.74107 12.8594 7.125C12.8594 6.9375 12.8333 6.72321 12.7552 6.5625C13.3021 6.29464 13.6667 5.73214 13.6667 5.08929Z"
|
|
4
|
+
fill="currentColor" />
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
2
|
+
import dayjs from 'dayjs'
|
|
3
|
+
import type { CSSProperties } from 'react'
|
|
4
|
+
import { useTranslation } from 'react-i18next'
|
|
5
|
+
|
|
6
|
+
import { Button, Icon } from '@/src/lib/components'
|
|
7
|
+
import type { ParsedMessage } from '../../types'
|
|
8
|
+
|
|
9
|
+
export type MessageActionsProps = {
|
|
10
|
+
message: ParsedMessage
|
|
11
|
+
className?: string
|
|
12
|
+
showActions?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function MessageActions({ message, className, showActions = false }: MessageActionsProps) {
|
|
16
|
+
const { t } = useTranslation()
|
|
17
|
+
const copyToClipboard = () => {
|
|
18
|
+
if (!message.text) return
|
|
19
|
+
|
|
20
|
+
navigator.clipboard.writeText(message.text).catch((err) => {
|
|
21
|
+
console.error('Failed to copy text: ', err)
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className={className}>
|
|
27
|
+
<span className='text-xs tracking-wide'>
|
|
28
|
+
{dayjs(message.timestamp).format('DD/MM [•] LT')}
|
|
29
|
+
</span>
|
|
30
|
+
<div
|
|
31
|
+
style={{ '--custom-btn-padding': '0.5rem' } as CSSProperties}
|
|
32
|
+
className={clsx('flex flex-nowrap gap-2 text-neutral-600', {
|
|
33
|
+
hidden: !showActions
|
|
34
|
+
})}>
|
|
35
|
+
<Button aria-label={t('general.buttons.like')}>
|
|
36
|
+
<Icon name='like' className='h-3 w-3.5' />
|
|
37
|
+
</Button>
|
|
38
|
+
<Button className='rotate-180 scale-x-[-1]' aria-label={t('general.buttons.dislike')}>
|
|
39
|
+
<Icon name='like' className='h-3 w-3.5' />
|
|
40
|
+
</Button>
|
|
41
|
+
<Button onClick={copyToClipboard} aria-label={t('general.buttons.copy')}>
|
|
42
|
+
<Icon name='copy' className='h-3 w-3.5' />
|
|
43
|
+
</Button>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default MessageActions
|
|
@@ -3,6 +3,7 @@ import type { Components } from 'react-markdown'
|
|
|
3
3
|
|
|
4
4
|
import { MarkdownRenderer } from '@/src/lib/components'
|
|
5
5
|
import type { ParsedMessage } from '../../types'
|
|
6
|
+
import { MessageActions } from '../message-actions'
|
|
6
7
|
import { MessageImg } from '../message-img'
|
|
7
8
|
|
|
8
9
|
const imgComponent: Components['img'] = ({ src }) => {
|
|
@@ -10,17 +11,32 @@ const imgComponent: Components['img'] = ({ src }) => {
|
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
function MessageItem({ message }: { message: ParsedMessage }) {
|
|
14
|
+
const messageFromUser = message.metadata.author === 'user'
|
|
15
|
+
const messageFromAi = message.metadata.author === 'ai'
|
|
16
|
+
|
|
13
17
|
return (
|
|
14
18
|
<div
|
|
15
|
-
data-test='messages-item'
|
|
16
19
|
className={clsx(
|
|
17
|
-
'max-w-[min(
|
|
20
|
+
'flex max-w-[min(90%,52rem)] flex-col items-end gap-2 text-sm/normal text-neutral-900',
|
|
18
21
|
{
|
|
19
|
-
'self-end
|
|
20
|
-
'border border-neutral-300 bg-neutral-100': message.metadata.author === 'ai'
|
|
22
|
+
'self-end': messageFromUser
|
|
21
23
|
}
|
|
22
24
|
)}>
|
|
23
|
-
<
|
|
25
|
+
<div
|
|
26
|
+
data-test='messages-item'
|
|
27
|
+
className={clsx('w-full overflow-x-hidden rounded-lg px-3', {
|
|
28
|
+
'bg-neutral-200': messageFromUser,
|
|
29
|
+
'border border-neutral-300 bg-neutral-100': messageFromAi
|
|
30
|
+
})}>
|
|
31
|
+
<MarkdownRenderer content={message?.text ?? message?.name} imgComponent={imgComponent} />
|
|
32
|
+
</div>
|
|
33
|
+
<MessageActions
|
|
34
|
+
className={clsx('flex items-center justify-between gap-2', {
|
|
35
|
+
'w-full': messageFromAi
|
|
36
|
+
})}
|
|
37
|
+
message={message}
|
|
38
|
+
showActions={messageFromAi}
|
|
39
|
+
/>
|
|
24
40
|
</div>
|
|
25
41
|
)
|
|
26
42
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type MouseEventHandler, type ReactNode, useEffect } from 'react'
|
|
2
|
+
import { useTranslation } from 'react-i18next'
|
|
3
|
+
|
|
4
|
+
import { Button } from '@/src/lib/components'
|
|
5
|
+
import { devMode } from '@/src/lib/utils'
|
|
2
6
|
|
|
3
7
|
export type MessageItemErrorProps = {
|
|
4
8
|
message?: ReactNode
|
|
@@ -6,18 +10,21 @@ export type MessageItemErrorProps = {
|
|
|
6
10
|
show?: boolean
|
|
7
11
|
}
|
|
8
12
|
|
|
9
|
-
// TODO: [PLACEHOLDER] Refactor using the PD choice for Error Handling
|
|
10
13
|
function MessageItemError({ message, retry, show = true }: MessageItemErrorProps) {
|
|
14
|
+
const { t } = useTranslation()
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (show && devMode) console.log(message)
|
|
18
|
+
}, [message, show])
|
|
19
|
+
|
|
11
20
|
if (!show) return null
|
|
12
21
|
|
|
13
22
|
return (
|
|
14
|
-
<div className='rounded bg-
|
|
15
|
-
{
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Retry
|
|
20
|
-
</button>
|
|
23
|
+
<div className='mt-2 flex justify-start gap-2 rounded border border-danger-500 bg-neutral-200 p-4 text-neutral-800'>
|
|
24
|
+
<span>{t('chat_page.error.loading.content')}</span>
|
|
25
|
+
<Button variant='tertiary' className='px-4 text-sm/relaxed text-danger-400' onClick={retry}>
|
|
26
|
+
{t('chat_page.error.loading.action')}
|
|
27
|
+
</Button>
|
|
21
28
|
</div>
|
|
22
29
|
)
|
|
23
30
|
}
|
|
@@ -4,10 +4,7 @@ import { AIAvatarIcon } from '@/src/modules/widget'
|
|
|
4
4
|
|
|
5
5
|
const MessageSkeleton = forwardRef<HTMLDivElement>((_, ref) => {
|
|
6
6
|
return (
|
|
7
|
-
<div
|
|
8
|
-
ref={ref}
|
|
9
|
-
className='flex max-w-[86%] flex-col items-start gap-2'
|
|
10
|
-
aria-label='Loading Component'>
|
|
7
|
+
<div ref={ref} className='flex flex-col items-start gap-2' aria-label='Loading Component'>
|
|
11
8
|
<AIAvatarIcon className='rounded-lg bg-ai-chat-response' />
|
|
12
9
|
<div className='flex w-full flex-col items-start gap-2'>
|
|
13
10
|
<div className='h-3 w-full animate-pulse rounded-full bg-neutral-200 transition-colors delay-0' />
|