diff --git a/src/apps/chat/components/message/ContinueFragment.tsx b/src/apps/chat/components/message/BlockOpContinue.tsx similarity index 83% rename from src/apps/chat/components/message/ContinueFragment.tsx rename to src/apps/chat/components/message/BlockOpContinue.tsx index 97620226a..a7b0039d0 100644 --- a/src/apps/chat/components/message/ContinueFragment.tsx +++ b/src/apps/chat/components/message/BlockOpContinue.tsx @@ -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 ( - diff --git a/src/apps/chat/components/message/ChatMessage.tsx b/src/apps/chat/components/message/ChatMessage.tsx index 05c0a7ec9..79272c451 100644 --- a/src/apps/chat/components/message/ChatMessage.tsx +++ b/src/apps/chat/components/message/ChatMessage.tsx @@ -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 && ( - but with externally controlled state rather than internal. * Made it for as the editing alternative for . */ -export function TextFragmentEditor(props: { +export function BlockEdit_TextFragment(props: { // current value textPartText: string, fragmentId: DMessageFragmentId, diff --git a/src/apps/chat/components/message/fragments-content/BlockOpEmpty.tsx b/src/apps/chat/components/message/fragments-content/BlockOpEmpty.tsx new file mode 100644 index 000000000..1dec48792 --- /dev/null +++ b/src/apps/chat/components/message/fragments-content/BlockOpEmpty.tsx @@ -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 }) => + // onResolve(true)} + // confirmationText='Are you sure you want to delete this message?' + // positiveActionText='Delete' + // title='Delete Message' + // />, + // )) onDelete(); + // }, [onDelete, showPromisedOverlay]); + + return ( + + + + + {!!props.onDelete && ( + } + > + Delete + + )} + + + ); +} \ No newline at end of file diff --git a/src/apps/chat/components/message/fragments-content/BlockPartError.tsx b/src/apps/chat/components/message/fragments-content/BlockPartError.tsx new file mode 100644 index 000000000..338440427 --- /dev/null +++ b/src/apps/chat/components/message/fragments-content/BlockPartError.tsx @@ -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 ( + + ); +} \ No newline at end of file diff --git a/src/apps/chat/components/message/fragments-content/ContentPartImageRef.tsx b/src/apps/chat/components/message/fragments-content/BlockPartImageRef.tsx similarity index 98% rename from src/apps/chat/components/message/fragments-content/ContentPartImageRef.tsx rename to src/apps/chat/components/message/fragments-content/BlockPartImageRef.tsx index fce82c19f..438a307fe 100644 --- a/src/apps/chat/components/message/fragments-content/ContentPartImageRef.tsx +++ b/src/apps/chat/components/message/fragments-content/BlockPartImageRef.tsx @@ -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, diff --git a/src/apps/chat/components/message/fragments-content/ContentPartPlaceholder.tsx b/src/apps/chat/components/message/fragments-content/BlockPartPlaceholder.tsx similarity index 69% rename from src/apps/chat/components/message/fragments-content/ContentPartPlaceholder.tsx rename to src/apps/chat/components/message/fragments-content/BlockPartPlaceholder.tsx index 9108fe873..e83c01eb8 100644 --- a/src/apps/chat/components/message/fragments-content/ContentPartPlaceholder.tsx +++ b/src/apps/chat/components/message/fragments-content/BlockPartPlaceholder.tsx @@ -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 ( - ); // diff --git a/src/apps/chat/components/message/fragments-content/ContentPartText_AutoBlocks.tsx b/src/apps/chat/components/message/fragments-content/BlockPartText_AutoBlocks.tsx similarity index 98% rename from src/apps/chat/components/message/fragments-content/ContentPartText_AutoBlocks.tsx rename to src/apps/chat/components/message/fragments-content/BlockPartText_AutoBlocks.tsx index 1d475e17e..757866ef0 100644 --- a/src/apps/chat/components/message/fragments-content/ContentPartText_AutoBlocks.tsx +++ b/src/apps/chat/components/message/fragments-content/BlockPartText_AutoBlocks.tsx @@ -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, diff --git a/src/apps/chat/components/message/fragments-content/ContentFragments.tsx b/src/apps/chat/components/message/fragments-content/ContentFragments.tsx index 11ab88f31..6d385b74f 100644 --- a/src/apps/chat/components/message/fragments-content/ContentFragments.tsx +++ b/src/apps/chat/components/message/fragments-content/ContentFragments.tsx @@ -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 - {/* The overall message is empty - show an indication of it */} + {/* Empty Message Block - if empty */} {props.showEmptyNotice && ( - - - + )} {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 ( - ); case 'image_ref': return ( - - Unknown Content fragment: {fragment.part.pt} - + ); } }).filter(Boolean)} diff --git a/src/common/layout/overlays/store-overlays.ts b/src/common/layout/overlays/store-overlays.ts index 261fe6eb3..9d69de5ce 100644 --- a/src/common/layout/overlays/store-overlays.ts +++ b/src/common/layout/overlays/store-overlays.ts @@ -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' diff --git a/src/modules/blocks/AutoBlocksRenderer.tsx b/src/modules/blocks/AutoBlocksRenderer.tsx index f7a4cd0b7..0100c4eed 100644 --- a/src/modules/blocks/AutoBlocksRenderer.tsx +++ b/src/modules/blocks/AutoBlocksRenderer.tsx @@ -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) ? ( - ) : ( + // Keep in sync with ScaledMarkdownRenderer + {props.textRenderVariant === 'markdown' ? + : props.textRenderVariant === 'text' ? + : ('unknown textRenderVariant: ' + props.textRenderVariant)} + + ); +} diff --git a/src/modules/blocks/plaintext/RenderPlainChatText.tsx b/src/modules/blocks/plaintext/RenderPlainText.tsx similarity index 93% rename from src/modules/blocks/plaintext/RenderPlainChatText.tsx rename to src/modules/blocks/plaintext/RenderPlainText.tsx index b4da0de3b..3819b2b09 100644 --- a/src/modules/blocks/plaintext/RenderPlainChatText.tsx +++ b/src/modules/blocks/plaintext/RenderPlainText.tsx @@ -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);