Chat: option to regenerate images in-place

This commit is contained in:
Enrico Ros
2024-06-13 03:48:33 -07:00
parent 547d7eca59
commit 91654ca219
5 changed files with 59 additions and 18 deletions
@@ -624,7 +624,9 @@ export function ChatMessage(props: {
<ContentPartImageRef
key={'image-part-' + fragmentIndex}
imageRefPart={fragment.part}
fragmentIndex={fragmentIndex}
contentScaling={contentScaling}
onFragmentEdit={handleFragmentEdit}
/>
);
@@ -9,7 +9,7 @@ import { RenderImageURL } from '~/modules/blocks/RenderImageURL';
import { blocksRendererSx } from '~/modules/blocks/BlocksRenderer';
import { useDBAsset } from '~/modules/dblobs/dblobs.hooks';
import type { DMessageImagePart } from '~/common/stores/chat/chat.message';
import type { DMessageContentFragment, DMessageImageRefPart } from '~/common/stores/chat/chat.message';
import { ContentScaling, themeScalingMap } from '~/common/app.theme';
import { showImageDataRefInNewTab } from '~/common/stores/chat/chat.dblobs';
@@ -20,6 +20,7 @@ function ContentPartImageDBlob(props: {
imageAltText?: string,
imageWidth?: number,
imageHeight?: number,
onImageReplace: (newImageFragment: DMessageContentFragment) => void,
onOpenInNewTab: () => void
scaledImageSx?: SxProps,
}) {
@@ -27,6 +28,27 @@ function ContentPartImageDBlob(props: {
// external state from the DB
const [imageItem] = useDBAsset<DBlobImageAsset>(props.dataRefDBlobAssetId);
// handlers
const { label: imageItemLabel, origin: imageItemOrigin, metadata: imageItemMetadata } = imageItem || {};
const recreationPrompt = ((imageItemOrigin?.ot === 'generated') ? imageItemOrigin.prompt : undefined) || imageItemLabel || props.imageAltText;
const recreationWidth = imageItemMetadata?.width || props.imageWidth;
const recreationHeight = imageItemMetadata?.height || props.imageHeight;
const handleImageRegenerate = React.useCallback(() => {
// TODO: ... t2iGenerateImagesOrThrow()
console.log('ContentPartImageDBlob: handleImageRegenerate: notImplemented', imageItem, recreationPrompt, recreationWidth, recreationHeight);
// props.onImageReplace( createImageContentFragment()
// {
// type: 'image',
// dataRef: { reftype: 'dblob', dblobAssetId: props.dataRefDBlobAssetId },
// altText: props.imageAltText,
// width: props.imageWidth,
// height: props.imageHeight,
// });
}, [imageItem, recreationPrompt, recreationWidth, recreationHeight]);
// memo the description and overlay text
const { dataUrl, altText, overlayText } = React.useMemo(() => {
if (!imageItem?.data)
@@ -78,6 +100,7 @@ function ContentPartImageDBlob(props: {
infoText={altText}
description={overlayText}
onOpenInNewTab={props.onOpenInNewTab}
onImageRegenerate={(!!recreationPrompt) ? handleImageRegenerate : undefined}
scaledImageSx={props.scaledImageSx}
/>
);
@@ -85,16 +108,25 @@ function ContentPartImageDBlob(props: {
export function ContentPartImageRef(props: {
imageRefPart: DMessageImagePart,
imageRefPart: DMessageImageRefPart,
fragmentIndex: number,
contentScaling: ContentScaling,
onFragmentEdit?: (fragmentIndex: number, newFragment: DMessageContentFragment) => void,
}) {
// derived state
const imagePart = props.imageRefPart;
const { dataRef } = imagePart;
const { fragmentIndex, imageRefPart, onFragmentEdit } = props;
const { dataRef } = imageRefPart;
// event handlers
const handleOpenInNewTab = React.useCallback(() => showImageDataRefInNewTab(dataRef), [dataRef]);
const handleImageReplace = React.useCallback((newImageFragment: DMessageContentFragment) => {
onFragmentEdit?.(fragmentIndex, newImageFragment);
}, [onFragmentEdit, fragmentIndex]);
const handleOpenInNewTab = React.useCallback(() => {
void showImageDataRefInNewTab(dataRef); // fire/forget
}, [dataRef]);
// memo the scaled image style
const scaledImageSx = React.useMemo((): SxProps => (
@@ -112,16 +144,17 @@ export function ContentPartImageRef(props: {
<ContentPartImageDBlob
dataRefDBlobAssetId={dataRef.dblobAssetId}
dataRefMimeType={dataRef.mimeType}
imageAltText={imagePart.altText}
imageWidth={imagePart.width}
imageHeight={imagePart.height}
imageAltText={imageRefPart.altText}
imageWidth={imageRefPart.width}
imageHeight={imageRefPart.height}
onImageReplace={handleImageReplace}
onOpenInNewTab={handleOpenInNewTab}
scaledImageSx={scaledImageSx}
/>
) : dataRef.reftype === 'url' ? (
<RenderImageURL
imageURL={dataRef.url}
infoText={imagePart.altText}
infoText={imageRefPart.altText}
scaledImageSx={scaledImageSx}
/>
) : (
+4 -4
View File
@@ -52,14 +52,14 @@ export type DMessageFragment =
// expected a list of one or more per message, of similar or different types
export type DMessageContentFragment = {
ft: 'content',
part: DMessageTextPart | DMessageImagePart | DMessageToolCallPart | DMessageToolResponsePart;
part: DMessageTextPart | DMessageImageRefPart | DMessageToolCallPart | DMessageToolResponsePart;
}
// displayed at the bottom of the message, zero or more
export type DMessageAttachmentFragment = {
ft: 'attachment',
title: string;
part: DMessageTextPart | DMessageImagePart;
part: DMessageTextPart | DMessageImageRefPart;
}
// up to 1 per message, containing the Rays and Merges that would be used to restore the Beam state - could be volatile (omitted at save)
@@ -74,7 +74,7 @@ export type DMessageAttachmentFragment = {
// - small and efficient (larger objects need to only be referred to)
export type DMessageTextPart = { pt: 'text', text: string };
export type DMessageImagePart = { pt: 'image_ref', dataRef: DMessageDataRef, altText?: string, width?: number, height?: number };
export type DMessageImageRefPart = { pt: 'image_ref', dataRef: DMessageDataRef, altText?: string, width?: number, height?: number };
type DMessageToolCallPart = { pt: 'tool_call', function: string, args: Record<string, any> };
type DMessageToolResponsePart = { pt: 'tool_response', function: string, response: Record<string, any> };
// type DMessageErrorPart = { pt: 'error', error: string };
@@ -186,7 +186,7 @@ function createDMessageTextPart(text: string): DMessageTextPart {
return { pt: 'text', text };
}
function createDMessageImagePart(dataRef: DMessageDataRef, altText?: string, width?: number, height?: number): DMessageImagePart {
function createDMessageImagePart(dataRef: DMessageDataRef, altText?: string, width?: number, height?: number): DMessageImageRefPart {
return { pt: 'image_ref', dataRef, altText, width, height };
}
+1 -1
View File
@@ -194,7 +194,7 @@ export const BlocksRenderer = React.forwardRef<HTMLDivElement, BlocksRendererPro
: block.type === 'codeb'
? <RenderCodeMemoOrNot key={'code-' + index} codeBlock={block} fitScreen={props.fitScreen} initialShowHTML={props.showUnsafeHtml} noCopyButton={props.specialDiagramMode} optimizeLightweight={!optimizeSubBlockWithMemo} sx={scaledCodeSx} />
: block.type === 'imageb'
? <RenderImageURL key={'image-' + index} imageURL={block.url} infoText={block.alt} onRunAgain={/*props.isBottom ? props.onImageRegenerate :*/ undefined} scaledImageSx={scaledImageSx} />
? <RenderImageURL key={'image-' + index} imageURL={block.url} infoText={block.alt} onImageRegenerate={/*props.isBottom ? props.onImageRegenerate :*/ undefined} scaledImageSx={scaledImageSx} />
: block.type === 'diffb'
? <RenderTextDiff key={'text-diff-' + index} diffBlock={block} sx={scaledTypographySx} />
: (props.renderTextAsMarkdown && !fromSystem && !isUserCommand)
+10 -4
View File
@@ -49,6 +49,7 @@ const prodiaUrlRegex = /^(https?:\/\/images\.prodia\.\S+)$/i;
/**
* Legacy heuristic for detecting images from "images.prodia." URLs.
* @deprecated Remove in mid 2024
*/
export function heuristicLegacyImageBlocks(fullText: string): ImageBlock[] | null {
@@ -75,7 +76,7 @@ export const RenderImageURL = (props: {
description?: React.ReactNode,
infoText?: string,
onOpenInNewTab?: (e: React.MouseEvent) => void,
onRunAgain?: (e: React.MouseEvent) => void,
onImageRegenerate?: (e: React.MouseEvent) => void,
scaledImageSx?: SxProps,
}) => {
@@ -179,10 +180,15 @@ export const RenderImageURL = (props: {
)}
{/* (overlay) Image Buttons */}
<Box className='overlay-buttons' sx={{ ...overlayButtonsSx, pt: 0.5, px: 0.5, gap: 0.5 }}>
{!!props.onRunAgain && (
<Box className='overlay-buttons' sx={{
...overlayButtonsSx,
p: 0.5,
display: 'grid',
gap: 0.5,
}}>
{!!props.onImageRegenerate && (
<GoodTooltip title='Draw again'>
<OverlayButton variant='outlined' onClick={props.onRunAgain}>
<OverlayButton variant='outlined' onClick={props.onImageRegenerate} sx={{ gridRow: '2', gridColumn: '2' }}>
<ReplayIcon />
</OverlayButton>
</GoodTooltip>