Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion web/src/components/AssistantChat/AttachmentItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ function DragHandle(props: AttachmentDragHandleProps & { isFile?: boolean }) {
}

export function AttachmentItem(props: { dragHandleProps?: AttachmentDragHandleProps } = {}) {
const { name, status, previewUrl } = useThreadComposerAttachment() as ComposerAttachmentWithPreview
const { name, status, previewUrl, file } = useThreadComposerAttachment() as ComposerAttachmentWithPreview
const isParking = useComposerParking()
const isUploading = status.type === 'running'
const isError = status.type === 'incomplete'
Expand All @@ -120,6 +120,7 @@ export function AttachmentItem(props: { dragHandleProps?: AttachmentDragHandlePr
src={previewUrl}
fileName={name}
label={name}
fileSize={file?.size}
galleryId="composer-attachments"
buttonClassName={`group h-full w-full cursor-zoom-in overflow-hidden rounded-lg text-left ${props.dragHandleProps ? 'touch-none' : ''}`}
imageClassName="h-full w-full object-cover"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ function ImageAttachment(props: { attachment: AttachmentMetadata }) {
src={attachment.previewUrl ?? ''}
fileName={attachment.filename}
label={attachment.filename}
fileSize={attachment.size}
buttonClassName="relative overflow-hidden rounded-lg text-left cursor-zoom-in"
imageClassName="max-h-48 max-w-full object-contain"
caption={(
Expand Down
4 changes: 4 additions & 0 deletions web/src/components/AssistantChat/messages/ToolMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export function GeneratedImageCard(props: { block: GeneratedImageBlock }) {
const ctx = useHappyChatContext()
const { t } = useTranslation()
const [objectUrl, setObjectUrl] = useState<string | null>(null)
const [fileSize, setFileSize] = useState<number | undefined>(undefined)
const [error, setError] = useState<string | null>(null)
const [imageStyle, setImageStyle] = useState<CSSProperties | undefined>(undefined)
const [loadMedia, setLoadMedia] = useState(false)
Expand Down Expand Up @@ -102,6 +103,7 @@ export function GeneratedImageCard(props: { block: GeneratedImageBlock }) {
objectUrlRef.current = null
}
setObjectUrl(null)
setFileSize(undefined)
setImageStyle(undefined)
setError(null)

Expand All @@ -114,6 +116,7 @@ export function GeneratedImageCard(props: { block: GeneratedImageBlock }) {
}
objectUrlRef.current = nextObjectUrl
setObjectUrl(nextObjectUrl)
setFileSize(blob.size)
if (isImage) {
setImageStyle(undefined)
const probe = new Image()
Expand Down Expand Up @@ -172,6 +175,7 @@ export function GeneratedImageCard(props: { block: GeneratedImageBlock }) {
src={objectUrl}
fileName={props.block.fileName}
label={props.block.fileName}
fileSize={fileSize}
buttonClassName="block max-h-[min(28rem,60vh)] max-w-full cursor-zoom-in rounded-xl text-left"
imageClassName="max-h-[min(28rem,60vh)] max-w-full rounded-xl object-contain"
imageStyle={imageStyle}
Expand Down
51 changes: 48 additions & 3 deletions web/src/components/ImagePreview.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { ImagePreview } from './ImagePreview'
function renderGallery() {
render(
<>
<ImagePreview src="/first.png" fileName="first.png" label="First image" />
<ImagePreview src="/second.png" fileName="second.png" label="Second image" />
<ImagePreview src="/first.png" fileName="first.png" label="First image" fileSize={1536} />
<ImagePreview src="/second.png" fileName="second.png" label="Second image" fileSize={2048} />
</>
)
}
Expand All @@ -18,13 +18,58 @@ describe('ImagePreview gallery navigation', () => {
fireEvent.click(screen.getByRole('button', { name: /first image/i }))

const dialog = screen.getByRole('dialog', { name: 'First image' })
expect(within(dialog).getAllByRole('button').map((button) => button.getAttribute('title'))).toEqual([
'Close',
'Zoom out',
'Reset zoom',
'Zoom in',
'Previous image',
'Next image',
])
expect(within(dialog).getAllByRole('button')
.map((button) => button.querySelector('svg')?.getAttribute('class'))
.filter(Boolean)).toEqual([
'h-4 w-4',
'h-4 w-4',
'h-4 w-4',
'h-4 w-4',
'h-4 w-4',
])
expect(within(dialog).getByRole('button', { name: '100%' })).toHaveClass(
'flex',
'h-8',
'items-center',
'justify-center',
)
const image = within(dialog).getByRole('img', { name: 'First image' })
Object.defineProperty(image, 'naturalWidth', { configurable: true, value: 1200 })
Object.defineProperty(image, 'naturalHeight', { configurable: true, value: 700 })
fireEvent.load(image)
const desktopInfo = dialog.querySelector('[data-image-preview-info="desktop"]')
expect(desktopInfo).not.toBeNull()
expect(desktopInfo).toHaveClass('flex', 'flex-1', 'items-center', 'max-sm:hidden')
expect(desktopInfo).toHaveTextContent('first.png')
const mobileInfo = dialog.querySelector('[data-image-preview-info="mobile"]')
expect(mobileInfo).not.toBeNull()
expect(mobileInfo).toHaveClass('hidden', 'max-sm:flex', 'border-t')
const imageMetadata = within(desktopInfo as HTMLElement).getByText('1200 × 700 px · 1.5 KB')
expect(imageMetadata).toBeInTheDocument()
expect(imageMetadata).toHaveClass('shrink-0', 'text-sm', 'text-white/60')
expect(imageMetadata.parentElement).toHaveClass('items-center', 'text-sm')
expect(imageMetadata.parentElement).toHaveAttribute('data-image-preview-info', 'desktop')
expect(dialog).toHaveClass('fixed', 'inset-0', 'flex-col')
expect(within(dialog).getByRole('button', { name: 'Zoom out' }).parentElement).toHaveAttribute('data-image-preview-controls', '')
expect(within(dialog).getByRole('button', { name: 'Zoom out' }).parentElement).toHaveClass('max-sm:order-2', 'max-sm:gap-1')
expect(within(dialog).getByText('1 / 2')).toBeInTheDocument()
expect(within(dialog).getByRole('button', { name: 'Previous image' })).toBeDisabled()

fireEvent.click(within(dialog).getByRole('button', { name: 'Next image' }))

const nextDialog = screen.getByRole('dialog', { name: 'Second image' })
expect(within(nextDialog).getByText('second.png')).toBeInTheDocument()
const nextDesktopInfo = nextDialog.querySelector('[data-image-preview-info="desktop"]')
expect(nextDesktopInfo).not.toBeNull()
expect(nextDesktopInfo).toHaveTextContent('second.png')
expect(nextDesktopInfo).toHaveTextContent('2 KB')
expect(within(nextDialog).getByText('2 / 2')).toBeInTheDocument()
expect(within(nextDialog).getByRole('img', { name: 'Second image' })).toHaveAttribute('src', '/second.png')
expect(within(nextDialog).getByRole('button', { name: 'Next image' })).toBeDisabled()
Expand Down
Loading
Loading