1- import { Bot , Check , Copy , MessageCircle , User2 } from "lucide-react"
2- import { Fragment , memo , useMemo } from "react"
1+ import { Fragment , useMemo } from "react"
2+ import { CopyIcon , CheckIcon } from "@radix-ui/react-icons"
3+ import { BrainCircuit , CircleUserRound } from "lucide-react"
34
45import { cn } from "@/lib/utils"
5- import { Button } from "@/components/ui"
66
77import { ChatHandler , Message } from "./types"
88import { ChatMessageProvider } from "./providers/ChatMessageProvider"
@@ -23,26 +23,25 @@ import {
2323 * ChatMessage
2424 */
2525
26- interface ChatMessageProps extends React . PropsWithChildren {
27- message : Message
28- isLast : boolean
29- className ?: string
26+ interface ChatMessageProps {
3027 isLoading ?: boolean
28+ isLast : boolean
29+ message : Message
3130 append ?: ChatHandler [ "append" ]
3231}
3332
34- function ChatMessage ( props : ChatMessageProps ) {
35- const children = props . children ?? (
36- < >
37- < ChatMessageAvatar />
38- < ChatMessageContent isLoading = { props . isLoading } append = { props . append } />
39- < ChatMessageActions />
40- </ >
41- )
42-
33+ export function ChatMessage ( { isLoading, isLast, message, append } : ChatMessageProps ) {
4334 return (
44- < ChatMessageProvider value = { { message : props . message , isLast : props . isLast } } >
45- < div className = { cn ( "group flex gap-4 p-3" , props . className ) } > { children } </ div >
35+ < ChatMessageProvider value = { { message, isLast } } >
36+ < div
37+ className = { cn ( "relative group flex" , {
38+ "flex-row-reverse" : message . role === "user" ,
39+ "bg-vscode-input-background/50" : message . role === "user" ,
40+ } ) } >
41+ < ChatMessageAvatar />
42+ < ChatMessageContent isLoading = { isLoading } append = { append } />
43+ < ChatMessageActions />
44+ </ div >
4645 </ ChatMessageProvider >
4746 )
4847}
@@ -51,25 +50,17 @@ function ChatMessage(props: ChatMessageProps) {
5150 * ChatMessageAvatar
5251 */
5352
54- interface ChatMessageAvatarProps extends React . PropsWithChildren {
55- className ?: string
56- }
57-
58- function ChatMessageAvatar ( props : ChatMessageAvatarProps ) {
53+ function ChatMessageAvatar ( ) {
5954 const { message } = useChatMessage ( )
6055
6156 const roleIconMap : Record < string , React . ReactNode > = {
62- user : < User2 className = "h-4 w-4" /> ,
63- assistant : < Bot className = "h-4 w-4" /> ,
57+ user : < CircleUserRound className = "h-4 w-4" /> ,
58+ assistant : < BrainCircuit className = "h-4 w-4" /> ,
6459 }
6560
66- const children = props . children ?? roleIconMap [ message . role ] ?? < MessageCircle className = "h-4 w-4" />
67-
68- return (
69- < div className = "bg-background flex h-8 w-8 shrink-0 select-none items-center justify-center border" >
70- { children }
71- </ div >
72- )
61+ return roleIconMap [ message . role ] ? (
62+ < div className = "shrink-0 opacity-25 select-none p-2" > { roleIconMap [ message . role ] } </ div >
63+ ) : null
7364}
7465
7566/**
@@ -101,25 +92,22 @@ type ContentDisplayConfig = {
10192 component : React . ReactNode | null
10293}
10394
104- interface ChatMessageContentProps extends React . PropsWithChildren {
105- className ?: string
106- content ?: ContentDisplayConfig [ ]
95+ interface ChatMessageContentProps {
10796 isLoading ?: boolean
97+ content ?: ContentDisplayConfig [ ]
10898 append ?: ChatHandler [ "append" ]
109- message ?: Message // in case you want to customize the message
11099}
111100
112- function ChatMessageContent ( props : ChatMessageContentProps ) {
113- const { message : defaultMessage , isLast } = useChatMessage ( )
114- const message = props . message ?? defaultMessage
101+ function ChatMessageContent ( { isLoading, content, append } : ChatMessageContentProps ) {
102+ const { message, isLast } = useChatMessage ( )
115103 const annotations = message . annotations as MessageAnnotation [ ] | undefined
116104
117105 const contents = useMemo < ContentDisplayConfig [ ] > ( ( ) => {
118106 const displayMap : {
119107 [ key in ContentPosition ] ?: React . ReactNode | null
120108 } = {
121109 [ ContentPosition . CHAT_EVENTS ] : (
122- < EventAnnotations message = { message } showLoading = { ( isLast && props . isLoading ) ?? false } />
110+ < EventAnnotations message = { message } showLoading = { ( isLast && isLoading ) ?? false } />
123111 ) ,
124112 [ ContentPosition . CHAT_AGENT_EVENTS ] : < AgentEventAnnotations message = { message } /> ,
125113 [ ContentPosition . CHAT_IMAGE ] : < ImageAnnotations message = { message } /> ,
@@ -132,78 +120,52 @@ function ChatMessageContent(props: ChatMessageContentProps) {
132120 [ ContentPosition . CHAT_DOCUMENT_FILES ] : < DocumentFileAnnotations message = { message } /> ,
133121 [ ContentPosition . CHAT_SOURCES ] : < SourceAnnotations message = { message } /> ,
134122 ...( isLast &&
135- props . append && {
136- // show suggested questions only on the last message
123+ append && {
124+ // Show suggested questions only on the last message.
137125 [ ContentPosition . SUGGESTED_QUESTIONS ] : (
138- < SuggestedQuestionsAnnotations message = { message } append = { props . append } />
126+ < SuggestedQuestionsAnnotations message = { message } append = { append } />
139127 ) ,
140128 } ) ,
141129 }
142130
143- // Override the default display map with the custom content
144- props . content ?. forEach ( ( content ) => {
131+ // Override the default display map with the custom content.
132+ content ?. forEach ( ( content ) => {
145133 displayMap [ content . position ] = content . component
146134 } )
147135
148136 return Object . entries ( displayMap ) . map ( ( [ position , component ] ) => ( {
149137 position : parseInt ( position ) ,
150138 component,
151139 } ) )
152- } , [ annotations , isLast , message , props . append , props . content , props . isLoading ] )
140+ } , [ annotations , isLast , isLoading , content , append , message ] )
153141
154- const children = props . children ?? (
155- < >
142+ return (
143+ < div
144+ className = { cn ( "flex flex-col gap-4 flex-1 min-w-0 px-2 pt-4 pb-6" , {
145+ "text-right" : message . role === "user" ,
146+ } ) } >
156147 { contents
157148 . sort ( ( a , b ) => a . position - b . position )
158149 . map ( ( content , index ) => (
159150 < Fragment key = { index } > { content . component } </ Fragment >
160151 ) ) }
161- </ >
152+ </ div >
162153 )
163-
164- return < div className = { cn ( "flex min-w-0 flex-1 flex-col gap-4" , props . className ) } > { children } </ div >
165154}
166155
167156/**
168157 * ChatMessageActions
169158 */
170159
171- interface ChatMessageActionsProps extends React . PropsWithChildren {
172- className ?: string
173- }
174-
175- function ChatMessageActions ( props : ChatMessageActionsProps ) {
176- const { isCopied, copyToClipboard } = useCopyToClipboard ( { timeout : 2000 } )
160+ function ChatMessageActions ( ) {
177161 const { message } = useChatMessage ( )
162+ const { isCopied, copyToClipboard } = useCopyToClipboard ( { timeout : 2000 } )
178163
179- const children = props . children ?? (
180- < Button onClick = { ( ) => copyToClipboard ( message . content ) } size = "icon" variant = "ghost" className = "h-8 w-8" >
181- { isCopied ? < Check className = "h-4 w-4" /> : < Copy className = "h-4 w-4" /> }
182- </ Button >
183- )
184164 return (
185- < div className = { cn ( "flex shrink-0 flex-col gap-2 opacity-0 group-hover:opacity-100" , props . className ) } >
186- { children }
165+ < div
166+ className = "absolute right-2 bottom-2 opacity-0 group-hover:opacity-25 cursor-pointer"
167+ onClick = { ( ) => copyToClipboard ( message . content ) } >
168+ { isCopied ? < CheckIcon /> : < CopyIcon /> }
187169 </ div >
188170 )
189171}
190-
191- /**
192- * ComposibleChatMessage
193- */
194-
195- type ComposibleChatMessage = typeof ChatMessage & {
196- Avatar : typeof ChatMessageAvatar
197- Content : typeof ChatMessageContent
198- Actions : typeof ChatMessageActions
199- }
200-
201- const PrimiviteChatMessage = memo ( ChatMessage , ( prevProps , nextProps ) => {
202- return ! nextProps . isLast && prevProps . isLast === nextProps . isLast && prevProps . message === nextProps . message
203- } ) as unknown as ComposibleChatMessage
204-
205- PrimiviteChatMessage . Avatar = ChatMessageAvatar
206- PrimiviteChatMessage . Content = ChatMessageContent
207- PrimiviteChatMessage . Actions = ChatMessageActions
208-
209- export default PrimiviteChatMessage
0 commit comments