diff --git a/src/apps/chat/components/message/ChatMessage.tsx b/src/apps/chat/components/message/ChatMessage.tsx
index c08213d18..eb7e0d21a 100644
--- a/src/apps/chat/components/message/ChatMessage.tsx
+++ b/src/apps/chat/components/message/ChatMessage.tsx
@@ -624,7 +624,9 @@ export function ChatMessage(props: {
);
diff --git a/src/apps/chat/components/message/ContentPartImageRef.tsx b/src/apps/chat/components/message/ContentPartImageRef.tsx
index 09977030e..53501838b 100644
--- a/src/apps/chat/components/message/ContentPartImageRef.tsx
+++ b/src/apps/chat/components/message/ContentPartImageRef.tsx
@@ -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(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: {
) : dataRef.reftype === 'url' ? (
) : (
diff --git a/src/common/stores/chat/chat.message.ts b/src/common/stores/chat/chat.message.ts
index f9ab1af3c..934c9ad30 100644
--- a/src/common/stores/chat/chat.message.ts
+++ b/src/common/stores/chat/chat.message.ts
@@ -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 };
type DMessageToolResponsePart = { pt: 'tool_response', function: string, response: Record };
// 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 };
}
diff --git a/src/modules/blocks/BlocksRenderer.tsx b/src/modules/blocks/BlocksRenderer.tsx
index 3b6985cf2..c6c80d04f 100644
--- a/src/modules/blocks/BlocksRenderer.tsx
+++ b/src/modules/blocks/BlocksRenderer.tsx
@@ -194,7 +194,7 @@ export const BlocksRenderer = React.forwardRef
: block.type === 'imageb'
- ?
+ ?
: block.type === 'diffb'
?
: (props.renderTextAsMarkdown && !fromSystem && !isUserCommand)
diff --git a/src/modules/blocks/RenderImageURL.tsx b/src/modules/blocks/RenderImageURL.tsx
index 1b7b9a69a..81d9de220 100644
--- a/src/modules/blocks/RenderImageURL.tsx
+++ b/src/modules/blocks/RenderImageURL.tsx
@@ -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 */}
-
- {!!props.onRunAgain && (
+
+ {!!props.onImageRegenerate && (
-
+