@@ -9,14 +9,13 @@ import {
99 GitHubLogoIcon ,
1010 IconFrame ,
1111 IconFrameProps ,
12- Markdown ,
1312 PluralLogoMark ,
1413 Spinner ,
1514 TrashCanIcon ,
1615 WrapWithIf ,
1716} from '@pluralsh/design-system'
1817
19- import { ComponentPropsWithRef , ReactNode , useState } from 'react'
18+ import { ComponentPropsWithRef , useState } from 'react'
2019import styled , { CSSObject , useTheme } from 'styled-components'
2120import { aiGradientBorderStyles } from '../explain/ExplainWithAIButton'
2221
@@ -25,10 +24,12 @@ import {
2524 AiRole ,
2625 ChatType ,
2726 ChatTypeAttributes ,
27+ PrAutomationFragment ,
2828 PullRequestFragment ,
2929 useDeleteChatMutation ,
3030} from 'generated/graphql'
3131import CopyToClipboard from 'react-copy-to-clipboard'
32+ import { formatDateTime } from 'utils/datetime'
3233import { useChatbot } from '../AIContext'
3334import { ChatMessageContent } from './ChatMessageContent'
3435
@@ -37,55 +38,40 @@ export function ChatMessage({
3738 seq,
3839 content,
3940 role,
41+ threadId,
4042 type = ChatType . Text ,
4143 attributes,
4244 pullRequest,
45+ prAutomation,
4346 confirm,
4447 confirmedAt,
4548 serverName,
4649 disableActions,
4750 highlightToolContent,
4851 contentStyles,
52+ updatedAt,
4953 ...props
5054} : {
5155 id ?: string
5256 seq ?: number
5357 content ?: Nullable < string >
5458 role : AiRole
59+ threadId ?: string
5560 type ?: ChatType
5661 attributes ?: Nullable < ChatTypeAttributes >
5762 pullRequest ?: Nullable < PullRequestFragment >
63+ prAutomation ?: Nullable < PrAutomationFragment >
5864 confirm ?: Nullable < boolean >
5965 confirmedAt ?: Nullable < string >
6066 serverName ?: Nullable < string >
6167 disableActions ?: boolean
6268 contentStyles ?: CSSObject
6369 highlightToolContent ?: boolean
70+ updatedAt ?: Nullable < string >
6471} & Omit < ComponentPropsWithRef < typeof ChatMessageSC > , '$role' | 'content' > ) {
6572 const [ showActions , setShowActions ] = useState ( false )
66- let finalContent : ReactNode
6773 const rightAlign = role === AiRole . User
6874
69- if ( role === AiRole . Assistant || role === AiRole . System ) {
70- finalContent = < Markdown text = { content ?? '' } />
71- } else {
72- finalContent = (
73- < ChatMessageContent
74- id = { id ?? '' }
75- seq = { seq }
76- showActions = { showActions && ! disableActions }
77- side = { rightAlign ? 'right' : 'left' }
78- content = { content ?? '' }
79- type = { type }
80- attributes = { attributes }
81- confirm = { confirm }
82- confirmedAt = { confirmedAt }
83- serverName = { serverName }
84- highlightToolContent = { highlightToolContent }
85- />
86- )
87- }
88-
8975 return pullRequest ? (
9076 < PrChatMesssage
9177 url = { pullRequest . url }
@@ -110,12 +96,28 @@ export function ChatMessage({
11096 ...contentStyles ,
11197 } }
11298 >
113- { finalContent }
99+ < ChatMessageContent
100+ id = { id ?? '' }
101+ seq = { seq }
102+ showActions = { showActions && ! disableActions }
103+ side = { rightAlign ? 'right' : 'left' }
104+ content = { content ?? '' }
105+ role = { role }
106+ threadId = { threadId }
107+ type = { type }
108+ attributes = { attributes }
109+ confirm = { confirm }
110+ confirmedAt = { confirmedAt }
111+ serverName = { serverName }
112+ highlightToolContent = { highlightToolContent }
113+ prAutomation = { prAutomation }
114+ />
114115 { type !== ChatType . File && (
115116 < ChatMessageActions
116117 id = { id ?? '' }
117118 seq = { seq }
118119 content = { content ?? '' }
120+ timestamp = { updatedAt }
119121 show = { showActions && ! disableActions }
120122 side = { rightAlign ? 'right' : 'left' }
121123 />
@@ -130,6 +132,7 @@ export function ChatMessageActions({
130132 id,
131133 seq,
132134 content,
135+ timestamp,
133136 show = true ,
134137 side,
135138 iconFrameType = 'tertiary' ,
@@ -138,6 +141,7 @@ export function ChatMessageActions({
138141 id : string
139142 seq : number | undefined
140143 content : string
144+ timestamp ?: Nullable < string >
141145 show ?: boolean
142146 side : 'left' | 'right'
143147 iconFrameType ?: IconFrameProps [ 'type' ]
@@ -166,49 +170,61 @@ export function ChatMessageActions({
166170 $side = { side }
167171 { ...props }
168172 >
169- < WrapWithIf
170- condition = { ! copied }
171- wrapper = {
172- < CopyToClipboard
173- text = { content }
174- onCopy = { showCopied }
173+ { timestamp && side === 'right' && (
174+ < CaptionP $color = "text-light" >
175+ { formatDateTime ( timestamp , 'h:mma' ) }
176+ </ CaptionP >
177+ ) }
178+ < Flex gap = "xxsmall" >
179+ < WrapWithIf
180+ condition = { ! copied }
181+ wrapper = {
182+ < CopyToClipboard
183+ text = { content }
184+ onCopy = { showCopied }
185+ />
186+ }
187+ >
188+ < IconFrame
189+ clickable
190+ as = "div"
191+ tooltip = "Copy to clipboard"
192+ type = { iconFrameType }
193+ icon = {
194+ copied ? (
195+ < CheckIcon color = "icon-success" />
196+ ) : (
197+ < CopyIcon color = "icon-xlight" />
198+ )
199+ }
175200 />
176- }
177- >
201+ </ WrapWithIf >
178202 < IconFrame
179203 clickable
180204 as = "div"
181- tooltip = "Copy to clipboard "
205+ tooltip = "Fork thread from this message "
182206 type = { iconFrameType }
207+ onClick = { onFork }
183208 icon = {
184- copied ? (
185- < CheckIcon color = "icon-success" />
186- ) : (
187- < CopyIcon color = "icon-xlight" />
188- )
209+ mutationLoading ? < Spinner /> : < GitForkIcon color = "icon-xlight" />
189210 }
190211 />
191- </ WrapWithIf >
192- < IconFrame
193- clickable
194- as = "div"
195- tooltip = "Fork thread from this message"
196- type = { iconFrameType }
197- onClick = { onFork }
198- icon = {
199- mutationLoading ? < Spinner /> : < GitForkIcon color = "icon-xlight" />
200- }
201- />
202- < IconFrame
203- clickable
204- as = "div"
205- tooltip = "Delete message"
206- type = { iconFrameType }
207- onClick = { onDelete }
208- icon = {
209- deleteLoading ? < Spinner /> : < TrashCanIcon color = "icon-xlight" />
210- }
211- />
212+ < IconFrame
213+ clickable
214+ as = "div"
215+ tooltip = "Delete message"
216+ type = { iconFrameType }
217+ onClick = { onDelete }
218+ icon = {
219+ deleteLoading ? < Spinner /> : < TrashCanIcon color = "icon-danger" />
220+ }
221+ />
222+ </ Flex >
223+ { timestamp && side === 'left' && (
224+ < CaptionP $color = "text-light" >
225+ { formatDateTime ( timestamp , 'h:mma' ) }
226+ </ CaptionP >
227+ ) }
212228 </ ActionsWrapperSC >
213229 )
214230}
@@ -274,7 +290,9 @@ const ActionsWrapperSC = styled.div<{
274290 $side : 'left' | 'right'
275291} > ( ( { theme, $show, $side } ) => ( {
276292 display : 'flex' ,
277- gap : theme . spacing . xxsmall ,
293+ width : '100%' ,
294+ gap : theme . spacing . large ,
295+ alignItems : 'center' ,
278296 opacity : $show ? 1 : 0 ,
279297 transition : '0.3s opacity ease' ,
280298 pointerEvents : $show ? 'auto' : 'none' ,
0 commit comments