mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
ChatMessage: extract avatars
This commit is contained in:
@@ -21,7 +21,7 @@ import { InlineTextarea } from '~/common/components/InlineTextarea';
|
||||
import { isDeepEqual } from '~/common/util/jsUtils';
|
||||
import { useChatStore } from '~/common/stores/chat/store-chats';
|
||||
|
||||
import { ANIM_BUSY_TYPING } from './message/ChatMessage';
|
||||
import { ANIM_BUSY_TYPING } from './message/messageUtils';
|
||||
import { CHAT_NOVEL_TITLE } from '../AppChat';
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useShallow } from 'zustand/react/shallow';
|
||||
import TimeAgo from 'react-timeago';
|
||||
|
||||
import type { SxProps } from '@mui/joy/styles/types';
|
||||
import { Avatar, Box, ButtonGroup, CircularProgress, IconButton, ListDivider, ListItem, ListItemDecorator, MenuItem, Switch, Tooltip, Typography } from '@mui/joy';
|
||||
import { Box, ButtonGroup, CircularProgress, IconButton, ListDivider, ListItem, ListItemDecorator, MenuItem, Switch, Tooltip, Typography } from '@mui/joy';
|
||||
import { ClickAwayListener, Popper } from '@mui/base';
|
||||
import AccountTreeOutlinedIcon from '@mui/icons-material/AccountTreeOutlined';
|
||||
import CheckRoundedIcon from '@mui/icons-material/CheckRounded';
|
||||
@@ -12,35 +12,31 @@ import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
import DifferenceIcon from '@mui/icons-material/Difference';
|
||||
import EditRoundedIcon from '@mui/icons-material/EditRounded';
|
||||
import Face6Icon from '@mui/icons-material/Face6';
|
||||
import ForkRightIcon from '@mui/icons-material/ForkRight';
|
||||
import FormatPaintOutlinedIcon from '@mui/icons-material/FormatPaintOutlined';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
import RecordVoiceOverOutlinedIcon from '@mui/icons-material/RecordVoiceOverOutlined';
|
||||
import ReplayIcon from '@mui/icons-material/Replay';
|
||||
import ReplyRoundedIcon from '@mui/icons-material/ReplyRounded';
|
||||
import SettingsSuggestIcon from '@mui/icons-material/SettingsSuggest';
|
||||
import SmartToyOutlinedIcon from '@mui/icons-material/SmartToyOutlined';
|
||||
import StarOutlineRoundedIcon from '@mui/icons-material/StarOutlineRounded';
|
||||
import StarRoundedIcon from '@mui/icons-material/StarRounded';
|
||||
import TelegramIcon from '@mui/icons-material/Telegram';
|
||||
import VerticalAlignBottomIcon from '@mui/icons-material/VerticalAlignBottom';
|
||||
|
||||
import { SystemPurposeId, SystemPurposes } from '../../../../data';
|
||||
|
||||
import { ChatBeamIcon } from '~/common/components/icons/ChatBeamIcon';
|
||||
import { CloseableMenu } from '~/common/components/CloseableMenu';
|
||||
import { createTextContentFragment, DMessage, DMessageAttachmentFragment, DMessageContentFragment, DMessageFragment, DMessageFragmentId, DMessageId, DMessageRole, DMessageUserFlag, messageFragmentsReduceText, messageHasUserFlag } from '~/common/stores/chat/chat.message';
|
||||
import { KeyStroke } from '~/common/components/KeyStroke';
|
||||
import { adjustContentScaling, themeScalingMap, themeZIndexPageBar } from '~/common/app.theme';
|
||||
import { animationColorRainbow } from '~/common/util/animUtils';
|
||||
import { copyToClipboard } from '~/common/util/clipboardUtils';
|
||||
import { createTextContentFragment, DMessage, DMessageAttachmentFragment, DMessageContentFragment, DMessageFragment, DMessageFragmentId, DMessageId, DMessageUserFlag, messageFragmentsReduceText, messageHasUserFlag } from '~/common/stores/chat/chat.message';
|
||||
import { prettyBaseModel } from '~/common/util/modelUtils';
|
||||
import { useUIPreferencesStore } from '~/common/state/store-ui';
|
||||
|
||||
import { AttachmentFragments } from './fragments-attachments/AttachmentFragments';
|
||||
import { ContentFragments } from './fragments-content/ContentFragments';
|
||||
import { ReplyToBubble } from './ReplyToBubble';
|
||||
import { avatarIconSx, makeMessageAvatar, messageBackground, personaColumnSx } from './messageUtils';
|
||||
import { useChatShowTextDiff } from '../../store-app-chat';
|
||||
|
||||
|
||||
@@ -52,100 +48,6 @@ const BUBBLE_MIN_TEXT_LENGTH = 3;
|
||||
// Enable the hover button to copy the whole message. The Copy button is also available in Blocks, or in the Avatar Menu.
|
||||
const ENABLE_COPY_MESSAGE_OVERLAY: boolean = false;
|
||||
|
||||
// Animations
|
||||
const ANIM_BUSY_DOWNLOADING = 'https://i.giphy.com/26u6dIwIphLj8h10A.webp'; // hourglass: https://i.giphy.com/TFSxpAIYz5inJGuY8f.webp, small-lq: https://i.giphy.com/131tNuGktpXGhy.webp, floppy: https://i.giphy.com/RxR1KghIie2iI.webp
|
||||
const ANIM_BUSY_PAINTING = 'https://i.giphy.com/media/5t9ujj9cMisyVjUZ0m/giphy.webp';
|
||||
const ANIM_BUSY_THINKING = 'https://i.giphy.com/media/l44QzsOLXxcrigdgI/giphy.webp';
|
||||
export const ANIM_BUSY_TYPING = 'https://i.giphy.com/media/jJxaUysjzO9ri/giphy.webp';
|
||||
|
||||
|
||||
export function messageBackground(messageRole: DMessageRole | string, wasEdited: boolean, isAssistantIssue: boolean): string {
|
||||
switch (messageRole) {
|
||||
case 'user':
|
||||
return 'primary.plainHoverBg'; // was .background.level1
|
||||
case 'assistant':
|
||||
return isAssistantIssue ? 'danger.softBg' : 'background.surface';
|
||||
case 'system':
|
||||
return wasEdited ? 'warning.softHoverBg' : 'neutral.softBg';
|
||||
default:
|
||||
return '#ff0000';
|
||||
}
|
||||
}
|
||||
|
||||
const avatarIconSx = {
|
||||
width: 36,
|
||||
height: 36,
|
||||
};
|
||||
|
||||
const personaSx: SxProps = {
|
||||
// make this stick to the top of the screen
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
|
||||
// flexBasis: 0, // this won't let the item grow
|
||||
minWidth: { xs: 50, md: 64 },
|
||||
maxWidth: 80,
|
||||
textAlign: 'center',
|
||||
// layout
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
};
|
||||
|
||||
|
||||
export function makeMessageAvatar(messageAvatarUrl: string | null, messageRole: DMessageRole | string, messageOriginLLM: string | undefined, messagePurposeId: SystemPurposeId | string | undefined, messageSender: string, messageIncomplete: boolean, larger?: boolean): React.JSX.Element {
|
||||
if (typeof messageAvatarUrl === 'string' && messageAvatarUrl)
|
||||
return <Avatar alt={messageSender} src={messageAvatarUrl} />;
|
||||
|
||||
const mascotSx = larger ? { width: 64, height: 64 } : avatarIconSx;
|
||||
switch (messageRole) {
|
||||
case 'system':
|
||||
return <SettingsSuggestIcon sx={avatarIconSx} />; // https://em-content.zobj.net/thumbs/120/apple/325/robot_1f916.png
|
||||
|
||||
case 'user':
|
||||
return <Face6Icon sx={avatarIconSx} />; // https://www.svgrepo.com/show/306500/openai.svg
|
||||
|
||||
case 'assistant':
|
||||
const isDownload = messageOriginLLM === 'web';
|
||||
const isTextToImage = messageOriginLLM === 'DALL·E' || messageOriginLLM === 'Prodia';
|
||||
const isReact = messageOriginLLM?.startsWith('react-');
|
||||
|
||||
// animation on incomplete messages
|
||||
if (messageIncomplete)
|
||||
return <Avatar
|
||||
alt={messageSender} variant='plain'
|
||||
src={isDownload ? ANIM_BUSY_DOWNLOADING
|
||||
: isTextToImage ? ANIM_BUSY_PAINTING
|
||||
: isReact ? ANIM_BUSY_THINKING
|
||||
: ANIM_BUSY_TYPING}
|
||||
sx={{ ...mascotSx, borderRadius: 'sm' }}
|
||||
/>;
|
||||
|
||||
// icon: text-to-image
|
||||
if (isTextToImage)
|
||||
return <FormatPaintOutlinedIcon sx={{
|
||||
...avatarIconSx,
|
||||
animation: `${animationColorRainbow} 1s linear 2.66`,
|
||||
}} />;
|
||||
|
||||
// purpose symbol (if present)
|
||||
const symbol = SystemPurposes[messagePurposeId as SystemPurposeId]?.symbol;
|
||||
if (symbol)
|
||||
return <Box sx={{
|
||||
fontSize: '24px',
|
||||
textAlign: 'center',
|
||||
width: '100%',
|
||||
minWidth: `${avatarIconSx.width}px`,
|
||||
lineHeight: `${avatarIconSx.height}px`,
|
||||
}}>
|
||||
{symbol}
|
||||
</Box>;
|
||||
|
||||
// default assistant avatar
|
||||
return <SmartToyOutlinedIcon sx={avatarIconSx} />; // https://mui.com/static/images/avatar/2.jpg
|
||||
}
|
||||
return <Avatar alt={messageSender} />;
|
||||
}
|
||||
|
||||
export type ChatMessageTextContentEditState = { [fragmentId: DMessageFragmentId]: string };
|
||||
|
||||
@@ -234,7 +136,6 @@ export function ChatMessage(props: {
|
||||
const couldSpeak = couldImagine;
|
||||
|
||||
const attachmentFragments = messageFragments.filter(f => f.ft === 'attachment') as DMessageAttachmentFragment[];
|
||||
const hasAttachments = attachmentFragments.length > 0;
|
||||
|
||||
|
||||
// TODO: fix the diffing
|
||||
@@ -553,7 +454,7 @@ export function ChatMessage(props: {
|
||||
|
||||
{/* Editing: Apply */}
|
||||
{isEditingText && (
|
||||
<Box sx={personaSx}>
|
||||
<Box sx={personaColumnSx}>
|
||||
<Tooltip arrow disableInteractive title='Apply Edits'>
|
||||
<IconButton variant='solid' color='warning' onClick={handleEditsApply}>
|
||||
<CheckRoundedIcon />
|
||||
@@ -567,7 +468,7 @@ export function ChatMessage(props: {
|
||||
|
||||
{/* Avatar (Persona) */}
|
||||
{showAvatar && !isEditingText && (
|
||||
<Box sx={personaSx}>
|
||||
<Box sx={personaColumnSx}>
|
||||
|
||||
{/* Persona Avatar or Menu Button */}
|
||||
<Box
|
||||
@@ -654,7 +555,7 @@ export function ChatMessage(props: {
|
||||
{/* Attachment Fragments */}
|
||||
{/*{hasAttachments && (*/}
|
||||
<AttachmentFragments
|
||||
attachmentFragments={[]}
|
||||
attachmentFragments={attachmentFragments}
|
||||
messageRole={messageRole}
|
||||
contentScaling={contentScaling}
|
||||
/>
|
||||
@@ -673,9 +574,9 @@ export function ChatMessage(props: {
|
||||
|
||||
{/* Editing: Cancel */}
|
||||
{isEditingText && (
|
||||
<Box sx={personaSx}>
|
||||
<Box sx={personaColumnSx}>
|
||||
<Tooltip arrow disableInteractive title='Discard Edits'>
|
||||
<IconButton onClick={handleEditsCancel} sx={avatarIconSx}>
|
||||
<IconButton onClick={handleEditsCancel}>
|
||||
<CloseRoundedIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
@@ -7,8 +7,8 @@ import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
|
||||
import { DMessage, messageFragmentsReduceText } from '~/common/stores/chat/chat.message';
|
||||
|
||||
import { TokenBadgeMemo } from '../composer/TokenBadge';
|
||||
import { makeMessageAvatar, messageBackground } from './ChatMessage';
|
||||
import { isErrorChatMessage } from './explainServiceErrors';
|
||||
import { makeMessageAvatar, messageBackground } from './messageUtils';
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import type { SxProps } from '@mui/joy/styles/types';
|
||||
import { Avatar, Box } from '@mui/joy';
|
||||
import Face6Icon from '@mui/icons-material/Face6';
|
||||
import FormatPaintOutlinedIcon from '@mui/icons-material/FormatPaintOutlined';
|
||||
import SettingsSuggestIcon from '@mui/icons-material/SettingsSuggest';
|
||||
import SmartToyOutlinedIcon from '@mui/icons-material/SmartToyOutlined';
|
||||
|
||||
import { SystemPurposeId, SystemPurposes } from '../../../../data';
|
||||
|
||||
import type { DMessageRole } from '~/common/stores/chat/chat.message';
|
||||
import { animationColorRainbow } from '~/common/util/animUtils';
|
||||
|
||||
|
||||
// Animations
|
||||
const ANIM_BUSY_DOWNLOADING = 'https://i.giphy.com/26u6dIwIphLj8h10A.webp'; // hourglass: https://i.giphy.com/TFSxpAIYz5inJGuY8f.webp, small-lq: https://i.giphy.com/131tNuGktpXGhy.webp, floppy: https://i.giphy.com/RxR1KghIie2iI.webp
|
||||
const ANIM_BUSY_PAINTING = 'https://i.giphy.com/media/5t9ujj9cMisyVjUZ0m/giphy.webp';
|
||||
const ANIM_BUSY_THINKING = 'https://i.giphy.com/media/l44QzsOLXxcrigdgI/giphy.webp';
|
||||
export const ANIM_BUSY_TYPING = 'https://i.giphy.com/media/jJxaUysjzO9ri/giphy.webp';
|
||||
|
||||
|
||||
export const personaColumnSx: SxProps = {
|
||||
// make this stick to the top of the screen
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
|
||||
// flexBasis: 0, // this won't let the item grow
|
||||
minWidth: { xs: 50, md: 64 },
|
||||
maxWidth: 80,
|
||||
textAlign: 'center',
|
||||
// layout
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
};
|
||||
|
||||
export const avatarIconSx = {
|
||||
width: 36,
|
||||
height: 36,
|
||||
} as const;
|
||||
|
||||
|
||||
export function makeMessageAvatar(
|
||||
messageAvatarUrl: string | null,
|
||||
messageRole: DMessageRole | string,
|
||||
messageOriginLLM: string | undefined,
|
||||
messagePurposeId: SystemPurposeId | string | undefined,
|
||||
messageSender: string,
|
||||
messageIncomplete: boolean,
|
||||
larger?: boolean,
|
||||
): React.JSX.Element {
|
||||
if (typeof messageAvatarUrl === 'string' && messageAvatarUrl)
|
||||
return <Avatar alt={messageSender} src={messageAvatarUrl} />;
|
||||
|
||||
const mascotSx = larger ? { width: 48, height: 48 } : avatarIconSx;
|
||||
switch (messageRole) {
|
||||
case 'system':
|
||||
return <SettingsSuggestIcon sx={avatarIconSx} />; // https://em-content.zobj.net/thumbs/120/apple/325/robot_1f916.png
|
||||
|
||||
case 'user':
|
||||
return <Face6Icon sx={avatarIconSx} />; // https://www.svgrepo.com/show/306500/openai.svg
|
||||
|
||||
case 'assistant':
|
||||
const isDownload = messageOriginLLM === 'web';
|
||||
const isTextToImage = messageOriginLLM === 'DALL·E' || messageOriginLLM === 'Prodia';
|
||||
const isReact = messageOriginLLM?.startsWith('react-');
|
||||
|
||||
// animation on incomplete messages
|
||||
if (messageIncomplete)
|
||||
return <Avatar
|
||||
alt={messageSender} variant='plain'
|
||||
src={isDownload ? ANIM_BUSY_DOWNLOADING
|
||||
: isTextToImage ? ANIM_BUSY_PAINTING
|
||||
: isReact ? ANIM_BUSY_THINKING
|
||||
: ANIM_BUSY_TYPING}
|
||||
sx={{ ...mascotSx, borderRadius: 'sm' }}
|
||||
/>;
|
||||
|
||||
// icon: text-to-image
|
||||
if (isTextToImage)
|
||||
return <FormatPaintOutlinedIcon sx={{
|
||||
...avatarIconSx,
|
||||
animation: `${animationColorRainbow} 1s linear 2.66`,
|
||||
}} />;
|
||||
|
||||
// purpose symbol (if present)
|
||||
const symbol = SystemPurposes[messagePurposeId as SystemPurposeId]?.symbol;
|
||||
if (symbol)
|
||||
return <Box sx={{
|
||||
fontSize: '24px',
|
||||
textAlign: 'center',
|
||||
width: '100%',
|
||||
minWidth: `${avatarIconSx.width}px`,
|
||||
lineHeight: `${avatarIconSx.height}px`,
|
||||
}}>
|
||||
{symbol}
|
||||
</Box>;
|
||||
|
||||
// default assistant avatar
|
||||
return <SmartToyOutlinedIcon sx={avatarIconSx} />; // https://mui.com/static/images/avatar/2.jpg
|
||||
}
|
||||
return <Avatar alt={messageSender} />;
|
||||
}
|
||||
|
||||
|
||||
export function messageBackground(messageRole: DMessageRole | string, wasEdited: boolean, isAssistantIssue: boolean): string {
|
||||
switch (messageRole) {
|
||||
case 'user':
|
||||
return 'primary.plainHoverBg'; // was .background.level1
|
||||
case 'assistant':
|
||||
return isAssistantIssue ? 'danger.softBg' : 'background.surface';
|
||||
case 'system':
|
||||
return wasEdited ? 'warning.softHoverBg' : 'neutral.softBg';
|
||||
default:
|
||||
return '#ff0000';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user