mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
MP: Image parts support deletion
This commit is contained in:
@@ -127,6 +127,10 @@ export function ChatMessageList(props: {
|
||||
props.conversationHandler?.messagesDelete([messageId]);
|
||||
}, [props.conversationHandler]);
|
||||
|
||||
const handleMessageDeleteFragment = React.useCallback((messageId: DMessageId, fragmentId: DMessageFragmentId) => {
|
||||
props.conversationHandler?.messageFragmentDelete(messageId, fragmentId, false, true);
|
||||
}, [props.conversationHandler]);
|
||||
|
||||
const handleMessageReplaceFragment = React.useCallback((messageId: DMessageId, fragmentId: DMessageFragmentId, newFragment: DMessageFragment) => {
|
||||
props.conversationHandler?.messageFragmentReplace(messageId, fragmentId, newFragment, false);
|
||||
}, [props.conversationHandler]);
|
||||
@@ -282,6 +286,7 @@ export function ChatMessageList(props: {
|
||||
onMessageBeam={handleMessageBeam}
|
||||
onMessageBranch={handleMessageBranch}
|
||||
onMessageDelete={handleMessageDelete}
|
||||
onMessageFragmentDelete={handleMessageDeleteFragment}
|
||||
onMessageFragmentReplace={handleMessageReplaceFragment}
|
||||
onMessageToggleUserFlag={handleMessageToggleUserFlag}
|
||||
onMessageTruncate={handleMessageTruncate}
|
||||
|
||||
@@ -175,6 +175,7 @@ export function ChatMessage(props: {
|
||||
onMessageBeam?: (messageId: string) => Promise<void>,
|
||||
onMessageBranch?: (messageId: string) => void,
|
||||
onMessageDelete?: (messageId: string) => 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,
|
||||
@@ -239,7 +240,12 @@ export function ChatMessage(props: {
|
||||
// const textDiffs = useSanityTextDiffs(messageText, props.diffPreviousText, showDiff);
|
||||
|
||||
|
||||
const { onMessageFragmentReplace } = props;
|
||||
const { onMessageFragmentDelete, onMessageFragmentReplace } = props;
|
||||
|
||||
const handleFragmentDelete = React.useCallback((fragmentId: DMessageFragmentId) => {
|
||||
setIsEditing(false);
|
||||
onMessageFragmentDelete?.(messageId, fragmentId);
|
||||
}, [messageId, onMessageFragmentDelete]);
|
||||
|
||||
const handleFragmentReplace = React.useCallback((fragmentId: DMessageFragmentId, newContent: DMessageContentFragment) => {
|
||||
setIsEditing(false);
|
||||
@@ -616,6 +622,7 @@ export function ChatMessage(props: {
|
||||
imageRefPart={fragment.part}
|
||||
fragmentId={fragment.fId}
|
||||
contentScaling={contentScaling}
|
||||
onFragmentDelete={messageFragments.length > 1 ? handleFragmentDelete : undefined}
|
||||
onFragmentReplace={handleFragmentReplace}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -40,7 +40,8 @@ function ContentPartImageRefDBlob(props: {
|
||||
imageWidth?: number,
|
||||
imageHeight?: number,
|
||||
onOpenInNewTab: () => void
|
||||
onReplaceFragment: (newFragment: DMessageContentFragment) => void,
|
||||
onDeleteFragment?: () => void,
|
||||
onReplaceFragment?: (newFragment: DMessageContentFragment) => void,
|
||||
scaledImageSx?: SxProps,
|
||||
}) {
|
||||
|
||||
@@ -58,10 +59,10 @@ function ContentPartImageRefDBlob(props: {
|
||||
enabled: false,
|
||||
queryKey: ['regen-image-asset', props.dataRefDBlobAssetId, recreationPrompt],
|
||||
queryFn: async ({ signal }) => {
|
||||
if (signal?.aborted || !recreationPrompt) return;
|
||||
if (signal?.aborted || !recreationPrompt || !props.onReplaceFragment) return;
|
||||
const newImageFragments = await t2iGenerateImageContentFragments(null, recreationPrompt, 1, 'global', 'app-chat');
|
||||
if (newImageFragments.length === 1)
|
||||
props.onReplaceFragment(newImageFragments[0]);
|
||||
props.onReplaceFragment?.(newImageFragments[0]);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -116,7 +117,8 @@ function ContentPartImageRefDBlob(props: {
|
||||
infoText={altText}
|
||||
description={overlayText}
|
||||
onOpenInNewTab={props.onOpenInNewTab}
|
||||
onImageRegenerate={(!!recreationPrompt && !isRegenerating) ? handleImageRegenerate : undefined}
|
||||
onImageDelete={props.onDeleteFragment}
|
||||
onImageRegenerate={(!!recreationPrompt && !isRegenerating && !!props.onReplaceFragment) ? handleImageRegenerate : undefined}
|
||||
className={isRegenerating ? 'agi-border-4' : undefined}
|
||||
scaledImageSx={props.scaledImageSx}
|
||||
/>
|
||||
@@ -139,14 +141,19 @@ export function ContentPartImageRef(props: {
|
||||
imageRefPart: DMessageImageRefPart,
|
||||
fragmentId: DMessageFragmentId,
|
||||
contentScaling: ContentScaling,
|
||||
onFragmentDelete?: (fragmentId: DMessageFragmentId) => void,
|
||||
onFragmentReplace?: (fragmentId: DMessageFragmentId, newFragment: DMessageContentFragment) => void,
|
||||
}) {
|
||||
|
||||
// derived state
|
||||
const { fragmentId, imageRefPart, onFragmentReplace } = props;
|
||||
const { fragmentId, imageRefPart, onFragmentDelete, onFragmentReplace } = props;
|
||||
const { dataRef } = imageRefPart;
|
||||
|
||||
// event handlers
|
||||
const handleDeleteFragment = React.useCallback(() => {
|
||||
onFragmentDelete?.(fragmentId);
|
||||
}, [fragmentId, onFragmentDelete]);
|
||||
|
||||
const handleReplaceFragment = React.useCallback((newImageFragment: DMessageContentFragment) => {
|
||||
onFragmentReplace?.(fragmentId, newImageFragment);
|
||||
}, [fragmentId, onFragmentReplace]);
|
||||
@@ -174,7 +181,8 @@ export function ContentPartImageRef(props: {
|
||||
imageWidth={imageRefPart.width}
|
||||
imageHeight={imageRefPart.height}
|
||||
onOpenInNewTab={handleOpenInNewTab}
|
||||
onReplaceFragment={handleReplaceFragment}
|
||||
onDeleteFragment={onFragmentDelete ? handleDeleteFragment : undefined}
|
||||
onReplaceFragment={onFragmentReplace ? handleReplaceFragment : undefined}
|
||||
scaledImageSx={scaledImageSx}
|
||||
/>
|
||||
) : dataRef.reftype === 'url' ? (
|
||||
|
||||
@@ -3,6 +3,8 @@ import * as React from 'react';
|
||||
import type { SxProps } from '@mui/joy/styles/types';
|
||||
import { Alert, Box, IconButton, Sheet } from '@mui/joy';
|
||||
import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
|
||||
import DeleteForeverIcon from '@mui/icons-material/DeleteForever';
|
||||
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
|
||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
|
||||
import ReplayIcon from '@mui/icons-material/Replay';
|
||||
@@ -76,6 +78,7 @@ export const RenderImageURL = (props: {
|
||||
description?: React.ReactNode,
|
||||
infoText?: string,
|
||||
onOpenInNewTab?: (e: React.MouseEvent) => void,
|
||||
onImageDelete?: () => void,
|
||||
onImageRegenerate?: () => void,
|
||||
scaledImageSx?: SxProps,
|
||||
className?: string,
|
||||
@@ -84,6 +87,7 @@ export const RenderImageURL = (props: {
|
||||
// state
|
||||
const [infoOpen, setInfoOpen] = React.useState(false);
|
||||
const [loadingTimeout, setLoadingTimeout] = React.useState(false);
|
||||
const [deleteArmed, setDeleteArmed] = React.useState(false);
|
||||
const [regenArmed, setRegenArmed] = React.useState(false);
|
||||
const [showDalleAlert, setShowDalleAlert] = React.useState(true);
|
||||
|
||||
@@ -94,24 +98,33 @@ export const RenderImageURL = (props: {
|
||||
}, []);
|
||||
|
||||
// handlers
|
||||
const { onImageRegenerate, onOpenInNewTab } = props;
|
||||
const { onImageDelete, onImageRegenerate, onOpenInNewTab } = props;
|
||||
|
||||
const handleToggleInfoOpen = React.useCallback(() => {
|
||||
setDeleteArmed(false);
|
||||
setRegenArmed(false);
|
||||
setInfoOpen(open => !open);
|
||||
}, []);
|
||||
|
||||
const handleOpenInNewTab = React.useCallback((e: React.MouseEvent) => {
|
||||
setDeleteArmed(false);
|
||||
setRegenArmed(false);
|
||||
onOpenInNewTab?.(e);
|
||||
}, [onOpenInNewTab]);
|
||||
|
||||
const handleToggleDeleteArmed = React.useCallback(() => {
|
||||
setRegenArmed(false);
|
||||
setDeleteArmed(armed => !armed);
|
||||
}, []);
|
||||
|
||||
const handleImageRegenerate = React.useCallback(() => {
|
||||
setDeleteArmed(false);
|
||||
setRegenArmed(false);
|
||||
onImageRegenerate?.();
|
||||
}, [onImageRegenerate]);
|
||||
|
||||
const handleToggleRegenArmed = React.useCallback(() => {
|
||||
setDeleteArmed(false);
|
||||
setRegenArmed(armed => !armed);
|
||||
}, []);
|
||||
|
||||
@@ -235,16 +248,35 @@ export const RenderImageURL = (props: {
|
||||
</GoodTooltip>
|
||||
)}
|
||||
|
||||
{/* Regenerate [armed, arming] buttons */}
|
||||
{regenArmed && (
|
||||
<GoodTooltip title='Confirm Regeneration'>
|
||||
<OverlayButton variant='soft' color='success' onClick={handleImageRegenerate} sx={{ gridRow: '2', gridColumn: '1' }}>
|
||||
<ReplayIcon />
|
||||
|
||||
{/* Deletion */}
|
||||
|
||||
{!!onImageDelete && !regenArmed && (
|
||||
<GoodTooltip title={deleteArmed ? 'Cancel Deletion' : 'Delete Image'}>
|
||||
<OverlayButton variant={deleteArmed ? 'solid' : 'soft'} onClick={handleToggleDeleteArmed} sx={{ gridRow: '2', gridColumn: '1' }}>
|
||||
{deleteArmed ? <CloseRoundedIcon /> : <DeleteOutlineIcon />}
|
||||
</OverlayButton>
|
||||
</GoodTooltip>
|
||||
)}
|
||||
|
||||
{!!onImageRegenerate && (
|
||||
{deleteArmed && !regenArmed && (
|
||||
<GoodTooltip title='Confirm Deletion'>
|
||||
<OverlayButton variant='soft' color='danger' onClick={onImageDelete} sx={{ gridRow: '2', gridColumn: '2' }}>
|
||||
<DeleteForeverIcon sx={{ color: 'danger.solidBg' }} />
|
||||
</OverlayButton>
|
||||
</GoodTooltip>
|
||||
)}
|
||||
|
||||
{/* Regenerate [armed, arming] buttons */}
|
||||
{regenArmed && !deleteArmed && (
|
||||
<GoodTooltip title='Confirm Regeneration'>
|
||||
<OverlayButton variant='soft' color='success' onClick={handleImageRegenerate} sx={{ gridRow: '2', gridColumn: '1' }}>
|
||||
<ReplayIcon sx={{ color: 'success.solidBg' }} />
|
||||
</OverlayButton>
|
||||
</GoodTooltip>
|
||||
)}
|
||||
|
||||
{!!onImageRegenerate && !deleteArmed && (
|
||||
<GoodTooltip title={regenArmed ? 'Cancel Regeneration' : 'Draw again with the current drawing configuration'}>
|
||||
<OverlayButton variant={regenArmed ? 'solid' : 'soft'} onClick={handleToggleRegenArmed} sx={{ gridRow: '2', gridColumn: '2' }}>
|
||||
{regenArmed
|
||||
|
||||
Reference in New Issue
Block a user