codexmobile-relay / client /src /components /MessageImages.jsx
Codex
deploy: CodexMobile Relay
90f0300
Raw
History Blame Contribute Delete
3.11 kB
import { useEffect, useRef, useState } from 'react';
import { RefreshCw, X } from 'lucide-react';
import { imageUrlWithRetry } from '../app-core-utils.js';
export function GeneratedImage({ part, onPreviewImage }) {
const [loadState, setLoadState] = useState('loading');
const [retryKey, setRetryKey] = useState(0);
const src = imageUrlWithRetry(part.url, retryKey);
return (
<button
type="button"
className={`message-image-link ${loadState === 'failed' ? 'is-failed' : ''}`}
onClick={() => (loadState === 'failed' ? setRetryKey(Date.now()) : onPreviewImage(part))}
aria-label={loadState === 'failed' ? '重新加载图片' : '预览图片'}
>
<img
className="message-image"
src={src}
alt={part.alt}
loading="eager"
decoding="async"
onLoad={() => setLoadState('loaded')}
onError={() => setLoadState('failed')}
/>
{loadState === 'failed' ? (
<span className="image-error">
图片加载失败
<span>点击重试</span>
</span>
) : null}
</button>
);
}
export function ImagePreviewModal({ image, onClose }) {
const [loadState, setLoadState] = useState('loading');
const [retryKey, setRetryKey] = useState(0);
const closeButtonRef = useRef(null);
const previousFocusRef = useRef(null);
useEffect(() => {
setLoadState('loading');
setRetryKey(0);
}, [image?.url]);
useEffect(() => {
if (!image) {
return undefined;
}
previousFocusRef.current = document.activeElement;
const previousOverflow = document.body.style.overflow;
document.body.style.overflow = 'hidden';
const frame = window.requestAnimationFrame(() => closeButtonRef.current?.focus());
return () => {
window.cancelAnimationFrame(frame);
document.body.style.overflow = previousOverflow;
previousFocusRef.current?.focus?.();
previousFocusRef.current = null;
};
}, [image]);
if (!image) {
return null;
}
const src = imageUrlWithRetry(image.url, retryKey);
return (
<div className="image-lightbox" role="dialog" aria-modal="true" onClick={onClose}>
<div className="lightbox-top">
<button ref={closeButtonRef} type="button" className="lightbox-close" onClick={onClose} aria-label="关闭图片预览">
<X size={22} />
</button>
</div>
<div className="lightbox-stage" onClick={(event) => event.stopPropagation()}>
<img
src={src}
alt={image.alt || '生成图片'}
onLoad={() => setLoadState('loaded')}
onError={() => setLoadState('failed')}
/>
</div>
{loadState === 'failed' ? (
<div className="lightbox-actions" onClick={(event) => event.stopPropagation()}>
<button
type="button"
onClick={() => {
setLoadState('loading');
setRetryKey(Date.now());
}}
>
<RefreshCw size={16} />
重新加载
</button>
</div>
) : null}
</div>
);
}