Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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 @@ -38,7 +38,7 @@ function RemoveIcon() {
}

export function AttachmentItem() {
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 @@ -50,6 +50,7 @@ export function AttachmentItem() {
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"
imageClassName="h-full w-full object-cover transition-opacity group-hover:opacity-85"
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