106 lines
2.8 KiB
TypeScript
106 lines
2.8 KiB
TypeScript
"use client";
|
||
/**
|
||
* MessageBubble 消息气泡(容器)
|
||
*
|
||
*
|
||
*
|
||
* 组成(从左到右):
|
||
* - AI:[Avatar] [Content] [占位 spacer]
|
||
* - 用户:[占位 spacer] [Content] [Avatar]
|
||
*/
|
||
import { useUserState } from "@/stores/user/user-context";
|
||
|
||
import { MessageAvatar } from "./message-avatar";
|
||
import { MessageContent } from "./message-content";
|
||
import styles from "./chat-area.module.css";
|
||
|
||
export interface MessageBubbleProps {
|
||
messageId?: string;
|
||
content: string;
|
||
imageUrl?: string | null;
|
||
imagePaywalled?: boolean;
|
||
audioUrl?: string | null;
|
||
isFromAI: boolean;
|
||
locked?: boolean | null;
|
||
lockReason?: string | null;
|
||
lockedPrivate?: boolean | null;
|
||
privateMessageHint?: string | null;
|
||
isUnlockingMessage?: boolean;
|
||
onUnlockPrivateMessage?: (messageId: string) => void;
|
||
onUnlockVoiceMessage?: (messageId: string) => void;
|
||
}
|
||
|
||
export function MessageBubble({
|
||
messageId,
|
||
content,
|
||
imageUrl,
|
||
imagePaywalled,
|
||
audioUrl,
|
||
isFromAI,
|
||
locked,
|
||
lockReason,
|
||
lockedPrivate,
|
||
privateMessageHint,
|
||
isUnlockingMessage,
|
||
onUnlockPrivateMessage,
|
||
onUnlockVoiceMessage,
|
||
}: MessageBubbleProps) {
|
||
const { avatarUrl } = useUserState();
|
||
|
||
if (isFromAI) {
|
||
return (
|
||
<div
|
||
className={styles.bubbleRowAi}
|
||
data-chat-message-id={messageId}
|
||
aria-label="AI message"
|
||
>
|
||
<MessageAvatar isFromAI={true} />
|
||
<div style={{ width: 8 }} />
|
||
<MessageContent
|
||
content={content}
|
||
imageUrl={imageUrl}
|
||
imagePaywalled={imagePaywalled}
|
||
audioUrl={audioUrl}
|
||
messageId={messageId}
|
||
isFromAI={true}
|
||
locked={locked}
|
||
lockReason={lockReason}
|
||
lockedPrivate={lockedPrivate}
|
||
privateMessageHint={privateMessageHint}
|
||
isUnlockingMessage={isUnlockingMessage}
|
||
onUnlockPrivateMessage={onUnlockPrivateMessage}
|
||
onUnlockVoiceMessage={onUnlockVoiceMessage}
|
||
/>
|
||
<div style={{ width: 43 }} /> {/* 占位:保持与头像宽度一致 */}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div
|
||
className={styles.bubbleRowUser}
|
||
data-chat-message-id={messageId}
|
||
aria-label="User message"
|
||
>
|
||
<div style={{ width: 43 }} /> {/* 占位 */}
|
||
<MessageContent
|
||
content={content}
|
||
imageUrl={imageUrl}
|
||
imagePaywalled={imagePaywalled}
|
||
audioUrl={audioUrl}
|
||
messageId={messageId}
|
||
isFromAI={false}
|
||
locked={locked}
|
||
lockReason={lockReason}
|
||
lockedPrivate={lockedPrivate}
|
||
privateMessageHint={privateMessageHint}
|
||
isUnlockingMessage={isUnlockingMessage}
|
||
onUnlockPrivateMessage={onUnlockPrivateMessage}
|
||
onUnlockVoiceMessage={onUnlockVoiceMessage}
|
||
/>
|
||
<div style={{ width: 8 }} />
|
||
<MessageAvatar isFromAI={false} userAvatarUrl={avatarUrl} />
|
||
</div>
|
||
);
|
||
}
|