163 lines
4.9 KiB
TypeScript
163 lines
4.9 KiB
TypeScript
"use client";
|
||
/**
|
||
* MessageBubble 消息气泡(容器)
|
||
*
|
||
*
|
||
*
|
||
* 组成(从左到右):
|
||
* - AI:[Avatar] [Content] [占位 spacer]
|
||
* - 用户:[占位 spacer] [Content] [Avatar]
|
||
*/
|
||
import { useUserSelector } from "@/stores/user/user-context";
|
||
import type { ChatAction, CommercialAction, PaymentGuidance } from "@/data/schemas/chat";
|
||
|
||
import { MessageAvatar } from "./message-avatar";
|
||
import { MessageContent } from "./message-content";
|
||
import styles from "./chat-area.module.css";
|
||
|
||
export interface MessageBubbleProps {
|
||
characterId: string;
|
||
displayMessageId: string;
|
||
remoteMessageId?: 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;
|
||
commercialAction?: CommercialAction | null;
|
||
chatAction?: ChatAction | null;
|
||
paymentGuidance?: PaymentGuidance | null;
|
||
isUnlockingMessage?: boolean;
|
||
onUnlockPrivateMessage?: ChatMessageAction;
|
||
onUnlockVoiceMessage?: ChatMessageAction;
|
||
onUnlockImageMessage?: ChatMessageAction;
|
||
onOpenImage?: (displayMessageId: string) => void;
|
||
onUserAvatarClick?: () => void;
|
||
onCharacterAvatarClick?: () => void;
|
||
onCommercialAction?: (action: CommercialAction) => void;
|
||
onCommercialActionDismiss?: (action: CommercialAction) => void;
|
||
onChatActionViewed?: (action: ChatAction) => void;
|
||
onChatAction?: (action: ChatAction) => void | Promise<void>;
|
||
onChatActionDismiss?: (action: ChatAction) => void;
|
||
}
|
||
|
||
type ChatMessageAction = (
|
||
displayMessageId: string,
|
||
remoteMessageId?: string,
|
||
) => void;
|
||
|
||
export function MessageBubble({
|
||
characterId,
|
||
displayMessageId,
|
||
remoteMessageId,
|
||
content,
|
||
imageUrl,
|
||
imagePaywalled,
|
||
audioUrl,
|
||
isFromAI,
|
||
locked,
|
||
lockReason,
|
||
lockedPrivate,
|
||
privateMessageHint,
|
||
commercialAction,
|
||
chatAction,
|
||
paymentGuidance,
|
||
isUnlockingMessage,
|
||
onUnlockPrivateMessage,
|
||
onUnlockVoiceMessage,
|
||
onUnlockImageMessage,
|
||
onOpenImage,
|
||
onUserAvatarClick,
|
||
onCharacterAvatarClick,
|
||
onCommercialAction,
|
||
onCommercialActionDismiss,
|
||
onChatActionViewed,
|
||
onChatAction,
|
||
onChatActionDismiss,
|
||
}: MessageBubbleProps) {
|
||
const avatarUrl = useUserSelector((state) => state.context.avatarUrl);
|
||
|
||
if (isFromAI) {
|
||
return (
|
||
<div
|
||
className={styles.bubbleRowAi}
|
||
data-chat-message-id={displayMessageId}
|
||
aria-label="AI message"
|
||
>
|
||
<MessageAvatar
|
||
isFromAI={true}
|
||
onClick={onCharacterAvatarClick}
|
||
/>
|
||
<div className={styles.bubbleInlineSpacer} aria-hidden="true" />
|
||
<MessageContent
|
||
characterId={characterId}
|
||
displayMessageId={displayMessageId}
|
||
remoteMessageId={remoteMessageId}
|
||
content={content}
|
||
imageUrl={imageUrl}
|
||
imagePaywalled={imagePaywalled}
|
||
audioUrl={audioUrl}
|
||
isFromAI={true}
|
||
locked={locked}
|
||
lockReason={lockReason}
|
||
lockedPrivate={lockedPrivate}
|
||
privateMessageHint={privateMessageHint}
|
||
commercialAction={commercialAction}
|
||
chatAction={chatAction}
|
||
paymentGuidance={paymentGuidance}
|
||
isUnlockingMessage={isUnlockingMessage}
|
||
onUnlockPrivateMessage={onUnlockPrivateMessage}
|
||
onUnlockVoiceMessage={onUnlockVoiceMessage}
|
||
onUnlockImageMessage={onUnlockImageMessage}
|
||
onOpenImage={onOpenImage}
|
||
onCommercialAction={onCommercialAction}
|
||
onCommercialActionDismiss={onCommercialActionDismiss}
|
||
onChatActionViewed={onChatActionViewed}
|
||
onChatAction={onChatAction}
|
||
onChatActionDismiss={onChatActionDismiss}
|
||
/>
|
||
<div className={styles.bubbleAvatarSpacer} aria-hidden="true" />
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div
|
||
className={styles.bubbleRowUser}
|
||
data-chat-message-id={displayMessageId}
|
||
aria-label="User message"
|
||
>
|
||
<div className={styles.bubbleAvatarSpacer} aria-hidden="true" />
|
||
<MessageContent
|
||
characterId={characterId}
|
||
displayMessageId={displayMessageId}
|
||
remoteMessageId={remoteMessageId}
|
||
content={content}
|
||
imageUrl={imageUrl}
|
||
imagePaywalled={imagePaywalled}
|
||
audioUrl={audioUrl}
|
||
isFromAI={false}
|
||
locked={locked}
|
||
lockReason={lockReason}
|
||
lockedPrivate={lockedPrivate}
|
||
privateMessageHint={privateMessageHint}
|
||
isUnlockingMessage={isUnlockingMessage}
|
||
onUnlockPrivateMessage={onUnlockPrivateMessage}
|
||
onUnlockVoiceMessage={onUnlockVoiceMessage}
|
||
onUnlockImageMessage={onUnlockImageMessage}
|
||
onOpenImage={onOpenImage}
|
||
/>
|
||
<div className={styles.bubbleInlineSpacer} aria-hidden="true" />
|
||
<MessageAvatar
|
||
isFromAI={false}
|
||
userAvatarUrl={avatarUrl}
|
||
onClick={onUserAvatarClick}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|