Blocks: rationalize, reduce PH usage and ABR usage.

This commit is contained in:
Enrico Ros
2024-09-26 18:14:29 -07:00
parent 5242d09b53
commit 93797afa7a
14 changed files with 187 additions and 54 deletions
@@ -3,17 +3,18 @@ import * as React from 'react';
import type { SxProps } from '@mui/joy/styles/types';
import { Box, Chip, ColorPaletteProp } from '@mui/joy';
import { ScaledTextBlockRenderer } from '~/modules/blocks/ScaledTextBlockRenderer';
import type { ContentScaling } from '~/common/app.theme';
import type { DMessageId, DMessageRole } from '~/common/stores/chat/chat.message';
import { ContentPartPlaceholder } from './fragments-content/ContentPartPlaceholder';
// configuration
const ACTIVE_COLOR: ColorPaletteProp = 'warning';
const containerSx: SxProps = {
marginInlineStart: 1.5,
backgroundColor: `${ACTIVE_COLOR}.softBg`,
borderRadius: 'lg',
// boxShadow: 'xs',
@@ -30,7 +31,7 @@ const chipSx: SxProps = {
};
export function ContinueFragment(props: {
export function BlockOpContinue(props: {
contentScaling: ContentScaling,
messageId: DMessageId,
messageRole: DMessageRole,
@@ -46,10 +47,10 @@ export function ContinueFragment(props: {
return (
<Box sx={containerSx}>
<ContentPartPlaceholder
placeholderText='🧱 Token limit hit.'
messageRole={props.messageRole}
<ScaledTextBlockRenderer
text='🧱 Token limit hit.'
contentScaling={props.contentScaling}
textRenderVariant='text'
// showAsItalic
/>
@@ -47,8 +47,8 @@ import { createTextContentFragment, DMessageFragment, DMessageFragmentId } from
import { useUIPreferencesStore } from '~/common/state/store-ui';
import { useUXLabsStore } from '~/common/state/store-ux-labs';
import { BlockOpContinue } from './BlockOpContinue';
import { ContentFragments } from './fragments-content/ContentFragments';
import { ContinueFragment } from './ContinueFragment';
import { DocumentAttachmentFragments } from './fragments-attachment-doc/DocumentAttachmentFragments';
import { ImageAttachmentFragments } from './fragments-attachment-image/ImageAttachmentFragments';
import { InReferenceToList } from './in-reference-to/InReferenceToList';
@@ -226,7 +226,7 @@ export function ChatMessage(props: {
// const textDiffs = useSanityTextDiffs(messageText, props.diffPreviousText, showDiff);
const { onMessageAssistantFrom, onMessageFragmentAppend, onMessageFragmentDelete, onMessageFragmentReplace } = props;
const { onMessageAssistantFrom, onMessageDelete, onMessageFragmentAppend, onMessageFragmentDelete, onMessageFragmentReplace } = props;
const handleFragmentNew = React.useCallback(() => {
onMessageFragmentAppend?.(messageId, createTextContentFragment(''));
@@ -385,9 +385,9 @@ export function ChatMessage(props: {
handleCloseOpsMenu();
};
const handleOpsDelete = (_e: React.MouseEvent) => {
props.onMessageDelete?.(messageId);
};
const handleOpsDelete = React.useCallback(() => {
onMessageDelete?.(messageId);
}, [messageId, onMessageDelete]);
// Context Menu
@@ -712,6 +712,7 @@ export function ChatMessage(props: {
onFragmentBlank={handleFragmentNew}
onFragmentDelete={handleFragmentDelete}
onFragmentReplace={handleFragmentReplace}
onMessageDelete={props.onMessageDelete ? handleOpsDelete : undefined}
onContextMenu={(props.onMessageFragmentReplace && ENABLE_CONTEXT_MENU) ? handleBlocksContextMenu : undefined}
onDoubleClick={(props.onMessageFragmentReplace /*&& doubleClickToEdit disabled, as we may have shift too */) ? handleBlocksDoubleClick : undefined}
@@ -734,7 +735,7 @@ export function ChatMessage(props: {
{/* Continue... */}
{props.isBottom && messageGenerator?.tokenStopReason === 'out-of-tokens' && !!props.onMessageContinue && (
<ContinueFragment
<BlockOpContinue
contentScaling={adjContentScaling}
messageId={messageId}
messageRole={messageRole}
@@ -18,7 +18,7 @@ import { createDMessageDataInlineText, createDocAttachmentFragment, DMessageAtta
import { useContextWorkspaceId } from '~/common/stores/workspace/WorkspaceIdProvider';
import { useScrollToBottom } from '~/common/scroll-to-bottom/useScrollToBottom';
import { TextFragmentEditor } from '../fragments-content/TextFragmentEditor';
import { BlockEdit_TextFragment } from '../fragments-content/BlockEdit_TextFragment';
import { buttonIconForFragment, DocSelColor } from './DocAttachmentFragmentButton';
import { useLiveFileSync } from './livefile-sync/useLiveFileSync';
@@ -258,7 +258,7 @@ export function DocAttachmentFragment(props: {
{/* Show / Edit the Document Attachment Part */}
{isEditing ? (
// Document Editor
<TextFragmentEditor
<BlockEdit_TextFragment
textPartText={fragmentDocPart.data.text}
fragmentId={fragmentId}
contentScaling={props.contentScaling}
@@ -32,7 +32,7 @@ const textAreaSlotPropsDone = {
* Very similar to <InlineTextArea /> but with externally controlled state rather than internal.
* Made it for as the editing alternative for <ContentPartText />.
*/
export function TextFragmentEditor(props: {
export function BlockEdit_TextFragment(props: {
// current value
textPartText: string,
fragmentId: DMessageFragmentId,
@@ -0,0 +1,76 @@
import * as React from 'react';
import type { SxProps } from '@mui/joy/styles/types';
import { Box, Chip } from '@mui/joy';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
import { ScaledTextBlockRenderer } from '~/modules/blocks/ScaledTextBlockRenderer';
import type { ContentScaling } from '~/common/app.theme';
const containerSx: SxProps = {
marginInlineStart: 1.5,
backgroundColor: 'neutral.softBg',
borderRadius: 'lg',
// layout
display: 'flex',
alignItems: 'center',
gap: 1,
};
const chipSx: SxProps = {
px: 2,
};
export function BlockOpEmpty(props: {
text: string,
contentScaling: ContentScaling,
onDelete?: () => void,
}) {
// state
// const { showPromisedOverlay } = useOverlayComponents();
// derived state
// const { onDelete } = props;
// const handleConfirmDelete = React.useCallback(async () => {
// if (onDelete && await showPromisedOverlay('chat-message-delete-confirmation', { rejectWithValue: false }, ({ onResolve, onUserReject }) =>
// <ConfirmationModal
// open onClose={onUserReject} onPositive={() => onResolve(true)}
// confirmationText='Are you sure you want to delete this message?'
// positiveActionText='Delete'
// title='Delete Message'
// />,
// )) onDelete();
// }, [onDelete, showPromisedOverlay]);
return (
<Box sx={containerSx}>
<ScaledTextBlockRenderer
text={props.text}
contentScaling={props.contentScaling}
textRenderVariant='text'
showAsItalic
/>
{!!props.onDelete && (
<Chip
color='neutral'
variant='outlined'
size={props.contentScaling === 'md' ? 'lg' : 'md'}
onClick={props.onDelete}
sx={chipSx}
startDecorator={<DeleteOutlineIcon />}
>
Delete
</Chip>
)}
</Box>
);
}
@@ -0,0 +1,22 @@
import * as React from 'react';
import { ScaledTextBlockRenderer } from '~/modules/blocks/ScaledTextBlockRenderer';
import type { ContentScaling } from '~/common/app.theme';
import type { DMessageRole } from '~/common/stores/chat/chat.message';
export function BlockPartError(props: {
errorText: string,
messageRole: DMessageRole,
contentScaling: ContentScaling,
}) {
return (
<ScaledTextBlockRenderer
text={props.errorText}
contentScaling={props.contentScaling}
textRenderVariant='text'
showAsDanger
/>
);
}
@@ -11,7 +11,7 @@ import type { DMessageContentFragment, DMessageFragmentId, DMessageImageRefPart
import { ContentScaling, themeScalingMap } from '~/common/app.theme';
export function ContentPartImageRef(props: {
export function BlockPartImageRef(props: {
imageRefPart: DMessageImageRefPart,
fragmentId: DMessageFragmentId,
contentScaling: ContentScaling,
@@ -1,30 +1,25 @@
import * as React from 'react';
import { AutoBlocksRenderer } from '~/modules/blocks/AutoBlocksRenderer';
import { ScaledTextBlockRenderer } from '~/modules/blocks/ScaledTextBlockRenderer';
import type { ContentScaling } from '~/common/app.theme';
import type { DMessageRole } from '~/common/stores/chat/chat.message';
export function ContentPartPlaceholder(props: {
export function BlockPartPlaceholder(props: {
placeholderText: string,
messageRole: DMessageRole,
contentScaling: ContentScaling,
showAsDanger?: boolean,
showAsItalic?: boolean,
// showAsProgress?: boolean,
}) {
// const placeholder = (
return (
<AutoBlocksRenderer
<ScaledTextBlockRenderer
text={props.placeholderText}
fromRole={props.messageRole}
contentScaling={props.contentScaling}
fitScreen={false}
isMobile={false /* assumption that the Placeholder Part doesn't react to size, and we assume desktop */}
showAsDanger={props.showAsDanger}
showAsItalic={props.showAsItalic}
textRenderVariant='text'
showAsItalic={props.showAsItalic}
/>
);
//
@@ -15,7 +15,7 @@ import { explainServiceErrors } from '../explainServiceErrors';
* The OG part, comprised of text, which can be markdown, have code blocks, etc.
* Uses BlocksRenderer to render the markdown/code/html/text, etc.
*/
export function ContentPartText_AutoBlocks(props: {
export function BlockPartText_AutoBlocks(props: {
// current value
textPartText: string,
setEditedText: (fragmentId: DMessageFragmentId, value: string, applyNow: boolean) => void,
@@ -1,19 +1,22 @@
import * as React from 'react';
import type { SxProps } from '@mui/joy/styles/types';
import { Box, Button, Sheet, Typography } from '@mui/joy';
import { Box, Button, Sheet } from '@mui/joy';
import { BlocksContainer } from '~/modules/blocks/BlocksContainers';
import { ScaledTextBlockRenderer } from '~/modules/blocks/ScaledTextBlockRenderer';
import type { ContentScaling } from '~/common/app.theme';
import type { DMessageRole } from '~/common/stores/chat/chat.message';
import { DMessageContentFragment, DMessageFragment, DMessageFragmentId, isContentFragment, isTextPart } from '~/common/stores/chat/chat.fragments';
import type { ChatMessageTextPartEditState } from '../ChatMessage';
import { ContentPartImageRef } from './ContentPartImageRef';
import { ContentPartPlaceholder } from './ContentPartPlaceholder';
import { ContentPartText_AutoBlocks } from './ContentPartText_AutoBlocks';
import { TextFragmentEditor } from './TextFragmentEditor';
import { BlockEdit_TextFragment } from './BlockEdit_TextFragment';
import { BlockOpEmpty } from './BlockOpEmpty';
import { BlockPartError } from './BlockPartError';
import { BlockPartImageRef } from './BlockPartImageRef';
import { BlockPartPlaceholder } from './BlockPartPlaceholder';
import { BlockPartText_AutoBlocks } from './BlockPartText_AutoBlocks';
const editLayoutSx: SxProps = {
@@ -60,6 +63,7 @@ export function ContentFragments(props: {
onFragmentBlank: () => void
onFragmentDelete: (fragmentId: DMessageFragmentId) => void,
onFragmentReplace: (fragmentId: DMessageFragmentId, newFragment: DMessageContentFragment) => void,
onMessageDelete?: () => void,
onContextMenu?: (event: React.MouseEvent) => void;
onDoubleClick?: (event: React.MouseEvent) => void;
@@ -90,16 +94,13 @@ export function ContentFragments(props: {
return <Box aria-label='message body' sx={isEditingText ? editLayoutSx : fromAssistant ? startLayoutSx : endLayoutSx}>
{/* The overall message is empty - show an indication of it */}
{/* Empty Message Block - if empty */}
{props.showEmptyNotice && (
<Sheet variant='solid' color='neutral' invertedColors sx={{ mx: 1.5 }}>
<ContentPartPlaceholder
placeholderText={`empty ${fromAssistant ? 'assistant ' : fromUser ? 'user ' : ''}message - please edit or delete`}
messageRole={props.messageRole}
contentScaling={props.contentScaling}
showAsItalic
/>
</Sheet>
<BlockOpEmpty
text={`empty ${fromAssistant ? 'model ' : fromUser ? 'user ' : ''}message`}
contentScaling={props.contentScaling}
onDelete={props.onMessageDelete}
/>
)}
{props.fragments.map((fragment) => {
@@ -111,7 +112,7 @@ export function ContentFragments(props: {
// editing for text parts
if (props.textEditsState && (isTextPart(fragment.part) || fragment.part.pt === 'error')) {
return (
<TextFragmentEditor
<BlockEdit_TextFragment
key={'edit-' + fragment.fId}
textPartText={isTextPart(fragment.part) ? fragment.part.text : fragment.part.error}
fragmentId={fragment.fId}
@@ -129,20 +130,18 @@ export function ContentFragments(props: {
switch (fragment.part.pt) {
case 'error':
return (
<ContentPartPlaceholder
<BlockPartError
key={fragment.fId}
placeholderText={fragment.part.error}
errorText={fragment.part.error}
messageRole={props.messageRole}
contentScaling={props.contentScaling}
showAsDanger
// showAsItalic
/>
);
case 'image_ref':
return (
<ContentPartImageRef
<BlockPartImageRef
key={fragment.fId}
imageRefPart={fragment.part}
fragmentId={fragment.fId}
@@ -154,7 +153,7 @@ export function ContentFragments(props: {
case 'ph':
return (
<ContentPartPlaceholder
<BlockPartPlaceholder
key={fragment.fId}
placeholderText={fragment.part.pText}
messageRole={props.messageRole}
@@ -166,7 +165,7 @@ export function ContentFragments(props: {
// This is the most frequent part by far, and can be broken down into sub-blocks
case 'text':
return (
<ContentPartText_AutoBlocks
<BlockPartText_AutoBlocks
key={fragment.fId}
// ref={blocksRendererRef}
textPartText={fragment.part.text}
@@ -309,9 +308,13 @@ export function ContentFragments(props: {
case '_pt_sentinel':
default:
return (
<Typography key={fragment.fId} level='body-sm' color='danger'>
Unknown Content fragment: {fragment.part.pt}
</Typography>
<ScaledTextBlockRenderer
key={fragment.fId}
text={`Unknown Content fragment: ${fragment.part.pt}`}
contentScaling={props.contentScaling}
textRenderVariant='text'
showAsDanger
/>
);
}
}).filter(Boolean)}
@@ -20,6 +20,7 @@ export type GlobalOverlayId = // string - disabled so we keep an orderliness
| 'chat-attachments-clear'
| 'chat-delete-confirmation'
| 'chat-reset-confirmation'
| 'chat-message-delete-confirmation'
| 'livefile-overwrite'
| 'shortcuts-confirm-close'
| 'blocks-off-enhance-code'
+4 -2
View File
@@ -10,7 +10,7 @@ import { EnhancedRenderCode } from './enhanced-code/EnhancedRenderCode';
import { RenderDangerousHtml } from './danger-html/RenderDangerousHtml';
import { RenderImageURL } from './image/RenderImageURL';
import { RenderMarkdown, RenderMarkdownMemo } from './markdown/RenderMarkdown';
import { RenderPlainChatText } from './plaintext/RenderPlainChatText';
import { RenderPlainText } from './plaintext/RenderPlainText';
import { RenderTextDiff } from './textdiff/RenderTextDiff';
import { ToggleExpansionButton } from './ToggleExpansionButton';
import { useAutoBlocksMemoSemiStable, useTextCollapser } from './blocks.hooks';
@@ -122,12 +122,14 @@ export function AutoBlocksRenderer(props: {
case 'md-bk':
const RenderMarkdownMemoOrNot = optimizeMemoBeforeLastBlock ? RenderMarkdownMemo : RenderMarkdown;
return (props.textRenderVariant === 'text' || fromSystem || isUserCommand) ? (
<RenderPlainChatText
// Keep in sync with ScaledPlainTextRenderer
<RenderPlainText
key={'txt-bk-' + index}
content={bkInput.content}
sx={scaledTypographySx}
/>
) : (
// Keep in sync with ScaledMarkdownRenderer
<RenderMarkdownMemoOrNot
key={'md-bk-' + index}
content={bkInput.content}
@@ -0,0 +1,32 @@
import * as React from 'react';
import type { ContentScaling } from '~/common/app.theme';
import { BlocksContainer } from './BlocksContainers';
import { RenderMarkdown } from './markdown/RenderMarkdown';
import { RenderPlainText } from './plaintext/RenderPlainText';
import { useScaledTypographySx } from './blocks.styles';
/**
* Smaller and lighter-weight version of AutoBlocksRenderer for rendering just some text
*/
export function ScaledTextBlockRenderer(props: {
text: string,
contentScaling: ContentScaling,
textRenderVariant: 'text' | 'markdown',
showAsDanger?: boolean,
showAsItalic?: boolean,
}) {
// state
const scaledTypographySx = useScaledTypographySx(props.contentScaling, !!props.showAsDanger, !!props.showAsItalic);
return (
<BlocksContainer>
{props.textRenderVariant === 'markdown' ? <RenderMarkdown content={props.text} sx={scaledTypographySx} />
: props.textRenderVariant === 'text' ? <RenderPlainText content={props.text} sx={scaledTypographySx} />
: ('unknown textRenderVariant: ' + props.textRenderVariant)}
</BlocksContainer>
);
}
@@ -10,7 +10,7 @@ import { extractChatCommand } from '../../../apps/chat/commands/commands.registr
* Renders a text block with chat commands.
* NOTE: should remove the commands parsing dependency.
*/
export const RenderPlainChatText = (props: { content: string; sx?: SxProps; }) => {
export const RenderPlainText = (props: { content: string; sx?: SxProps; }) => {
const elements = extractChatCommand(props.content);