From fc8c984cd4baa9b908b93e9f0b8bff9dbfb232ba Mon Sep 17 00:00:00 2001 From: Enrico Ros Date: Thu, 26 Sep 2024 22:16:08 -0700 Subject: [PATCH] Attachments: view images --- .../llmattachments/LLMAttachmentMenu.tsx | 17 ++++---- .../llmattachments/LLMAttachmentsList.tsx | 20 +++++++++- .../fragments-content/BlockPartImageRef.tsx | 8 ++-- .../fragments-content/ImageRefPartModal.tsx | 39 +++++++++++++++++++ 4 files changed, 70 insertions(+), 14 deletions(-) create mode 100644 src/apps/chat/components/message/fragments-content/ImageRefPartModal.tsx diff --git a/src/apps/chat/components/composer/llmattachments/LLMAttachmentMenu.tsx b/src/apps/chat/components/composer/llmattachments/LLMAttachmentMenu.tsx index 995c05bdb..e72518191 100644 --- a/src/apps/chat/components/composer/llmattachments/LLMAttachmentMenu.tsx +++ b/src/apps/chat/components/composer/llmattachments/LLMAttachmentMenu.tsx @@ -15,10 +15,8 @@ import ReadMoreIcon from '@mui/icons-material/ReadMore'; import VerticalAlignBottomIcon from '@mui/icons-material/VerticalAlignBottom'; import VisibilityIcon from '@mui/icons-material/Visibility'; -import { showImageDataRefInNewTab } from '~/modules/blocks/image/RenderImageRefDBlob'; - import { CloseableMenu } from '~/common/components/CloseableMenu'; -import { DMessageAttachmentFragment, DMessageDataRef, isDocPart, isImageRefPart } from '~/common/stores/chat/chat.fragments'; +import { DMessageAttachmentFragment, DMessageImageRefPart, isDocPart, isImageRefPart } from '~/common/stores/chat/chat.fragments'; import { LiveFileIcon } from '~/common/livefile/liveFile.icons'; import { copyToClipboard } from '~/common/util/clipboardUtils'; import { showImageDataURLInNewTab } from '~/common/util/imageUtils'; @@ -50,8 +48,9 @@ export function LLMAttachmentMenu(props: { menuAnchor: HTMLAnchorElement, isPositionFirst: boolean, isPositionLast: boolean, - onDraftAction: (attachmentDraftId: AttachmentDraftId, actionId: LLMAttachmentDraftsAction) => void, onClose: () => void, + onDraftAction: (attachmentDraftId: AttachmentDraftId, actionId: LLMAttachmentDraftsAction) => void, + onViewImageRefPart: (imageRefPart: DMessageImageRefPart) => void }) { // state @@ -97,7 +96,7 @@ export function LLMAttachmentMenu(props: { // operations - const { attachmentDraftsStoreApi, onDraftAction, onClose } = props; + const { attachmentDraftsStoreApi, onClose, onDraftAction, onViewImageRefPart } = props; const handleMoveUp = React.useCallback(() => { attachmentDraftsStoreApi.getState().moveAttachmentDraft(draftId, -1); @@ -128,11 +127,11 @@ export function LLMAttachmentMenu(props: { copyToClipboard(text, 'Attachment Text'); }, []); - const handleViewMessageDataRef = React.useCallback((event: React.MouseEvent, dataRef: DMessageDataRef) => { + const handleViewImageRefPart = React.useCallback((event: React.MouseEvent, imageRefPart: DMessageImageRefPart) => { event.preventDefault(); event.stopPropagation(); - void showImageDataRefInNewTab(dataRef); // fire/forget - }, []); + onViewImageRefPart(imageRefPart); + }, [onViewImageRefPart]); const canHaveDetails = !!draftInput && !isConverting; @@ -327,7 +326,7 @@ export function LLMAttachmentMenu(props: { return ( }> {mime /*.replace('image/', 'img: ')*/} · {resolution} · {part.dataRef.reftype === 'dblob' ? (part.dataRef.bytesSize?.toLocaleString() || 'no size') : '(remote)'} ·  - } onClick={(event) => handleViewMessageDataRef(event, part.dataRef)}> + } onClick={(event) => handleViewImageRefPart(event, part)}> see {isOutputMultiple && } onClick={(event) => handleDeleteOutputFragment(event, index)}> diff --git a/src/apps/chat/components/composer/llmattachments/LLMAttachmentsList.tsx b/src/apps/chat/components/composer/llmattachments/LLMAttachmentsList.tsx index cb0c0c128..9ff0fd4f7 100644 --- a/src/apps/chat/components/composer/llmattachments/LLMAttachmentsList.tsx +++ b/src/apps/chat/components/composer/llmattachments/LLMAttachmentsList.tsx @@ -15,6 +15,9 @@ import { useOverlayComponents } from '~/common/layout/overlays/useOverlayCompone import type { AttachmentDraftId } from '~/common/attachment-drafts/attachment.types'; import type { AttachmentDraftsStoreApi } from '~/common/attachment-drafts/store-attachment-drafts-slice'; +import type { DMessageImageRefPart } from '~/common/stores/chat/chat.fragments'; + +import { ImageRefPartModal } from '../../message/fragments-content/ImageRefPartModal'; import type { LLMAttachmentDraft } from './useLLMAttachmentDrafts'; import { LLMAttachmentButtonMemo } from './LLMAttachmentButton'; @@ -40,6 +43,7 @@ export function LLMAttachmentsList(props: { const { showPromisedOverlay } = useOverlayComponents(); const [draftMenu, setDraftMenu] = React.useState<{ anchor: HTMLAnchorElement, attachmentDraftId: AttachmentDraftId } | null>(null); const [overallMenuAnchor, setOverallMenuAnchor] = React.useState(null); + const [viewerImageRefPart, setViewerImageRefPart] = React.useState(null); // derived state @@ -106,6 +110,14 @@ export function LLMAttachmentsList(props: { onAttachmentDraftsAction(attachmentDraftId, actionId); }, [handleDraftMenuHide, onAttachmentDraftsAction]); + const handleViewImageRefPart = React.useCallback((imageRefPart: DMessageImageRefPart) => { + setViewerImageRefPart(imageRefPart); + }, []); + + const handleCloseImageViewer = React.useCallback(() => { + setViewerImageRefPart(null); + }, []); + // no components without attachments if (!hasAttachments) @@ -153,7 +165,10 @@ export function LLMAttachmentsList(props: { - {/* LLM Attachment Draft Menu */} + {/* Optional Modal to view the Image */} + {viewerImageRefPart && } + + {/* Single LLM Attachment Draft Menu */} {!!itemMenuAnchor && !!itemMenuAttachmentDraft && !!props.attachmentDraftsStoreApi && ( )} diff --git a/src/apps/chat/components/message/fragments-content/BlockPartImageRef.tsx b/src/apps/chat/components/message/fragments-content/BlockPartImageRef.tsx index 438a307fe..539577bee 100644 --- a/src/apps/chat/components/message/fragments-content/BlockPartImageRef.tsx +++ b/src/apps/chat/components/message/fragments-content/BlockPartImageRef.tsx @@ -13,7 +13,7 @@ import { ContentScaling, themeScalingMap } from '~/common/app.theme'; export function BlockPartImageRef(props: { imageRefPart: DMessageImageRefPart, - fragmentId: DMessageFragmentId, + fragmentId?: DMessageFragmentId, contentScaling: ContentScaling, onFragmentDelete?: (fragmentId: DMessageFragmentId) => void, onFragmentReplace?: (fragmentId: DMessageFragmentId, newFragment: DMessageContentFragment) => void, @@ -25,11 +25,13 @@ export function BlockPartImageRef(props: { // event handlers const handleDeleteFragment = React.useCallback(() => { - onFragmentDelete?.(fragmentId); + if (fragmentId && onFragmentDelete) + onFragmentDelete(fragmentId); }, [fragmentId, onFragmentDelete]); const handleReplaceFragment = React.useCallback((newImageFragment: DMessageContentFragment) => { - onFragmentReplace?.(fragmentId, newImageFragment); + if (fragmentId && onFragmentReplace) + onFragmentReplace(fragmentId, newImageFragment); }, [fragmentId, onFragmentReplace]); const handleOpenInNewTab = React.useCallback(() => { diff --git a/src/apps/chat/components/message/fragments-content/ImageRefPartModal.tsx b/src/apps/chat/components/message/fragments-content/ImageRefPartModal.tsx new file mode 100644 index 000000000..4f006c9c8 --- /dev/null +++ b/src/apps/chat/components/message/fragments-content/ImageRefPartModal.tsx @@ -0,0 +1,39 @@ +import * as React from 'react'; + +import type { SxProps } from '@mui/joy/styles/types'; +import { Box } from '@mui/joy'; + +import type { DMessageImageRefPart } from '~/common/stores/chat/chat.fragments'; +import { GoodModal } from '~/common/components/modals/GoodModal'; + +import { BlockPartImageRef } from './BlockPartImageRef'; + + +const imageViewerModalSx: SxProps = { + maxWidth: '90vw', +}; + +const imageViewerContainerSx: SxProps = { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + maxHeight: '80vh', + overflow: 'auto', +}; + + +export function ImageRefPartModal(props: { imageRefPart: DMessageImageRefPart, onClose: () => void }) { + return ( + + + + + + ); +} \ No newline at end of file