app-tutor-ai-consumer 1.21.0 → 1.21.2
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 +4 -0
- package/config/rspack/utils/envs.js +2 -1
- package/environments/.env.development +2 -0
- package/environments/.env.production +2 -0
- package/environments/.env.staging +3 -0
- package/environments/.env.test +4 -1
- package/package.json +2 -1
- package/src/config/datahub/schemas/tutor/constants.ts +1 -1
- package/src/lib/components/button/button.tsx +26 -79
- package/src/lib/components/horizontal-draggable-scroll/horizontal-draggable-scroll.tsx +62 -0
- package/src/lib/components/horizontal-draggable-scroll/index.ts +2 -0
- package/src/lib/components/icons/clone.svg +5 -0
- package/src/lib/components/icons/double-check.svg +5 -0
- package/src/lib/components/icons/icon-names.d.ts +7 -3
- package/src/lib/components/icons/info.svg +3 -3
- package/src/lib/components/icons/paste.svg +5 -0
- package/src/lib/components/index.ts +1 -0
- package/src/modules/messages/components/chat-input/chat-input.tsx +1 -1
- package/src/modules/messages/components/message-actions/message-actions.tsx +37 -9
- package/src/modules/messages/components/message-item/message-item.tsx +7 -3
- package/src/modules/messages/components/message-item-error/message-item-error.tsx +1 -1
- package/src/modules/messages/components/message-skeleton/message-skeleton.tsx +8 -5
- package/src/modules/messages/components/message-skeleton/styles.module.css +19 -0
- package/src/modules/messages/components/messages-container/messages-container.tsx +90 -65
- package/src/modules/messages/hooks/use-scroller/use-scroller.tsx +1 -1
- package/src/modules/widget/components/ai-avatar/ai-avatar.tsx +2 -2
- package/src/modules/widget/components/avatar-animation/avatar-animation.tsx +13 -0
- package/src/modules/widget/components/avatar-animation/index.ts +2 -0
- package/src/modules/widget/components/chat-page/chat-page.tsx +8 -26
- package/src/modules/widget/components/header/header.tsx +49 -27
- package/src/modules/widget/components/header/styles.module.css +11 -0
- package/src/modules/widget/components/index.ts +1 -0
- package/src/modules/widget/components/information-page/information-page.tsx +11 -17
- package/src/modules/widget/components/loading-page/loading-page.tsx +1 -1
- package/src/modules/widget/components/starter-page/starter-page.tsx +5 -9
- package/src/modules/widget/store/widget-tabs.atom.ts +1 -1
- package/tailwind.config.js +6 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
## [1.21.2](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.21.1...v1.21.2) (2025-08-01)
|
|
2
|
+
|
|
3
|
+
## [1.21.1](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.21.0...v1.21.1) (2025-07-31)
|
|
4
|
+
|
|
1
5
|
# [1.21.0](https://github.com/Hotmart-Org/app-tutor-ai-consumer/compare/v1.20.0...v1.21.0) (2025-07-29)
|
|
2
6
|
|
|
3
7
|
### Features
|
package/environments/.env.test
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "app-tutor-ai-consumer",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.2",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "rspack serve --env=development --config config/rspack/rspack.config.js",
|
|
@@ -106,6 +106,7 @@
|
|
|
106
106
|
"@hot-observability-js/react": "~1.1.0",
|
|
107
107
|
"@hotmart/event-agent-js": "~1.1.2",
|
|
108
108
|
"@hotmart/sparkie": "~5.1.0",
|
|
109
|
+
"@lottiefiles/dotlottie-react": "~0.14.4",
|
|
109
110
|
"@optimizely/react-sdk": "~3.2.4",
|
|
110
111
|
"@tanstack/query-sync-storage-persister": "~5.80.7",
|
|
111
112
|
"@tanstack/react-query": "~5.80.6",
|
|
@@ -5,6 +5,26 @@ import { Spinner } from '../spinner'
|
|
|
5
5
|
|
|
6
6
|
import styles from './styles.module.css'
|
|
7
7
|
|
|
8
|
+
function ButtonContent({
|
|
9
|
+
children,
|
|
10
|
+
loading
|
|
11
|
+
}: PropsWithChildren<{
|
|
12
|
+
loading?: boolean
|
|
13
|
+
}>) {
|
|
14
|
+
if (loading)
|
|
15
|
+
return (
|
|
16
|
+
<div className='col-start-1 row-start-1'>
|
|
17
|
+
<Spinner className='col-start-1 row-start-1 mx-auto my-auto h-[1em] w-[1em] text-current' />
|
|
18
|
+
</div>
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<span data-label='text-content' className='col-start-1 row-start-1 flex flex-nowrap gap-2'>
|
|
23
|
+
{children}
|
|
24
|
+
</span>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
8
28
|
export type ButtonProps = PropsWithChildren<
|
|
9
29
|
ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
10
30
|
variant?: 'brand' | 'secondary' | 'primary' | 'tertiary' | 'gradient-outline'
|
|
@@ -22,7 +42,7 @@ function Button({
|
|
|
22
42
|
...props
|
|
23
43
|
}: ButtonProps) {
|
|
24
44
|
const defaultClasses =
|
|
25
|
-
'rounded
|
|
45
|
+
'rounded outline-none focus-visible:ring-2 focus-visible:ring-blue-500 text-base font-medium'
|
|
26
46
|
const defaultBorder = 'border border-transparent'
|
|
27
47
|
const defaultPadding = 'px-4 py-2'
|
|
28
48
|
const disabledClasses = 'cursor-not-allowed'
|
|
@@ -77,20 +97,7 @@ function Button({
|
|
|
77
97
|
{...props}
|
|
78
98
|
disabled={props.disabled || loading}
|
|
79
99
|
aria-busy={loading}>
|
|
80
|
-
<
|
|
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
|
-
/>
|
|
100
|
+
<ButtonContent loading={loading}>{children}</ButtonContent>
|
|
94
101
|
</button>
|
|
95
102
|
)
|
|
96
103
|
case 'secondary':
|
|
@@ -107,20 +114,7 @@ function Button({
|
|
|
107
114
|
{...props}
|
|
108
115
|
disabled={props.disabled || loading}
|
|
109
116
|
aria-busy={loading}>
|
|
110
|
-
<
|
|
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
|
-
/>
|
|
117
|
+
<ButtonContent loading={loading}>{children}</ButtonContent>
|
|
124
118
|
</button>
|
|
125
119
|
)
|
|
126
120
|
case 'tertiary':
|
|
@@ -134,28 +128,7 @@ function Button({
|
|
|
134
128
|
{...props}
|
|
135
129
|
disabled={props.disabled || loading}
|
|
136
130
|
aria-busy={loading}>
|
|
137
|
-
<
|
|
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
|
-
)}
|
|
131
|
+
<ButtonContent loading={loading}>{children}</ButtonContent>
|
|
159
132
|
</button>
|
|
160
133
|
)
|
|
161
134
|
case 'brand':
|
|
@@ -172,20 +145,7 @@ function Button({
|
|
|
172
145
|
{...props}
|
|
173
146
|
disabled={props.disabled || loading}
|
|
174
147
|
aria-busy={loading}>
|
|
175
|
-
<
|
|
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
|
-
/>
|
|
148
|
+
<ButtonContent loading={loading}>{children}</ButtonContent>
|
|
189
149
|
</button>
|
|
190
150
|
)
|
|
191
151
|
default:
|
|
@@ -205,20 +165,7 @@ function Button({
|
|
|
205
165
|
{...props}
|
|
206
166
|
disabled={props.disabled || loading}
|
|
207
167
|
aria-busy={loading}>
|
|
208
|
-
<
|
|
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
|
-
/>
|
|
168
|
+
<ButtonContent loading={loading}>{children}</ButtonContent>
|
|
222
169
|
</button>
|
|
223
170
|
)
|
|
224
171
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useRef, useState } from 'react'
|
|
2
|
+
import clsx from 'clsx'
|
|
3
|
+
import type { MouseEventHandler, PropsWithChildren, WheelEvent } from 'react'
|
|
4
|
+
|
|
5
|
+
export type HorizontalDraggableScrollProps = PropsWithChildren<{ className?: string }>
|
|
6
|
+
|
|
7
|
+
function HorizontalDraggableScroll({ children, className }: HorizontalDraggableScrollProps) {
|
|
8
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
9
|
+
const [isDragging, setIsDragging] = useState(false)
|
|
10
|
+
const [startX, setStartX] = useState(0)
|
|
11
|
+
const [scrollLeft, setScrollLeft] = useState(0)
|
|
12
|
+
|
|
13
|
+
const handleMouseDown: MouseEventHandler<HTMLDivElement> = (e) => {
|
|
14
|
+
const container = containerRef.current
|
|
15
|
+
|
|
16
|
+
if (!container) return
|
|
17
|
+
|
|
18
|
+
setIsDragging(true)
|
|
19
|
+
setStartX(e.clientX)
|
|
20
|
+
setScrollLeft(container.scrollLeft)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
|
|
24
|
+
const container = containerRef.current
|
|
25
|
+
|
|
26
|
+
if (!container || !isDragging) return
|
|
27
|
+
|
|
28
|
+
e.preventDefault()
|
|
29
|
+
|
|
30
|
+
const walk = e.clientX - startX
|
|
31
|
+
|
|
32
|
+
container.scrollLeft = scrollLeft - walk
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const handleMouseUp = () => {
|
|
36
|
+
setIsDragging(false)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const handleWheel = (e: WheelEvent<HTMLDivElement>) => {
|
|
40
|
+
e.currentTarget.scrollLeft += e.deltaY
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div
|
|
45
|
+
ref={containerRef}
|
|
46
|
+
onMouseDown={handleMouseDown}
|
|
47
|
+
onMouseMove={handleMouseMove}
|
|
48
|
+
onMouseUp={handleMouseUp}
|
|
49
|
+
onMouseLeave={handleMouseUp}
|
|
50
|
+
onWheel={handleWheel}
|
|
51
|
+
className={clsx(
|
|
52
|
+
{
|
|
53
|
+
'cursor-grabbing select-none': isDragging
|
|
54
|
+
},
|
|
55
|
+
className
|
|
56
|
+
)}>
|
|
57
|
+
{children}
|
|
58
|
+
</div>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export default HorizontalDraggableScroll
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg"
|
|
2
|
+
viewBox="0 0 512 512">
|
|
3
|
+
<path fill="currentColor"
|
|
4
|
+
d="M64 480H288c17.7 0 32-14.3 32-32V384h32v64c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V224c0-35.3 28.7-64 64-64h64v32H64c-17.7 0-32 14.3-32 32V448c0 17.7 14.3 32 32 32zM224 320H448c17.7 0 32-14.3 32-32V64c0-17.7-14.3-32-32-32H224c-17.7 0-32 14.3-32 32V288c0 17.7 14.3 32 32 32zm-64-32V64c0-35.3 28.7-64 64-64H448c35.3 0 64 28.7 64 64V288c0 35.3-28.7 64-64 64H224c-35.3 0-64-28.7-64-64z"></path>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg aria-hidden="true" focusable="false" role="img"
|
|
2
|
+
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
|
|
3
|
+
<path fill="currentColor"
|
|
4
|
+
d="M331.3 75.3c6.2-6.2 6.2-16.4 0-22.6s-16.4-6.2-22.6 0L160 201.4 91.3 132.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l80 80c6.2 6.2 16.4 6.2 22.6 0l160-160zm112 112c6.2-6.2 6.2-16.4 0-22.6s-16.4-6.2-22.6 0L160 425.4 27.3 292.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l144 144c6.2 6.2 16.4 6.2 22.6 0l272-272z"></path>
|
|
5
|
+
</svg>
|
|
@@ -5,13 +5,17 @@ export type ValidIconNames =
|
|
|
5
5
|
| 'arrow-down'
|
|
6
6
|
| 'arrow-left'
|
|
7
7
|
| 'arrow-up'
|
|
8
|
+
| 'book'
|
|
8
9
|
| 'chevron-down'
|
|
9
|
-
| '
|
|
10
|
+
| 'clone'
|
|
10
11
|
| 'close'
|
|
12
|
+
| 'copy'
|
|
13
|
+
| 'double-check'
|
|
14
|
+
| 'gallery'
|
|
11
15
|
| 'info'
|
|
16
|
+
| 'interrogation'
|
|
12
17
|
| 'like'
|
|
18
|
+
| 'paste'
|
|
13
19
|
| 'send'
|
|
14
20
|
| 'stop'
|
|
15
21
|
| 'warning'
|
|
16
|
-
| 'interrogation'
|
|
17
|
-
| 'gallery'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
<svg viewBox="0 0 16
|
|
1
|
+
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
2
|
<path fill-rule="evenodd" clip-rule="evenodd"
|
|
3
|
-
d="M0
|
|
4
|
-
fill="currentColor" />
|
|
3
|
+
d="M0 8C0 3.58172 3.58172 0 8 0C12.4183 0 16 3.58172 16 8C16 12.4183 12.4183 16 8 16C3.58172 16 0 12.4183 0 8ZM8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1ZM7 5C7 4.44772 7.44772 4 8 4C8.55229 4 9 4.44772 9 5C9 5.55228 8.55229 6 8 6C7.44772 6 7 5.55228 7 5ZM7 7H7.5C8.32843 7 9 7.67157 9 8.5V12H8V8.5C8 8.22386 7.77614 8 7.5 8H7V7Z"
|
|
4
|
+
fill="currentColor" shape-rendering="geometricPrecision" />
|
|
5
5
|
</svg>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg aria-hidden="true" focusable="false" role="img" xmlns="http://www.w3.org/2000/svg"
|
|
2
|
+
viewBox="0 0 512 512">
|
|
3
|
+
<path fill="currentColor"
|
|
4
|
+
d="M160 32c11.6 0 21.3 8.2 23.5 19.2C185 58.6 191.6 64 199.2 64H208c8.8 0 16 7.2 16 16V96H96V80c0-8.8 7.2-16 16-16h8.8c7.6 0 14.2-5.4 15.7-12.8C138.7 40.2 148.4 32 160 32zM64 64h2.7C65 69 64 74.4 64 80V96c0 17.7 14.3 32 32 32H224c17.7 0 32-14.3 32-32V80c0-5.6-1-11-2.7-16H256c17.7 0 32 14.3 32 32h32c0-35.3-28.7-64-64-64H210.6c-9-18.9-28.3-32-50.6-32s-41.6 13.1-50.6 32H64C28.7 32 0 60.7 0 96V384c0 35.3 28.7 64 64 64H192V416H64c-17.7 0-32-14.3-32-32V96c0-17.7 14.3-32 32-32zM288 480c-17.7 0-32-14.3-32-32V192c0-17.7 14.3-32 32-32h96v56c0 22.1 17.9 40 40 40h56V448c0 17.7-14.3 32-32 32H288zM416 165.3L474.7 224H424c-4.4 0-8-3.6-8-8V165.3zM448 512c35.3 0 64-28.7 64-64V235.9c0-12.7-5.1-24.9-14.1-33.9l-59.9-59.9c-9-9-21.2-14.1-33.9-14.1H288c-35.3 0-64 28.7-64 64V448c0 35.3 28.7 64 64 64H448z"></path>
|
|
5
|
+
</svg>
|
|
@@ -71,7 +71,7 @@ const ChatInput = forwardRef<HTMLTextAreaElement, ChatInputProps>(
|
|
|
71
71
|
ref={ref}
|
|
72
72
|
className={clsx(
|
|
73
73
|
clsx(
|
|
74
|
-
'max-h-12 w-full resize-none border-none bg-transparent text-neutral-900 outline-none outline-0 placeholder:text-neutral-600',
|
|
74
|
+
'max-h-12 w-full resize-none border-none bg-transparent text-sm/normal text-neutral-900 outline-none outline-0 placeholder:text-neutral-600',
|
|
75
75
|
styles.textArea
|
|
76
76
|
),
|
|
77
77
|
{ 'cursor-not-allowed opacity-40': inputDisabled }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { type CSSProperties, useState } from 'react'
|
|
1
2
|
import clsx from 'clsx'
|
|
2
3
|
import dayjs from 'dayjs'
|
|
3
|
-
import type { CSSProperties } from 'react'
|
|
4
4
|
import { useTranslation } from 'react-i18next'
|
|
5
5
|
|
|
6
6
|
import { DataHubService } from '@/src/config/datahub'
|
|
@@ -17,18 +17,31 @@ export type MessageActionsProps = {
|
|
|
17
17
|
|
|
18
18
|
function MessageActions({ message, className, showActions = false }: MessageActionsProps) {
|
|
19
19
|
const { t } = useTranslation()
|
|
20
|
+
const [copying, setCopying] = useState(false)
|
|
21
|
+
const [copied, setCopied] = useState(false)
|
|
22
|
+
const [reaction, setReaction] = useState<ButtonReactionsType | null>(null)
|
|
20
23
|
|
|
21
24
|
const copyToClipboard = (): void => {
|
|
22
25
|
if (!message.text) return
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
setCopying(true)
|
|
28
|
+
|
|
29
|
+
navigator.clipboard
|
|
30
|
+
.writeText(message.text)
|
|
31
|
+
.then(() => setCopied(true))
|
|
32
|
+
.catch((err) => {
|
|
33
|
+
console.error('Failed to copy text: ', err)
|
|
34
|
+
})
|
|
35
|
+
.finally(() => {
|
|
36
|
+
setTimeout(() => setCopying(false), 1000)
|
|
37
|
+
setTimeout(() => setCopied(false), 3000)
|
|
38
|
+
})
|
|
27
39
|
}
|
|
28
40
|
|
|
29
41
|
const handleReaction = (reactionType: ButtonReactionsType = ButtonReactions.LIKE): void => {
|
|
30
42
|
const schema = new ClickHotmartTutor({ messageId: message.id, reactionType })
|
|
31
|
-
|
|
43
|
+
DataHubService.sendEvent({ schema })
|
|
44
|
+
setReaction(reactionType)
|
|
32
45
|
}
|
|
33
46
|
|
|
34
47
|
return (
|
|
@@ -42,16 +55,31 @@ function MessageActions({ message, className, showActions = false }: MessageActi
|
|
|
42
55
|
hidden: !showActions
|
|
43
56
|
})}>
|
|
44
57
|
<Button onClick={() => handleReaction()} aria-label={t('general.buttons.like')}>
|
|
45
|
-
<Icon
|
|
58
|
+
<Icon
|
|
59
|
+
name='like'
|
|
60
|
+
className={clsx('h-3 w-3.5', {
|
|
61
|
+
'text-info-500': reaction === ButtonReactions.LIKE
|
|
62
|
+
})}
|
|
63
|
+
/>
|
|
46
64
|
</Button>
|
|
47
65
|
<Button
|
|
48
66
|
className='rotate-180 scale-x-[-1]'
|
|
49
67
|
onClick={() => handleReaction(ButtonReactions.DISLIKE)}
|
|
50
68
|
aria-label={t('general.buttons.dislike')}>
|
|
51
|
-
<Icon
|
|
69
|
+
<Icon
|
|
70
|
+
name='like'
|
|
71
|
+
className={clsx('h-3 w-3.5', {
|
|
72
|
+
'text-danger-500': reaction === ButtonReactions.DISLIKE
|
|
73
|
+
})}
|
|
74
|
+
/>
|
|
52
75
|
</Button>
|
|
53
|
-
<Button onClick={copyToClipboard} aria-label={t('general.buttons.copy')}>
|
|
54
|
-
<Icon
|
|
76
|
+
<Button onClick={copyToClipboard} aria-label={t('general.buttons.copy')} disabled={copying}>
|
|
77
|
+
<Icon
|
|
78
|
+
name={copied ? 'paste' : 'copy'}
|
|
79
|
+
className={clsx('h-3 w-3.5', {
|
|
80
|
+
'text-info-500': copied
|
|
81
|
+
})}
|
|
82
|
+
/>
|
|
55
83
|
</Button>
|
|
56
84
|
</div>
|
|
57
85
|
</div>
|
|
@@ -25,10 +25,14 @@ function MessageItem({ message }: { message: ParsedMessage }) {
|
|
|
25
25
|
<div
|
|
26
26
|
data-test='messages-item'
|
|
27
27
|
className={clsx('w-full overflow-x-hidden rounded-lg px-3', {
|
|
28
|
-
'bg-neutral-
|
|
29
|
-
'
|
|
28
|
+
'max-w-max bg-[rgb(from_var(--hc-color-neutral-300)_r_g_b_/_0.8)]': messageFromUser,
|
|
29
|
+
'bg-neutral-200': messageFromAi
|
|
30
30
|
})}>
|
|
31
|
-
<MarkdownRenderer
|
|
31
|
+
<MarkdownRenderer
|
|
32
|
+
content={message?.text ?? message?.name}
|
|
33
|
+
imgComponent={imgComponent}
|
|
34
|
+
className='w-full'
|
|
35
|
+
/>
|
|
32
36
|
</div>
|
|
33
37
|
<MessageActions
|
|
34
38
|
className={clsx('flex items-center justify-between gap-2', {
|
|
@@ -20,7 +20,7 @@ function MessageItemError({ message, retry, show = true }: MessageItemErrorProps
|
|
|
20
20
|
if (!show) return null
|
|
21
21
|
|
|
22
22
|
return (
|
|
23
|
-
<div className='
|
|
23
|
+
<div className='absolute bottom-4 flex justify-start gap-2 rounded border border-danger-500 bg-neutral-200 p-4 text-neutral-800 max-md:inset-x-[1.125rem] md:inset-x-5'>
|
|
24
24
|
<span>{t('chat_page.error.loading.content')}</span>
|
|
25
25
|
<Button variant='tertiary' className='px-4 text-sm/relaxed text-danger-400' onClick={retry}>
|
|
26
26
|
{t('chat_page.error.loading.action')}
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { forwardRef } from 'react'
|
|
2
|
+
import clsx from 'clsx'
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
+
import { AvatarAnimation } from '@/src/modules/widget'
|
|
5
|
+
|
|
6
|
+
import styles from './styles.module.css'
|
|
4
7
|
|
|
5
8
|
const MessageSkeleton = forwardRef<HTMLDivElement>((_, ref) => {
|
|
6
9
|
return (
|
|
7
10
|
<div ref={ref} className='flex flex-col items-start gap-2' aria-label='Loading Component'>
|
|
8
|
-
<
|
|
11
|
+
<AvatarAnimation />
|
|
9
12
|
<div className='flex w-full flex-col items-start gap-2'>
|
|
10
|
-
<div className='h-3 w-full
|
|
11
|
-
<div className='h-3 w-[83%]
|
|
12
|
-
<div className='h-3 w-[56%]
|
|
13
|
+
<div className={clsx('h-3 w-full rounded-full', styles.shine)} />
|
|
14
|
+
<div className={clsx('h-3 w-[83%] rounded-full', styles.shine)} />
|
|
15
|
+
<div className={clsx('h-3 w-[56%] rounded-full', styles.shine)} />
|
|
13
16
|
</div>
|
|
14
17
|
</div>
|
|
15
18
|
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
.shine {
|
|
2
|
+
background: linear-gradient(
|
|
3
|
+
90deg,
|
|
4
|
+
var(--hc-color-neutral-200) 33%,
|
|
5
|
+
rgb(from var(--hc-color-neutral-900) r g b / 0.3) 53%,
|
|
6
|
+
var(--hc-color-neutral-200) 76%
|
|
7
|
+
);
|
|
8
|
+
background-size: 295% 100%;
|
|
9
|
+
animation: shine 1500ms infinite alternate;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@keyframes shine {
|
|
13
|
+
0% {
|
|
14
|
+
background-position: right;
|
|
15
|
+
}
|
|
16
|
+
100% {
|
|
17
|
+
background-position: left;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { forwardRef, useEffect } from 'react'
|
|
1
|
+
import { forwardRef, lazy, Suspense, useEffect } from 'react'
|
|
2
2
|
import clsx from 'clsx'
|
|
3
3
|
import type { MouseEventHandler, PropsWithChildren } from 'react'
|
|
4
4
|
import { createPortal } from 'react-dom'
|
|
@@ -14,77 +14,102 @@ import {
|
|
|
14
14
|
} from '@/src/modules/widget'
|
|
15
15
|
import { useScroller } from '../../hooks'
|
|
16
16
|
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
showButton?: boolean
|
|
21
|
-
loading?: boolean
|
|
22
|
-
handleShowMore?: () => Promise<void>
|
|
23
|
-
}>
|
|
24
|
-
>(({ children, handleShowMore, showButton = false, loading = false }, forwardedRef) => {
|
|
25
|
-
const { t } = useTranslation()
|
|
26
|
-
const skeletonRef = useSkeletonRef()
|
|
27
|
-
const [isLoadingNewMsg] = useWidgetLoadingAtom()
|
|
28
|
-
const mainLayoutRef = usePageLayoutMainRefContext()
|
|
29
|
-
const { scrollerRef, scrollToButtonRef, scrollToBottom, showScrollButton } =
|
|
30
|
-
useScroller(forwardedRef)
|
|
17
|
+
const MessageItemError = lazy(
|
|
18
|
+
() => import('@/src/modules/messages/components/message-item-error/message-item-error')
|
|
19
|
+
)
|
|
31
20
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
21
|
+
export type MessagesContainerProps = PropsWithChildren<{
|
|
22
|
+
showButton?: boolean
|
|
23
|
+
loading?: boolean
|
|
24
|
+
handleShowMore?: () => Promise<void>
|
|
25
|
+
error?: {
|
|
26
|
+
show: boolean
|
|
27
|
+
message: string
|
|
28
|
+
retry: () => void
|
|
29
|
+
}
|
|
30
|
+
}>
|
|
35
31
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
32
|
+
const MessagesContainer = forwardRef<HTMLDivElement, MessagesContainerProps>(
|
|
33
|
+
({ children, handleShowMore, error, showButton = false, loading = false }, forwardedRef) => {
|
|
34
|
+
const { t } = useTranslation()
|
|
35
|
+
const skeletonRef = useSkeletonRef()
|
|
36
|
+
const [isLoadingNewMsg] = useWidgetLoadingAtom()
|
|
37
|
+
const mainLayoutRef = usePageLayoutMainRefContext()
|
|
38
|
+
const { scrollerRef, scrollToButtonRef, scrollToBottom, showScrollButton } =
|
|
39
|
+
useScroller(forwardedRef)
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
() =>
|
|
44
|
-
scroller.scrollTo({
|
|
45
|
-
top: heightBeforeRender + 10,
|
|
46
|
-
behavior: 'smooth'
|
|
47
|
-
}),
|
|
48
|
-
180
|
|
49
|
-
)
|
|
50
|
-
}
|
|
51
|
-
})
|
|
52
|
-
}
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
scrollToBottom()
|
|
43
|
+
}, [scrollToBottom])
|
|
53
44
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
45
|
+
const handleClickShowMore: MouseEventHandler<HTMLButtonElement> = (e) => {
|
|
46
|
+
const scroller = scrollerRef?.current
|
|
47
|
+
const heightBeforeRender = Number(e?.currentTarget?.scrollHeight)
|
|
48
|
+
|
|
49
|
+
void handleShowMore?.().then(() => {
|
|
50
|
+
if (scroller && !isNaN(heightBeforeRender)) {
|
|
51
|
+
setTimeout(
|
|
52
|
+
() =>
|
|
53
|
+
scroller.scrollTo({
|
|
54
|
+
top: heightBeforeRender + 10,
|
|
55
|
+
behavior: 'smooth'
|
|
56
|
+
}),
|
|
57
|
+
180
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
}
|
|
67
62
|
|
|
68
|
-
|
|
69
|
-
createPortal(
|
|
70
|
-
<ScrollToBottomButton
|
|
71
|
-
ref={scrollToButtonRef}
|
|
72
|
-
show={showScrollButton}
|
|
73
|
-
onClick={scrollToBottom}
|
|
74
|
-
/>,
|
|
75
|
-
mainLayoutRef.current
|
|
76
|
-
)}
|
|
63
|
+
return (
|
|
77
64
|
<div
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
65
|
+
ref={scrollerRef}
|
|
66
|
+
className='flex h-full flex-col gap-2 overflow-auto max-md:p-[1.125rem] md:p-5'>
|
|
67
|
+
<div className='mb-auto flex-1 self-center'>
|
|
68
|
+
<Button
|
|
69
|
+
className='max-w-max rounded-full border border-neutral-300 bg-neutral-200 px-2 py-1 text-xs/normal tracking-wide text-neutral-900'
|
|
70
|
+
onClick={handleClickShowMore}
|
|
71
|
+
loading={loading}
|
|
72
|
+
show={showButton}>
|
|
73
|
+
<Icon name='arrow-up' className='h-4 w-3' aria-hidden />
|
|
74
|
+
<span className='text-nowrap'>{t('general.buttons.show_more')}</span>
|
|
75
|
+
</Button>
|
|
76
|
+
</div>
|
|
77
|
+
{children}
|
|
78
|
+
|
|
79
|
+
{error?.show &&
|
|
80
|
+
mainLayoutRef.current &&
|
|
81
|
+
createPortal(
|
|
82
|
+
<Suspense fallback={<div aria-live='polite' />}>
|
|
83
|
+
<MessageItemError
|
|
84
|
+
message={`❌ Error loading messages: ${error?.message}`}
|
|
85
|
+
show={error?.show}
|
|
86
|
+
retry={error?.retry}
|
|
87
|
+
/>
|
|
88
|
+
</Suspense>,
|
|
89
|
+
mainLayoutRef.current
|
|
90
|
+
)}
|
|
91
|
+
|
|
92
|
+
{mainLayoutRef.current &&
|
|
93
|
+
createPortal(
|
|
94
|
+
<ScrollToBottomButton
|
|
95
|
+
ref={scrollToButtonRef}
|
|
96
|
+
show={showScrollButton}
|
|
97
|
+
onClick={scrollToBottom}
|
|
98
|
+
/>,
|
|
99
|
+
mainLayoutRef.current
|
|
100
|
+
)}
|
|
101
|
+
<div
|
|
102
|
+
className={clsx({
|
|
103
|
+
'pointer-events-none h-0 overflow-hidden opacity-0': !isLoadingNewMsg,
|
|
104
|
+
'mt-2 pb-4': isLoadingNewMsg
|
|
105
|
+
})}
|
|
106
|
+
ref={skeletonRef}>
|
|
107
|
+
<MessageSkeleton />
|
|
108
|
+
</div>
|
|
84
109
|
</div>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
)
|
|
88
113
|
|
|
89
114
|
MessagesContainer.displayName = 'MessagesContainer'
|
|
90
115
|
|
|
@@ -11,8 +11,8 @@ function AIAvatar({
|
|
|
11
11
|
}: AIAvatarProps) {
|
|
12
12
|
return (
|
|
13
13
|
<figure
|
|
14
|
-
className={clsx('flex h-
|
|
15
|
-
<div className={clsx('flex h-
|
|
14
|
+
className={clsx('flex h-9 w-9 items-center justify-center rounded-full', styles.avatar)}>
|
|
15
|
+
<div className={clsx('flex h-8 w-8 items-center justify-center', className)}>
|
|
16
16
|
<Icon name='ai-color' className='h-6 w-6' aria-label='AI avatar Icon' />
|
|
17
17
|
</div>
|
|
18
18
|
</figure>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { DotLottieReact } from '@lottiefiles/dotlottie-react'
|
|
2
|
+
|
|
3
|
+
const AVATAR_ANIMATION_URL = `${process.env.STATIC_URL}/tutor/tutor_sparkle.lottie`
|
|
4
|
+
|
|
5
|
+
const AvatarAnimation = () => {
|
|
6
|
+
return (
|
|
7
|
+
<div className='flex h-11 w-11 items-center justify-center rounded-lg bg-ai-chat-response'>
|
|
8
|
+
<DotLottieReact src={AVATAR_ANIMATION_URL} loop autoplay className='h-auto w-full' />
|
|
9
|
+
</div>
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default AvatarAnimation
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect, useMemo, useRef } from 'react'
|
|
2
2
|
import { useInfiniteQuery } from '@tanstack/react-query'
|
|
3
3
|
|
|
4
4
|
import { isTextEmpty } from '@/src/lib/utils/is-text-empty'
|
|
@@ -15,17 +15,6 @@ import {
|
|
|
15
15
|
import { WidgetHeader } from '../header'
|
|
16
16
|
import { PageLayout } from '../page-layout'
|
|
17
17
|
|
|
18
|
-
const MessageItemError = lazy(
|
|
19
|
-
() => import('@/src/modules/messages/components/message-item-error/message-item-error')
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
const MessageItemEndOfScroll = lazy(
|
|
23
|
-
() =>
|
|
24
|
-
import(
|
|
25
|
-
'@/src/modules/messages/components/message-item-end-of-scroll/message-item-end-of-scroll'
|
|
26
|
-
)
|
|
27
|
-
)
|
|
28
|
-
|
|
29
18
|
function ChatPage() {
|
|
30
19
|
const chatInputRef = useRef<HTMLTextAreaElement>(null)
|
|
31
20
|
const scrollerRef = useRef<HTMLDivElement>(null)
|
|
@@ -82,7 +71,7 @@ function ChatPage() {
|
|
|
82
71
|
buttonDisabled={messagesQuery?.isLoading || !value.trim()}
|
|
83
72
|
/>
|
|
84
73
|
}>
|
|
85
|
-
<div className='
|
|
74
|
+
<div className='max-md:px-[1.125rem] max-md:pt-[1.125rem] md:px-5 md:pt-5'>
|
|
86
75
|
<WidgetHeader enabledButtons={['info', 'close']} tutorName={settings?.tutorName} />
|
|
87
76
|
</div>
|
|
88
77
|
<MessagesContainer
|
|
@@ -91,20 +80,13 @@ function ChatPage() {
|
|
|
91
80
|
await messagesQuery.fetchNextPage()
|
|
92
81
|
}}
|
|
93
82
|
showButton={messagesQuery.hasNextPage}
|
|
94
|
-
loading={messagesQuery.isFetchingNextPage}
|
|
95
|
-
|
|
96
|
-
show
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
/>
|
|
83
|
+
loading={messagesQuery.isFetchingNextPage}
|
|
84
|
+
error={{
|
|
85
|
+
show: messagesQuery.isError,
|
|
86
|
+
message: messagesQuery.error?.message ?? '',
|
|
87
|
+
retry: () => void messagesQuery.refetch()
|
|
88
|
+
}}>
|
|
102
89
|
{messagesQuery.data && <MessagesList messagesMap={messagesQuery.data} />}
|
|
103
|
-
<MessageItemError
|
|
104
|
-
show={messagesQuery.isError}
|
|
105
|
-
message={`❌ Error loading messages: ${messagesQuery.error?.message ?? ''}`}
|
|
106
|
-
retry={() => void messagesQuery.refetch()}
|
|
107
|
-
/>
|
|
108
90
|
</MessagesContainer>
|
|
109
91
|
</PageLayout>
|
|
110
92
|
)
|
|
@@ -1,31 +1,42 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
1
2
|
import { useTranslation } from 'react-i18next'
|
|
2
3
|
|
|
3
4
|
import { Button, Icon } from '@/src/lib/components'
|
|
4
5
|
import { TutorWidgetEvents } from '../../events'
|
|
5
|
-
import { useWidgetTabsAtom } from '../../store'
|
|
6
|
+
import { useWidgetGoBackTabAtom, useWidgetTabsAtom } from '../../store'
|
|
6
7
|
import { AIAvatar } from '../ai-avatar'
|
|
7
8
|
|
|
8
9
|
import type { WidgetHeaderContentProps, WidgetHeaderProps } from './types'
|
|
9
10
|
|
|
11
|
+
import styles from './styles.module.css'
|
|
12
|
+
|
|
10
13
|
export function WidgetHeaderContent({ tutorName }: WidgetHeaderContentProps) {
|
|
11
14
|
return (
|
|
12
15
|
<div className='flex w-full gap-2'>
|
|
13
16
|
<AIAvatar />
|
|
14
17
|
<div className='flex flex-col justify-center'>
|
|
15
|
-
{tutorName && <h4 className='text-
|
|
18
|
+
{tutorName && <h4 className='text-sm/loose font-bold'>{tutorName}</h4>}
|
|
16
19
|
</div>
|
|
17
20
|
</div>
|
|
18
21
|
)
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
export function WidgetHeaderContentWithoutMeta({ name }: { name?: string }) {
|
|
25
|
+
const [, goBack] = useWidgetGoBackTabAtom()
|
|
26
|
+
|
|
22
27
|
return (
|
|
23
|
-
<div
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
<div
|
|
29
|
+
className={clsx(
|
|
30
|
+
'grid-areas-[a_b] grid grid-cols-[auto_1fr] items-center gap-1',
|
|
31
|
+
styles.withoutMetaContainer
|
|
32
|
+
)}>
|
|
33
|
+
<Button className='grid-area-[a]' aria-label='Arrow Left Icon' onClick={goBack}>
|
|
34
|
+
<Icon name='arrow-left' className='h-3.5 w-3.5' aria-hidden />
|
|
26
35
|
</Button>
|
|
27
|
-
<div className='grid-area-[b] flex justify-center'>
|
|
28
|
-
<span>
|
|
36
|
+
<div className='grid-area-[b] flex min-h-6 justify-center text-center'>
|
|
37
|
+
<span className='absolute bottom-0 left-1/2 -translate-x-1/2 text-base font-bold'>
|
|
38
|
+
{name}
|
|
39
|
+
</span>
|
|
29
40
|
</div>
|
|
30
41
|
</div>
|
|
31
42
|
)
|
|
@@ -50,33 +61,44 @@ function WidgetHeader({
|
|
|
50
61
|
}
|
|
51
62
|
|
|
52
63
|
return (
|
|
53
|
-
<div className='
|
|
54
|
-
<div className='
|
|
55
|
-
|
|
56
|
-
{showContentWithoutMeta && !showContent && <WidgetHeaderContentWithoutMeta name={name} />}
|
|
57
|
-
</div>
|
|
58
|
-
<div className='shrink-0'>
|
|
59
|
-
<div className='grid-area-[b] ml-auto flex max-w-max gap-3 text-neutral-700'>
|
|
60
|
-
<Button
|
|
61
|
-
show={enabledButtons.includes('archive')}
|
|
62
|
-
onClick={handleClickArchive}
|
|
63
|
-
aria-label='Archive Icon'>
|
|
64
|
-
<Icon name='archive' className='h-4 w-4' aria-hidden />
|
|
65
|
-
</Button>
|
|
66
|
-
<Button
|
|
67
|
-
show={enabledButtons.includes('info')}
|
|
68
|
-
aria-label='Info Icon'
|
|
69
|
-
onClick={handleClickInfo}>
|
|
70
|
-
<Icon name='info' className='h-4 w-4' aria-hidden />
|
|
71
|
-
</Button>
|
|
64
|
+
<div className='mt-0.5 flex flex-col gap-2 text-neutral-900'>
|
|
65
|
+
<div className='flex justify-end'>
|
|
66
|
+
<div className={styles.closeContainer}>
|
|
72
67
|
<Button
|
|
68
|
+
className='text-neutral-500'
|
|
73
69
|
show={enabledButtons.includes('close')}
|
|
74
70
|
onClick={() => TutorWidgetEvents['c3po-app-widget-hide'].dispatch()}
|
|
75
71
|
aria-label='Close Icon'>
|
|
76
|
-
<Icon name='close' className='h-
|
|
72
|
+
<Icon name='close' className='h-3 w-3' aria-hidden />
|
|
77
73
|
</Button>
|
|
78
74
|
</div>
|
|
79
75
|
</div>
|
|
76
|
+
<div className='grid-areas-[a_b] grid grid-cols-[1fr_auto] items-center'>
|
|
77
|
+
<div className='grid-area-[a] relative'>
|
|
78
|
+
{showContent && !showContentWithoutMeta && <WidgetHeaderContent tutorName={name} />}
|
|
79
|
+
{showContentWithoutMeta && !showContent && <WidgetHeaderContentWithoutMeta name={name} />}
|
|
80
|
+
</div>
|
|
81
|
+
<div className='shrink-0'>
|
|
82
|
+
<div
|
|
83
|
+
className={clsx(
|
|
84
|
+
'grid-area-[b] ml-auto flex max-w-max gap-3 text-neutral-700',
|
|
85
|
+
styles.btnContainer
|
|
86
|
+
)}>
|
|
87
|
+
<Button
|
|
88
|
+
show={enabledButtons.includes('archive')}
|
|
89
|
+
onClick={handleClickArchive}
|
|
90
|
+
aria-label='Archive Icon'>
|
|
91
|
+
<Icon name='archive' className='h-4 w-4' aria-hidden />
|
|
92
|
+
</Button>
|
|
93
|
+
<Button
|
|
94
|
+
show={enabledButtons.includes('info')}
|
|
95
|
+
aria-label='Info Icon'
|
|
96
|
+
onClick={handleClickInfo}>
|
|
97
|
+
<Icon name='info' className='h-4 w-4' aria-hidden />
|
|
98
|
+
</Button>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
80
102
|
</div>
|
|
81
103
|
)
|
|
82
104
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import clsx from 'clsx'
|
|
2
2
|
import { useTranslation } from 'react-i18next'
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import { useWidgetSettingsAtom, useWidgetTabsAtom } from '../../store'
|
|
4
|
+
import { useWidgetSettingsAtom } from '../../store'
|
|
6
5
|
import { AIAvatar } from '../ai-avatar'
|
|
6
|
+
import { WidgetHeader } from '../header'
|
|
7
7
|
import { PageLayout } from '../page-layout'
|
|
8
8
|
|
|
9
9
|
import { infoItems } from './constants'
|
|
@@ -11,27 +11,21 @@ import { InformationCard } from './information-card'
|
|
|
11
11
|
|
|
12
12
|
function WidgetInformationPage() {
|
|
13
13
|
const { t } = useTranslation()
|
|
14
|
-
const [, setWidgetTabs] = useWidgetTabsAtom()
|
|
15
14
|
const [settings] = useWidgetSettingsAtom()
|
|
16
15
|
const isDarkMode = settings?.config?.theme === 'dark'
|
|
17
16
|
|
|
18
17
|
return (
|
|
19
|
-
<PageLayout className='
|
|
20
|
-
<div
|
|
21
|
-
|
|
22
|
-
'
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
aria-label='Return Button'
|
|
28
|
-
onClick={() => setWidgetTabs('chat')}>
|
|
29
|
-
<Icon name='arrow-left' width={16} height={16} />
|
|
30
|
-
</button>
|
|
31
|
-
<h1 className='mx-auto font-bold'>{t('info.title')}</h1>
|
|
18
|
+
<PageLayout className='flex min-h-0 flex-col text-neutral-900 max-md:p-[1.125rem] md:p-5'>
|
|
19
|
+
<div className='mb-4'>
|
|
20
|
+
<WidgetHeader
|
|
21
|
+
enabledButtons={['close']}
|
|
22
|
+
showContent={false}
|
|
23
|
+
showContentWithoutMeta
|
|
24
|
+
tutorName={t('info.title')}
|
|
25
|
+
/>
|
|
32
26
|
</div>
|
|
33
27
|
|
|
34
|
-
<div className='
|
|
28
|
+
<div className='my-8 flex justify-center'>
|
|
35
29
|
<div className='flex flex-col items-center gap-2'>
|
|
36
30
|
<AIAvatar />
|
|
37
31
|
|
|
@@ -32,7 +32,7 @@ function WidgetLoadingPage() {
|
|
|
32
32
|
return (
|
|
33
33
|
<PageLayout
|
|
34
34
|
asideChild={<ChatInput name='new-chat-msg-input' ref={chatInputRef} loading={true} />}>
|
|
35
|
-
<div className='
|
|
35
|
+
<div className='flex h-full flex-col justify-start max-md:p-[1.125rem] md:p-5'>
|
|
36
36
|
<WidgetHeader enabledButtons={['close']} showContent={false} />
|
|
37
37
|
<div className='mt-auto'>
|
|
38
38
|
<MessageSkeleton />
|
|
@@ -4,7 +4,7 @@ import clsx from 'clsx'
|
|
|
4
4
|
import type { MouseEventHandler } from 'react'
|
|
5
5
|
import { useTranslation } from 'react-i18next'
|
|
6
6
|
|
|
7
|
-
import { Button } from '@/src/lib/components'
|
|
7
|
+
import { Button, HorizontalDraggableScroll } from '@/src/lib/components'
|
|
8
8
|
import { useRefEventListener } from '@/src/lib/hooks'
|
|
9
9
|
import { ChatInput, useChatInputValueAtom } from '@/src/modules/messages/components'
|
|
10
10
|
import { getAllMessagesQuery, useSendTextMessage } from '@/src/modules/messages/hooks'
|
|
@@ -90,14 +90,10 @@ function WidgetStarterPage() {
|
|
|
90
90
|
}>
|
|
91
91
|
<div className='grid-areas-[a_b] grid h-full grid-cols-1 grid-rows-[1fr_auto]'>
|
|
92
92
|
<div
|
|
93
|
-
className={clsx('grid-area-[a] flex min-h-0 flex-col
|
|
93
|
+
className={clsx('grid-area-[a] flex min-h-0 flex-col max-md:p-[1.125rem] md:p-5', {
|
|
94
94
|
[styles.bg]: settings?.config?.theme === 'dark'
|
|
95
95
|
})}>
|
|
96
|
-
<WidgetHeader
|
|
97
|
-
enabledButtons={['archive', 'info', 'close']}
|
|
98
|
-
tutorName={name}
|
|
99
|
-
showContent={false}
|
|
100
|
-
/>
|
|
96
|
+
<WidgetHeader enabledButtons={['archive', 'info', 'close']} tutorName={name} />
|
|
101
97
|
|
|
102
98
|
<div className='my-auto'>
|
|
103
99
|
<GreetingsCard
|
|
@@ -107,7 +103,7 @@ function WidgetStarterPage() {
|
|
|
107
103
|
/>
|
|
108
104
|
</div>
|
|
109
105
|
</div>
|
|
110
|
-
<
|
|
106
|
+
<HorizontalDraggableScroll className='grid-area-[b] mx-5 my-6 flex flex-shrink-0 snap-x snap-mandatory gap-2 overflow-x-auto whitespace-nowrap [scrollbar-width:none] [&::-webkit-scrollbar]:hidden'>
|
|
111
107
|
<Button
|
|
112
108
|
variant='gradient-outline'
|
|
113
109
|
className='shrink-0 snap-end text-sm'
|
|
@@ -122,7 +118,7 @@ function WidgetStarterPage() {
|
|
|
122
118
|
<span>📝 </span>
|
|
123
119
|
{t('starter_page.wanna_summary')}
|
|
124
120
|
</Button>
|
|
125
|
-
</
|
|
121
|
+
</HorizontalDraggableScroll>
|
|
126
122
|
</div>
|
|
127
123
|
</PageLayout>
|
|
128
124
|
)
|
|
@@ -51,4 +51,4 @@ export const goBackTabAtom = atom(null, (get, set) => {
|
|
|
51
51
|
export const useGetWidgetTabsAtom = () => useAtom(widgetTabsAtom)
|
|
52
52
|
export const useWidgetTabsAtom = () => useAtom(setWidgetTabsAtom)
|
|
53
53
|
export const useWidgetTabsValueAtom = () => useAtomValue(setWidgetTabsAtom)
|
|
54
|
-
export const
|
|
54
|
+
export const useWidgetGoBackTabAtom = () => useAtom(goBackTabAtom)
|
package/tailwind.config.js
CHANGED
|
@@ -90,14 +90,12 @@ module.exports = {
|
|
|
90
90
|
900: 'var(--hc-color-neutral-900)',
|
|
91
91
|
1000: 'var(--hc-color-neutral-1000)'
|
|
92
92
|
},
|
|
93
|
-
ai:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
'gradient-accent': 'var(--ai-color-gradient-accent)'
|
|
100
|
-
}
|
|
93
|
+
'ai-primary': 'var(--ai-color-primary)',
|
|
94
|
+
'ai-secondary': 'var(--ai-color-secondary)',
|
|
95
|
+
'ai-dark': 'var(--ai-color-dark)',
|
|
96
|
+
'ai-chat-response': 'var(--ai-color-chat-response)',
|
|
97
|
+
'ai-gradient-primary': 'var(--ai-color-gradient-primary)',
|
|
98
|
+
'ai-gradient-accent': 'var(--ai-color-gradient-accent)'
|
|
101
99
|
}
|
|
102
100
|
}
|
|
103
101
|
},
|