Files
big-agi/components/ChatMessageSelectable.tsx
T
2023-04-22 00:19:19 -07:00

102 lines
3.8 KiB
TypeScript

import * as React from 'react';
import { Box, Button, Checkbox, IconButton, ListItem, Sheet, Typography, useTheme } from '@mui/joy';
import ClearIcon from '@mui/icons-material/Clear';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
import { DMessage } from '@/lib/stores/store-chats';
import { TokenBadge } from '@/components/util/TokenBadge';
import { makeAvatar, messageBackground } from '@/components/ChatMessage';
/**
* Header bar for controlling the operations during the Selection mode
*/
export const MessagesSelectionHeader = (props: { hasSelected: boolean, isBottom: boolean, onClose: () => void, onSelectAll: (selected: boolean) => void, onDeleteMessages: () => void }) =>
<Sheet color='neutral' variant='solid' invertedColors sx={{
display: 'flex', flexDirection: 'row', alignItems: 'center',
gap: { xs: 1, sm: 2 }, px: { xs: 1, md: 2 }, py: 1,
}}>
<Checkbox size='md' onChange={event => props.onSelectAll(event.target.checked)} sx={{ minWidth: 24, justifyContent: 'center' }} />
<Box>Select All</Box>
<Button variant='solid' disabled={!props.hasSelected} onClick={props.onDeleteMessages} sx={{ ml: 'auto', mr: 'auto', minWidth: 150 }} endDecorator={<DeleteOutlineIcon />}>
Delete
</Button>
<IconButton variant='plain' onClick={props.onClose}>
<ClearIcon />
</IconButton>
</Sheet>;
/**
* Small representation of a ChatMessage, used when in selection mode
*
* Shall look similarly to the main ChatMessage, for consistency, but just allow a simple checkbox selection
*/
export function ChatMessageSelectable(props: { message: DMessage, isBottom: boolean, selected: boolean, onToggleSelected: (messageId: string, selected: boolean) => void }) {
// external state
const theme = useTheme();
const {
id: messageId,
text: messageText,
sender: messageSender,
avatar: messageAvatar,
typing: messageTyping,
role: messageRole,
purposeId: messagePurposeId,
// originLLM: messageModelId,
tokenCount: messageTokenCount,
updated: messageUpdated,
} = props.message;
const fromAssistant = messageRole === 'assistant';
const isAssistantError = fromAssistant && (messageText.startsWith('[Issue] ') || messageText.startsWith('[OpenAI Issue]'));
const background = messageBackground(theme, messageRole, !!messageUpdated, isAssistantError);
const avatarEl: JSX.Element | null = React.useMemo(() =>
makeAvatar(messageAvatar, messageRole, messagePurposeId, messageSender, messageTyping, 'sm'),
[messageAvatar, messagePurposeId, messageRole, messageSender, messageTyping],
);
const handleCheckedChange = (event: React.ChangeEvent<HTMLInputElement>) => props.onToggleSelected(messageId, event.target.checked);
return (
<ListItem sx={{
display: 'flex', flexDirection: !fromAssistant ? 'row' : 'row', alignItems: 'center',
gap: { xs: 1, sm: 2 }, px: { xs: 1, md: 2 }, py: 2,
background,
borderBottom: `1px solid ${theme.vars.palette.divider}`,
// position: 'relative',
...(props.isBottom && { mb: 'auto' }),
'&:hover > button': { opacity: 1 },
}}>
<Box sx={{ display: 'flex', minWidth: 24, justifyContent: 'center' }}>
<Checkbox size='md' checked={props.selected} onChange={handleCheckedChange} />
</Box>
<Box sx={{ display: 'flex', minWidth: { xs: 40, sm: 48 }, justifyContent: 'center' }}>
{avatarEl}
</Box>
<Typography level='body2' sx={{ minWidth: 64 }}>
{messageRole}
</Typography>
<Box sx={{ display: 'flex', minWidth: { xs: 32, sm: 45 }, justifyContent: 'flex-end' }}>
<TokenBadge directTokens={messageTokenCount} tokenLimit={12345} inline />
</Box>
<Typography level='body1' sx={{ flexGrow: 1, textOverflow: 'ellipsis', overflow: 'hidden', whiteSpace: 'nowrap' }}>
{messageText}
</Typography>
</ListItem>
);
}