mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
MP: improve text part addition
This commit is contained in:
@@ -127,6 +127,10 @@ export function ChatMessageList(props: {
|
||||
props.conversationHandler?.messagesDelete([messageId]);
|
||||
}, [props.conversationHandler]);
|
||||
|
||||
const handleMessageAppendFragment = React.useCallback((messageId: DMessageId, fragment: DMessageFragment) => {
|
||||
props.conversationHandler?.messageFragmentAppend(messageId, fragment, false, false);
|
||||
}, [props.conversationHandler]);
|
||||
|
||||
const handleMessageDeleteFragment = React.useCallback((messageId: DMessageId, fragmentId: DMessageFragmentId) => {
|
||||
props.conversationHandler?.messageFragmentDelete(messageId, fragmentId, false, true);
|
||||
}, [props.conversationHandler]);
|
||||
@@ -286,6 +290,7 @@ export function ChatMessageList(props: {
|
||||
onMessageBeam={handleMessageBeam}
|
||||
onMessageBranch={handleMessageBranch}
|
||||
onMessageDelete={handleMessageDelete}
|
||||
onMessageFragmentAppend={handleMessageAppendFragment}
|
||||
onMessageFragmentDelete={handleMessageDeleteFragment}
|
||||
onMessageFragmentReplace={handleMessageReplaceFragment}
|
||||
onMessageToggleUserFlag={handleMessageToggleUserFlag}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useShallow } from 'zustand/react/shallow';
|
||||
import TimeAgo from 'react-timeago';
|
||||
|
||||
import type { SxProps } from '@mui/joy/styles/types';
|
||||
import { Box, ButtonGroup, CircularProgress, IconButton, ListDivider, ListItem, ListItemDecorator, MenuItem, Switch, Tooltip, Typography } from '@mui/joy';
|
||||
import { Box, Button, 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';
|
||||
@@ -33,10 +33,10 @@ import { copyToClipboard } from '~/common/util/clipboardUtils';
|
||||
import { prettyBaseModel } from '~/common/util/modelUtils';
|
||||
import { useUIPreferencesStore } from '~/common/state/store-ui';
|
||||
|
||||
import { AttachmentFragments } from './fragments-attachment-text/TextAttachmentFragments';
|
||||
import { ContentFragments } from './fragments-content/ContentFragments';
|
||||
import { ImageAttachmentFragments } from './fragments-attachment-image/ImageAttachmentFragments';
|
||||
import { ReplyToBubble } from './ReplyToBubble';
|
||||
import { TextAttachmentFragments } from './fragments-attachment-text/TextAttachmentFragments';
|
||||
import { avatarIconSx, makeMessageAvatar, messageBackground, personaColumnSx } from './messageUtils';
|
||||
import { useChatShowTextDiff } from '../../store-app-chat';
|
||||
|
||||
@@ -79,14 +79,15 @@ export function ChatMessage(props: {
|
||||
onMessageBeam?: (messageId: string) => Promise<void>,
|
||||
onMessageBranch?: (messageId: string) => void,
|
||||
onMessageDelete?: (messageId: string) => void,
|
||||
onMessageFragmentAppend?: (messageId: DMessageId, fragment: DMessageFragment) => void
|
||||
onMessageFragmentDelete?: (messageId: DMessageId, fragmentId: DMessageFragmentId) => void,
|
||||
onMessageFragmentReplace?: (messageId: DMessageId, fragmentId: DMessageFragmentId, newFragment: DMessageFragment) => void,
|
||||
onMessageToggleUserFlag?: (messageId: string, flag: DMessageUserFlag) => void,
|
||||
onMessageTruncate?: (messageId: string) => void,
|
||||
onReplyTo?: (messageId: string, selectedText: string) => void,
|
||||
onTextDiagram?: (messageId: string, text: string) => Promise<void>
|
||||
onTextImagine?: (text: string) => Promise<void>
|
||||
onTextSpeak?: (text: string) => Promise<void>
|
||||
onTextDiagram?: (messageId: string, text: string) => Promise<void>,
|
||||
onTextImagine?: (text: string) => Promise<void>,
|
||||
onTextSpeak?: (text: string) => Promise<void>,
|
||||
sx?: SxProps,
|
||||
}) {
|
||||
|
||||
@@ -125,7 +126,7 @@ export function ChatMessage(props: {
|
||||
} = props.message;
|
||||
|
||||
// split the fragments: image attachments are first, then content fragments, then other attachment fragments
|
||||
const [contentFragments, imageAttachments, otherAttachments] = classifyMessageFragments(messageFragments);
|
||||
const [contentFragments, imageAttachments, nonImageAttachments] = classifyMessageFragments(messageFragments);
|
||||
|
||||
const isUserStarred = messageHasUserFlag(props.message, 'starred');
|
||||
|
||||
@@ -143,7 +144,11 @@ export function ChatMessage(props: {
|
||||
// const textDiffs = useSanityTextDiffs(messageText, props.diffPreviousText, showDiff);
|
||||
|
||||
|
||||
const { onMessageFragmentDelete, onMessageFragmentReplace } = props;
|
||||
const { onMessageFragmentAppend, onMessageFragmentDelete, onMessageFragmentReplace } = props;
|
||||
|
||||
const handleFragmentNew = React.useCallback(() => {
|
||||
onMessageFragmentAppend?.(messageId, createTextContentFragment(''));
|
||||
}, [messageId, onMessageFragmentAppend]);
|
||||
|
||||
const handleFragmentDelete = React.useCallback((fragmentId: DMessageFragmentId) => {
|
||||
onMessageFragmentDelete?.(messageId, fragmentId);
|
||||
@@ -535,11 +540,22 @@ export function ChatMessage(props: {
|
||||
<ImageAttachmentFragments
|
||||
imageAttachments={imageAttachments}
|
||||
contentScaling={contentScaling}
|
||||
messageRole={messageRole}
|
||||
isMobile={props.isMobile}
|
||||
onFragmentDelete={handleFragmentDelete}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* If editing and there's no content, have a button to create a new TextContentFragment */}
|
||||
{isEditingText && !contentFragments.length && (
|
||||
<Button variant='plain' color='neutral' onClick={handleFragmentNew} sx={{
|
||||
ml: fromAssistant ? undefined : 'auto',
|
||||
mr: fromAssistant ? 'auto' : undefined,
|
||||
}}>
|
||||
Add text
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Content Fragments (iterating all to preserve the index) */}
|
||||
<ContentFragments
|
||||
fragments={contentFragments}
|
||||
@@ -567,13 +583,15 @@ export function ChatMessage(props: {
|
||||
/>
|
||||
|
||||
{/* Attachment Fragments */}
|
||||
{/*{hasAttachments && (*/}
|
||||
<AttachmentFragments
|
||||
attachmentFragments={otherAttachments}
|
||||
messageRole={messageRole}
|
||||
contentScaling={contentScaling}
|
||||
/>
|
||||
{/*)}*/}
|
||||
{nonImageAttachments.length >= 1 && (
|
||||
<TextAttachmentFragments
|
||||
textFragments={nonImageAttachments}
|
||||
messageRole={messageRole}
|
||||
contentScaling={contentScaling}
|
||||
isMobile={props.isMobile}
|
||||
onFragmentDelete={handleFragmentDelete}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Reply-To Bubble */}
|
||||
{!!messageMetadata?.inReplyToText && (
|
||||
|
||||
+13
-13
@@ -3,7 +3,7 @@ import * as React from 'react';
|
||||
import type { SxProps } from '@mui/joy/styles/types';
|
||||
import { Box } from '@mui/joy';
|
||||
|
||||
import type { DMessageAttachmentFragment, DMessageFragmentId } from '~/common/stores/chat/chat.message';
|
||||
import type { DMessageAttachmentFragment, DMessageFragmentId, DMessageRole } from '~/common/stores/chat/chat.message';
|
||||
import { ContentScaling, themeScalingMap } from '~/common/app.theme';
|
||||
|
||||
import { ContentPartImageRefDBlob, showImageDataRefInNewTab } from '../fragments-content/ContentPartImageRef';
|
||||
@@ -23,11 +23,8 @@ const layoutSx: SxProps = {
|
||||
// layout
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
// alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
|
||||
// display: 'grid',
|
||||
// gridTemplateColumns: 'repeat(auto-fit, minmax(max(min(100%, 400px), 100%/5), 1fr))',
|
||||
// alignItems: 'center', // commented to keep them to the top
|
||||
// justifyContent: 'flex-end', // commented as we do it dynamically
|
||||
gap: { xs: 0.5, md: 1 },
|
||||
};
|
||||
|
||||
@@ -48,8 +45,6 @@ const imageSheetPatchSx: SxProps = {
|
||||
// override the style in RenderImageURL
|
||||
maxWidth: CARD_MAX_WIDTH, // very important to keep the aspect ratio
|
||||
maxHeight: CARD_MAX_HEIGHT, // very important to keep the aspect ratio
|
||||
|
||||
// style
|
||||
// width: '100%',
|
||||
// height: '100%',
|
||||
// objectFit: 'cover',
|
||||
@@ -58,18 +53,23 @@ const imageSheetPatchSx: SxProps = {
|
||||
|
||||
|
||||
/**
|
||||
* Shows image attachments in a Grid (responsive), similarly to
|
||||
* Shows image attachments in a flexbox that wraps the images (overflowing by rows)
|
||||
* Also see `TextAttachmentFragments` for the text version, and 'ContentFragments'.
|
||||
*/
|
||||
export function ImageAttachmentFragments(props: {
|
||||
imageAttachments: DMessageAttachmentFragment[],
|
||||
contentScaling: ContentScaling,
|
||||
messageRole: DMessageRole,
|
||||
isMobile?: boolean,
|
||||
onFragmentDelete: (fragmentId: DMessageFragmentId) => void,
|
||||
}) {
|
||||
|
||||
const layoutSxMemo = React.useMemo((): SxProps => ({
|
||||
...layoutSx,
|
||||
justifyContent: props.messageRole === 'assistant' ? 'flex-start' : 'flex-end',
|
||||
}), [props.messageRole]);
|
||||
|
||||
// memo the scaled image style
|
||||
const scaledImageCardSx = React.useMemo((): SxProps => ({
|
||||
const cardStyleSxMemo = React.useMemo((): SxProps => ({
|
||||
fontSize: themeScalingMap[props.contentScaling]?.blockFontSize ?? undefined,
|
||||
lineHeight: themeScalingMap[props.contentScaling]?.blockLineHeight ?? 1.75,
|
||||
...imageSheetPatchSx,
|
||||
@@ -77,7 +77,7 @@ export function ImageAttachmentFragments(props: {
|
||||
|
||||
|
||||
return (
|
||||
<Box aria-label={`${props.imageAttachments.length} image(s)`} sx={layoutSx}>
|
||||
<Box aria-label={`${props.imageAttachments.length} image(s)`} sx={layoutSxMemo}>
|
||||
|
||||
{/* render each image attachment */}
|
||||
{props.imageAttachments.map(attachmentFragment => {
|
||||
@@ -99,7 +99,7 @@ export function ImageAttachmentFragments(props: {
|
||||
imageHeight={imageRefPart.height}
|
||||
onOpenInNewTab={() => showImageDataRefInNewTab(dataRef)}
|
||||
onDeleteFragment={() => props.onFragmentDelete(attachmentFragment.fId)}
|
||||
scaledImageSx={scaledImageCardSx}
|
||||
scaledImageSx={cardStyleSxMemo}
|
||||
variant='attachment-card'
|
||||
/>
|
||||
);
|
||||
|
||||
+17
-6
@@ -1,28 +1,38 @@
|
||||
import * as React from 'react';
|
||||
|
||||
import type { SxProps } from '@mui/joy/styles/types';
|
||||
import { Box } from '@mui/joy';
|
||||
|
||||
import type { ContentScaling } from '~/common/app.theme';
|
||||
import type { DMessageAttachmentFragment, DMessageRole } from '~/common/stores/chat/chat.message';
|
||||
import type { DMessageAttachmentFragment, DMessageFragmentId, DMessageRole } from '~/common/stores/chat/chat.message';
|
||||
|
||||
import { ContentPartPlaceholder } from '../fragments-content/ContentPartPlaceholder';
|
||||
|
||||
|
||||
const layoutSx: SxProps = {};
|
||||
|
||||
|
||||
/**
|
||||
* Displays a list of 'cards' which are buttons with a mutually exclusive active state.
|
||||
* When one is active, there is a content part just right under (with the collapse mechanism in case it's a user role).
|
||||
* If one is clicked the content part (use ContentFragments with a single Fragment) is displayed.
|
||||
*/
|
||||
export function AttachmentFragments(props: {
|
||||
attachmentFragments: DMessageAttachmentFragment[],
|
||||
export function TextAttachmentFragments(props: {
|
||||
textFragments: DMessageAttachmentFragment[],
|
||||
messageRole: DMessageRole,
|
||||
contentScaling: ContentScaling,
|
||||
isMobile?: boolean,
|
||||
onFragmentDelete: (fragmentId: DMessageFragmentId) => void,
|
||||
}) {
|
||||
|
||||
if (!props.attachmentFragments.length)
|
||||
if (!props.textFragments.length)
|
||||
return null;
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
{props.attachmentFragments.map((fragment, attachmentNumber) => (
|
||||
<Box aria-label={`${props.textFragments.length} image(s)`} sx={layoutSx}>
|
||||
|
||||
{/* render each text attachment */}
|
||||
{props.textFragments.map((fragment, attachmentNumber) => (
|
||||
<ContentPartPlaceholder
|
||||
key={'attachment-part-' + attachmentNumber}
|
||||
placeholderText={`Attachment Placeholder: ${fragment.part.pt}`}
|
||||
@@ -30,6 +40,7 @@ export function AttachmentFragments(props: {
|
||||
contentScaling={props.contentScaling}
|
||||
/>
|
||||
))}
|
||||
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user