1
1
import { cn } from '@/lib/utils' ;
2
2
import { useChat } from 'ai/react' ;
3
- import { Send , XCircle } from 'lucide-react' ;
3
+ import { Bot , Send , Trash , XCircle } from 'lucide-react' ;
4
4
import { Input } from './ui/input' ;
5
5
import { Button } from './ui/button' ;
6
6
import { Message } from 'ai' ;
7
+ import { useUser } from '@clerk/nextjs' ;
8
+ import Image from 'next/image' ;
9
+ import { useEffect , useRef } from 'react' ;
7
10
8
11
interface AIChatBoxProps {
9
12
open : boolean ,
@@ -13,6 +16,23 @@ interface AIChatBoxProps {
13
16
const AIChatBox = ( { open, onClose } : AIChatBoxProps ) => {
14
17
const { messages, input, handleInputChange, handleSubmit, setMessages, isLoading, error } = useChat ( ) ;
15
18
19
+ const inputRef = useRef < HTMLInputElement > ( null ) ;
20
+ const scrollRef = useRef < HTMLDivElement > ( null ) ;
21
+
22
+ useEffect ( ( ) => {
23
+ if ( scrollRef . current ) {
24
+ scrollRef . current . scrollTop = scrollRef . current . scrollHeight
25
+ }
26
+ } , [ messages ] )
27
+
28
+ useEffect ( ( ) => {
29
+ if ( open ) {
30
+ inputRef . current ?. focus ( )
31
+ }
32
+ } , [ open ] )
33
+
34
+ const lastMessageIsUser = messages [ messages . length - 1 ] ?. role === "user"
35
+
16
36
return (
17
37
< div
18
38
className = { cn ( 'bottom-0 right-0 md:right-2 md:bottom-2 z-10 h-full md:h-auto w-full max-w-[500px] p-1' , open ? "fixed" : "hidden" ) }
@@ -21,16 +41,54 @@ const AIChatBox = ({ open, onClose }: AIChatBoxProps) => {
21
41
< button onClick = { onClose } className = 'mb-1 ms-auto block p-3' >
22
42
< XCircle size = { 30 } />
23
43
</ button >
24
- < div className = 'h-full p-3' >
44
+ < div className = 'h-full p-3 overflow-y-auto' ref = { scrollRef } >
25
45
{ messages . map ( ( message ) => (
26
46
< ChatMessage key = { message . id } message = { message } />
27
47
) ) }
48
+ {
49
+ isLoading && lastMessageIsUser && (
50
+ < ChatMessage
51
+ message = { {
52
+ role : "assistant" ,
53
+ content : "Thinking..."
54
+ } }
55
+ />
56
+ )
57
+ }
58
+ {
59
+ error && (
60
+ < ChatMessage
61
+ message = { {
62
+ role : "assistant" ,
63
+ content : "Something went wrong."
64
+ } }
65
+ />
66
+ )
67
+ }
68
+ {
69
+ ! error && messages . length === 0 && (
70
+ < div className = 'h-full flex items-center justify-center gap-3' >
71
+ < Bot />
72
+ Ask anything related to your notes!
73
+ </ div >
74
+ )
75
+ }
28
76
</ div >
29
77
< form onSubmit = { handleSubmit } className = 'm-3 flex gap-2' >
78
+ < Button
79
+ variant = 'outline'
80
+ size = 'icon'
81
+ className = 'shrink-0'
82
+ type = 'button'
83
+ onClick = { ( ) => setMessages ( [ ] ) }
84
+ >
85
+ < Trash />
86
+ </ Button >
30
87
< Input
31
88
value = { input }
32
89
onChange = { handleInputChange }
33
90
placeholder = 'Message'
91
+ ref = { inputRef }
34
92
/>
35
93
< Button type = 'submit' className = 'bg-gradient-to-r from-[#0F9E7B] to-[#1a745d]' >
36
94
< Send size = { 20 } />
@@ -43,11 +101,30 @@ const AIChatBox = ({ open, onClose }: AIChatBoxProps) => {
43
101
44
102
export default AIChatBox
45
103
46
- function ChatMessage ( { message : { role, content } } : { message : Message } ) {
104
+ function ChatMessage ( { message : { role, content } } : { message : Pick < Message , "role" | "content" > } ) {
105
+ const { user } = useUser ( )
106
+
107
+ const isAiMessage = role === "assistant"
108
+
47
109
return (
48
- < div className = 'mb-3' >
49
- < div > { role } </ div >
50
- < div > { content } </ div >
110
+ < div className = { cn ( 'mb-3 flex items-center' , isAiMessage ? "me-5 justify-start" : "ms-5 justify-end" ) } >
111
+ { isAiMessage && < Bot className = 'mr-2 shrink-0' /> }
112
+ < p className = { cn (
113
+ "whitespace-pre-line rounded-md border px-3 py-2" , isAiMessage ? "bg-background" : "bg-primary text-primary-foreground"
114
+ ) } >
115
+ { content }
116
+ </ p >
117
+ {
118
+ ! isAiMessage && user ?. imageUrl && (
119
+ < Image
120
+ src = { user . imageUrl }
121
+ alt = "User Image"
122
+ width = { 80 }
123
+ height = { 80 }
124
+ className = 'ml-2 rounded-full w-8 h-8 object-cover'
125
+ />
126
+ )
127
+ }
51
128
</ div >
52
129
)
53
130
}
0 commit comments