'use client' import { type ComponentProps, useState } from 'react' import { Dialog, DialogContent } from '@/components/ui/dialog' import { Download } from '@/lib/icons' import { cn } from '@/lib/utils' import { notify, notifyError } from '@/store/notifications' function imageFilename(src?: string): string { if (!src) { return 'image' } try { const { pathname } = new URL(src, window.location.href) return pathname.split('/').filter(Boolean).pop() || 'image' } catch { return src.split(/[\\/]/).filter(Boolean).pop() || 'image' } } function isMissingIpcHandler(error: unknown): boolean { const message = error instanceof Error ? error.message : typeof error === 'string' ? error : '' return message.includes("No handler registered for 'hermes:saveImageFromUrl'") } async function startBrowserDownload(src: string) { const response = await fetch(src) if (!response.ok) { throw new Error(`Could not fetch image: ${response.status}`) } const blobUrl = URL.createObjectURL(await response.blob()) const link = document.createElement('a') link.href = blobUrl link.download = imageFilename(src) link.rel = 'noopener noreferrer' document.body.appendChild(link) link.click() link.remove() window.setTimeout(() => URL.revokeObjectURL(blobUrl), 30_000) } export interface ZoomableImageProps extends ComponentProps<'img'> { containerClassName?: string slot?: string } export function ZoomableImage({ className, containerClassName, src, alt, slot, ...props }: ZoomableImageProps) { const [saving, setSaving] = useState(false) const [lightboxOpen, setLightboxOpen] = useState(false) const canOpen = Boolean(src) async function handleDownload() { if (!src || saving) { return } setSaving(true) try { if (window.hermesDesktop?.saveImageFromUrl) { const saved = await window.hermesDesktop.saveImageFromUrl(src) if (saved) { notify({ kind: 'success', title: 'Image saved', message: imageFilename(src) }) } return } await startBrowserDownload(src) } catch (error) { if (isMissingIpcHandler(error)) { try { await startBrowserDownload(src) notify({ kind: 'info', title: 'Download started', message: 'Restart Hermes Desktop to use Save Image.' }) } catch (fallbackError) { notifyError(fallbackError, 'Restart Hermes Desktop to save images') } return } notifyError(error, 'Image download failed') } finally { setSaving(false) } } const lightbox = src ? ( setLightboxOpen(false)} src={src} /> ) : null return ( <> canOpen && setLightboxOpen(true)} title={canOpen ? 'Open image' : undefined} type="button" > {src && } {lightbox} > ) } function ImageActionButton({ onClick, saving, variant }: { onClick: () => void saving: boolean variant: 'inline' | 'lightbox' }) { return ( { event.stopPropagation() void onClick() }} title={saving ? 'Saving image' : 'Download image'} type="button" > ) }