Skip to content

Commit

Permalink
ReplyTo: render in ChatMessage
Browse files Browse the repository at this point in the history
  • Loading branch information
enricoros committed May 9, 2024
1 parent d929438 commit 2354cdc
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 86 deletions.
158 changes: 82 additions & 76 deletions src/apps/chat/components/message/ChatMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { copyToClipboard } from '~/common/util/clipboardUtils';
import { prettyBaseModel } from '~/common/util/modelUtils';
import { useUIPreferencesStore } from '~/common/state/store-ui';

import { ReplyToBubble } from './ReplyToBubble';
import { useChatShowTextDiff } from '../../store-app-chat';


Expand Down Expand Up @@ -267,6 +268,7 @@ export function ChatMessage(props: {
role: messageRole,
purposeId: messagePurposeId,
originLLM: messageOriginLLM,
metadata: messageMetadata,
created: messageCreated,
updated: messageUpdated,
} = props.message;
Expand Down Expand Up @@ -551,94 +553,98 @@ export function ChatMessage(props: {
}),

// style: make room for a top decorator if set
...(!!props.topDecorator && {
pt: '2.5rem',
}),
'&:hover > button': { opacity: 1 },

// layout
display: 'flex',
flexDirection: !fromAssistant ? 'row-reverse' : 'row',
alignItems: 'flex-start',
gap: { xs: 0, md: 1 },
display: 'grid',
gap: 1,

...props.sx,
}}
>

{/* (Optional) underlayed top decorator */}
{props.topDecorator && (
<Box sx={{ position: 'absolute', left: 0, right: 0, top: 0, textAlign: 'center' }}>
{props.topDecorator}
</Box>
)}
{props.topDecorator}

{/* Avatar (Persona) */}
{showAvatar && (
<Box sx={personaSx}>

{/* Persona Avatar or Menu Button */}
<Box
onClick={handleOpsMenuToggle}
onContextMenu={handleOpsMenuToggle}
onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)}
sx={{ display: 'flex' }}
>
{(isHovering || opsMenuAnchor) ? (
<IconButton variant={opsMenuAnchor ? 'solid' : 'soft'} color={(fromAssistant || fromSystem) ? 'neutral' : 'primary'} sx={avatarIconSx}>
<MoreVertIcon />
</IconButton>
) : (
avatarEl
{/* Message Row: Avatar, Blocks (1 text -> blocksRenderer) */}
<Box sx={{
display: 'flex',
flexDirection: !fromAssistant ? 'row-reverse' : 'row',
alignItems: 'flex-start',
gap: { xs: 0, md: 1 },
}}>

{/* Avatar (Persona) */}
{showAvatar && (
<Box sx={personaSx}>

{/* Persona Avatar or Menu Button */}
<Box
onClick={handleOpsMenuToggle}
onContextMenu={handleOpsMenuToggle}
onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)}
sx={{ display: 'flex' }}
>
{(isHovering || opsMenuAnchor) ? (
<IconButton variant={opsMenuAnchor ? 'solid' : 'soft'} color={(fromAssistant || fromSystem) ? 'neutral' : 'primary'} sx={avatarIconSx}>
<MoreVertIcon />
</IconButton>
) : (
avatarEl
)}
</Box>

{/* Assistant model name */}
{fromAssistant && (
<Tooltip arrow title={messageTyping ? null : (messageOriginLLM || 'unk-model')} variant='solid'>
<Typography level='body-xs' sx={{
overflowWrap: 'anywhere',
...(messageTyping ? { animation: `${animationColorRainbow} 5s linear infinite` } : {}),
}}>
{prettyBaseModel(messageOriginLLM)}
</Typography>
</Tooltip>
)}
</Box>

{/* Assistant model name */}
{fromAssistant && (
<Tooltip arrow title={messageTyping ? null : (messageOriginLLM || 'unk-model')} variant='solid'>
<Typography level='body-xs' sx={{
overflowWrap: 'anywhere',
...(messageTyping ? { animation: `${animationColorRainbow} 5s linear infinite` } : {}),
}}>
{prettyBaseModel(messageOriginLLM)}
</Typography>
</Tooltip>
)}

</Box>
)}


{/* Edit / Blocks */}
{isEditing ? (

<InlineTextarea
initialText={messageText} onEdit={handleTextEdited}
sx={editBlocksSx}
/>

) : (

<BlocksRenderer
ref={blocksRendererRef}
text={messageText}
fromRole={messageRole}
contentScaling={contentScaling}
errorMessage={errorMessage}
fitScreen={props.fitScreen}
isBottom={props.isBottom}
renderTextAsMarkdown={renderMarkdown}
renderTextDiff={textDiffs || undefined}
showDate={props.showBlocksDate === true ? messageUpdated || messageCreated || undefined : undefined}
showUnsafeHtml={props.showUnsafeHtml}
wasUserEdited={wasEdited}
onContextMenu={(props.onMessageEdit && ENABLE_SELECTION_RIGHT_CLICK_MENU) ? handleBlocksContextMenu : undefined}
onDoubleClick={(props.onMessageEdit && doubleClickToEdit) ? handleBlocksDoubleClick : undefined}
optiAllowMemo={messageTyping}
/>

)}
</Box>
)}


{/* Edit / Blocks */}
{isEditing ? (

<InlineTextarea
initialText={messageText} onEdit={handleTextEdited}
sx={editBlocksSx}
/>

) : (

<BlocksRenderer
ref={blocksRendererRef}
text={messageText}
fromRole={messageRole}
contentScaling={contentScaling}
errorMessage={errorMessage}
fitScreen={props.fitScreen}
isBottom={props.isBottom}
renderTextAsMarkdown={renderMarkdown}
renderTextDiff={textDiffs || undefined}
showDate={props.showBlocksDate === true ? messageUpdated || messageCreated || undefined : undefined}
showUnsafeHtml={props.showUnsafeHtml}
wasUserEdited={wasEdited}
onContextMenu={(props.onMessageEdit && ENABLE_SELECTION_RIGHT_CLICK_MENU) ? handleBlocksContextMenu : undefined}
onDoubleClick={(props.onMessageEdit && doubleClickToEdit) ? handleBlocksDoubleClick : undefined}
optiAllowMemo={messageTyping}
/>

)}

</Box>

{/* Reply-To Bubble */}
{!!messageMetadata?.inReplyToText && <ReplyToBubble inlineMessage replyToText={messageMetadata.inReplyToText} className='reply-to-bubble' />}


{/* Overlay copy icon */}
Expand Down
45 changes: 37 additions & 8 deletions src/apps/chat/components/message/ReplyToBubble.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
import ReplyRoundedIcon from '@mui/icons-material/ReplyRounded';


// configuration
const INLINE_COLOR = 'primary';


const bubbleComposerSx: SxProps = {
// contained
width: '100%',
Expand All @@ -24,16 +28,39 @@ const bubbleComposerSx: SxProps = {
alignItems: 'start',
};

const inlineMessageSx: SxProps = {
...bubbleComposerSx,

// redefine
// border: 'none',
mt: 1,
borderColor: `${INLINE_COLOR}.outlinedColor`,
borderRadius: 'sm',
boxShadow: 'xs',
width: undefined,
padding: '0.375rem 0.25rem 0.375rem 0.5rem',

// self-layout (parent: 'grid')
ml: 'auto',
mr: { xs: 7.75, md: 10.5 }, // personaSx.minWidth + gap (md: 1) + 1.5 (text margin)

};


export function ReplyToBubble(props: {
replyToText: string | null,
onClear: () => void
inlineMessage?: boolean
onClear?: () => void,
className?: string,
}) {
return (
<Box className={props.className} sx={bubbleComposerSx}>
<Tooltip disableInteractive arrow title='Replying to the assistant text' placement='top'>
<ReplyRoundedIcon sx={{ color: 'primary.solidBg', fontSize: 'xl', mt: 0.125 }} />
<Box className={props.className} sx={!props.inlineMessage ? bubbleComposerSx : inlineMessageSx}>
<Tooltip disableInteractive arrow title='Referring to this assistant text' placement='top'>
<ReplyRoundedIcon sx={{
color: props.inlineMessage ? `${INLINE_COLOR}.outlinedColor` : 'primary.solidBg',
fontSize: 'xl',
mt: 0.125,
}} />
</Tooltip>
<Typography level='body-sm' sx={{
flex: 1,
Expand All @@ -42,14 +69,16 @@ export function ReplyToBubble(props: {
overflow: 'auto',
maxHeight: '5.75rem',
lineHeight: 'xl',
color: 'text.secondary',
color: /*props.inlineMessage ? 'text.tertiary' :*/ 'text.secondary',
whiteSpace: 'break-spaces', // 'balance'
}}>
{props.replyToText}
</Typography>
<IconButton size='sm' onClick={props.onClear} sx={{ my: -0.5, background: 'none' }}>
<CloseRoundedIcon />
</IconButton>
{!!props.onClear && (
<IconButton size='sm' onClick={props.onClear} sx={{ my: -0.5, background: 'none' }}>
<CloseRoundedIcon />
</IconButton>
)}
</Box>
);
}
4 changes: 2 additions & 2 deletions src/modules/beam/scatter/BeamScatterInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ export function BeamScatterInput(props: {
const userMessageDecorator = React.useMemo(() => {
return (/*showHistoryMessage &&*/ otherHistoryCount >= 1 && scatterShowPrevMessages) ? (
// <Chip color='primary' variant='outlined' endDecorator={<ChipDelete />} sx={{ my: 1 }}>
<Typography level='body-xs' sx={{ my: 1.5 }} onClick={undefined /*() => setShowHistoryMessage(on => !on)*/}>
... {otherHistoryCount === 1 ? (isFirstMessageSystem ? '1 system message' : '1 message') : `${otherHistoryCount} messages`} before this input ...
<Typography level='body-xs' sx={{ my: 1, mx: 'auto', color: 'neutral.softColor' }} onClick={undefined /*() => setShowHistoryMessage(on => !on)*/}>
... {otherHistoryCount === 1 ? (isFirstMessageSystem ? '1 system message' : '1 message') : `${otherHistoryCount} messages`} before this one ...
</Typography>
// </Chip>
) : null;
Expand Down

0 comments on commit 2354cdc

Please sign in to comment.