77 lines
2.2 KiB
TypeScript
77 lines
2.2 KiB
TypeScript
"use client";
|
||
/**
|
||
* ImageBubble 图片气泡
|
||
*
|
||
* 支持:
|
||
* - base64 data URI 解码(`data:image/png;base64,...`)
|
||
* - 点击 → 通知聊天页打开页内全屏查看器
|
||
* - 错误占位符(broken image icon)
|
||
*/
|
||
|
||
import { ChatMediaImage } from "./chat-media-image";
|
||
|
||
export interface ImageBubbleProps {
|
||
characterId: string;
|
||
displayMessageId: string;
|
||
remoteMessageId?: string;
|
||
imageUrl: string; // base64 data URI 或 URL
|
||
imagePaywalled?: boolean;
|
||
onOpenImage?: (displayMessageId: string) => void;
|
||
}
|
||
|
||
export function ImageBubble({
|
||
characterId,
|
||
displayMessageId,
|
||
remoteMessageId,
|
||
imageUrl,
|
||
imagePaywalled = false,
|
||
onOpenImage,
|
||
}: ImageBubbleProps) {
|
||
const canOpen = Boolean(onOpenImage);
|
||
const imageClassName = [
|
||
"block h-auto w-full object-cover",
|
||
imagePaywalled ? "scale-104 blur-sm" : "",
|
||
]
|
||
.filter(Boolean)
|
||
.join(" ");
|
||
|
||
const openImage = () => {
|
||
if (!onOpenImage) return;
|
||
onOpenImage(displayMessageId);
|
||
};
|
||
|
||
return (
|
||
<div
|
||
className="relative max-h-(--chat-media-size,220px) max-w-(--chat-media-size,220px) cursor-pointer overflow-hidden rounded-lg rounded-tl-none bg-(--color-bubble-background,#fff)"
|
||
onClick={openImage}
|
||
role={canOpen ? "button" : undefined}
|
||
tabIndex={canOpen ? 0 : undefined}
|
||
onKeyDown={(e) => {
|
||
if (!canOpen) return;
|
||
if (e.key === "Enter" || e.key === " ") {
|
||
e.preventDefault();
|
||
openImage();
|
||
}
|
||
}}
|
||
aria-label={canOpen ? "Open image in fullscreen" : undefined}
|
||
>
|
||
<ChatMediaImage
|
||
characterId={characterId}
|
||
remoteMessageId={remoteMessageId}
|
||
remoteUrl={imageUrl}
|
||
className={imageClassName}
|
||
errorClassName="flex size-(--chat-media-size,220px) items-center justify-center bg-(--color-bubble-background,#fff) text-(length:--icon-size-xl,24px) text-(--color-text-secondary,#9e9e9e)"
|
||
width={240}
|
||
height={240}
|
||
>
|
||
{imagePaywalled ? (
|
||
<div
|
||
className="pointer-events-none absolute inset-0 bg-[linear-gradient(180deg,rgba(255,255,255,0.04)_0%,rgba(255,255,255,0.14)_100%)]"
|
||
aria-hidden="true"
|
||
/>
|
||
) : null}
|
||
</ChatMediaImage>
|
||
</div>
|
||
);
|
||
}
|