diff --git a/src/apps/chat/components/ChatDrawerItem.tsx b/src/apps/chat/components/ChatDrawerItem.tsx
index 76a7b974a..3ae18572b 100644
--- a/src/apps/chat/components/ChatDrawerItem.tsx
+++ b/src/apps/chat/components/ChatDrawerItem.tsx
@@ -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';
diff --git a/src/apps/chat/components/message/ChatMessage.tsx b/src/apps/chat/components/message/ChatMessage.tsx
index 26db2b604..31cda3e2d 100644
--- a/src/apps/chat/components/message/ChatMessage.tsx
+++ b/src/apps/chat/components/message/ChatMessage.tsx
@@ -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 ;
-
- const mascotSx = larger ? { width: 64, height: 64 } : avatarIconSx;
- switch (messageRole) {
- case 'system':
- return ; // https://em-content.zobj.net/thumbs/120/apple/325/robot_1f916.png
-
- case 'user':
- return ; // 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 ;
-
- // icon: text-to-image
- if (isTextToImage)
- return ;
-
- // purpose symbol (if present)
- const symbol = SystemPurposes[messagePurposeId as SystemPurposeId]?.symbol;
- if (symbol)
- return
- {symbol}
- ;
-
- // default assistant avatar
- return ; // https://mui.com/static/images/avatar/2.jpg
- }
- return ;
-}
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 && (
-
+
@@ -567,7 +468,7 @@ export function ChatMessage(props: {
{/* Avatar (Persona) */}
{showAvatar && !isEditingText && (
-
+
{/* Persona Avatar or Menu Button */}
@@ -673,9 +574,9 @@ export function ChatMessage(props: {
{/* Editing: Cancel */}
{isEditingText && (
-
+
-
+
diff --git a/src/apps/chat/components/message/CleanerMessage.tsx b/src/apps/chat/components/message/CleanerMessage.tsx
index 440d0bba5..6d2afb0ce 100644
--- a/src/apps/chat/components/message/CleanerMessage.tsx
+++ b/src/apps/chat/components/message/CleanerMessage.tsx
@@ -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';
/**
diff --git a/src/apps/chat/components/message/messageUtils.tsx b/src/apps/chat/components/message/messageUtils.tsx
new file mode 100644
index 000000000..831726064
--- /dev/null
+++ b/src/apps/chat/components/message/messageUtils.tsx
@@ -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 ;
+
+ const mascotSx = larger ? { width: 48, height: 48 } : avatarIconSx;
+ switch (messageRole) {
+ case 'system':
+ return ; // https://em-content.zobj.net/thumbs/120/apple/325/robot_1f916.png
+
+ case 'user':
+ return ; // 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 ;
+
+ // icon: text-to-image
+ if (isTextToImage)
+ return ;
+
+ // purpose symbol (if present)
+ const symbol = SystemPurposes[messagePurposeId as SystemPurposeId]?.symbol;
+ if (symbol)
+ return
+ {symbol}
+ ;
+
+ // default assistant avatar
+ return ; // https://mui.com/static/images/avatar/2.jpg
+ }
+ return ;
+}
+
+
+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';
+ }
+}