Files
cozsweet-frontend-nextjs/src/app/chat/components/message-content.tsx
T

163 lines
5.1 KiB
TypeScript

"use client";
import type { ChatAction, CommercialAction } from "@/data/schemas/chat";
import { ChatActionCard } from "./chat-action-card";
import { CommercialActionCard } from "./commercial-action-card";
import { ImageBubble } from "./image-bubble";
import { LockedImageMessageCard } from "./locked-image-message-card";
import { PrivateMessageCard } from "./private-message-card";
import { TextBubble } from "./text-bubble";
import { VoiceBubble } from "./voice-bubble";
import styles from "./chat-area.module.css";
export interface MessageContentProps {
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;
isUnlockingMessage?: boolean;
onUnlockPrivateMessage?: ChatMessageAction;
onUnlockVoiceMessage?: ChatMessageAction;
onUnlockImageMessage?: ChatMessageAction;
onOpenImage?: (displayMessageId: string) => 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;
const IMAGE_PLACEHOLDER = "[图片]";
export function MessageContent({
characterId,
displayMessageId,
remoteMessageId,
content,
imageUrl,
imagePaywalled,
audioUrl,
isFromAI,
locked,
lockReason,
lockedPrivate,
privateMessageHint,
commercialAction,
chatAction,
isUnlockingMessage,
onUnlockPrivateMessage,
onUnlockVoiceMessage,
onUnlockImageMessage,
onOpenImage,
onCommercialAction,
onCommercialActionDismiss,
onChatActionViewed,
onChatAction,
onChatActionDismiss,
}: MessageContentProps) {
const hasImage = imageUrl != null && imageUrl.length > 0;
const hasAudio = audioUrl != null && audioUrl.length > 0;
const hasText = content.length > 0 && content !== IMAGE_PLACEHOLDER;
const isLockedPrivateMessage =
lockedPrivate === true ||
(locked === true && lockReason === "private_message");
const isLockedVoiceMessage = locked === true && lockReason === "voice_message";
const isLockedImageMessage =
locked === true &&
(lockReason === "image_paywall" || lockReason === "image");
const shouldRenderVoiceMessage = hasAudio || isLockedVoiceMessage;
const handleUnlockPrivateMessage =
onUnlockPrivateMessage
? () =>
onUnlockPrivateMessage(displayMessageId, remoteMessageId)
: undefined;
const handleUnlockVoiceMessage =
isLockedVoiceMessage && onUnlockVoiceMessage
? () => onUnlockVoiceMessage(displayMessageId, remoteMessageId)
: undefined;
const handleUnlockImageMessage =
isLockedImageMessage && onUnlockImageMessage
? () => onUnlockImageMessage(displayMessageId, remoteMessageId)
: undefined;
return (
<div
className={[
styles.messageContent,
isFromAI ? styles.messageContentAi : styles.messageContentUser,
].join(" ")}
>
{isLockedPrivateMessage ? (
<PrivateMessageCard
hint={privateMessageHint}
isUnlocking={isUnlockingMessage}
onUnlock={handleUnlockPrivateMessage}
/>
) : isLockedImageMessage && !hasImage ? (
<LockedImageMessageCard
hint={privateMessageHint}
isUnlocking={isUnlockingMessage}
onUnlock={handleUnlockImageMessage}
/>
) : (
<>
{hasImage && imageUrl && (
<ImageBubble
characterId={characterId}
displayMessageId={displayMessageId}
remoteMessageId={remoteMessageId}
imageUrl={imageUrl}
imagePaywalled={imagePaywalled}
onOpenImage={onOpenImage}
/>
)}
{shouldRenderVoiceMessage ? (
<VoiceBubble
characterId={characterId}
remoteMessageId={remoteMessageId}
audioUrl={audioUrl}
isFromAI={isFromAI}
locked={isLockedVoiceMessage}
hint={privateMessageHint}
isUnlocking={isUnlockingMessage}
onUnlock={handleUnlockVoiceMessage}
/>
) : null}
{hasText && !shouldRenderVoiceMessage ? (
<TextBubble content={content} isFromAI={isFromAI} />
) : null}
</>
)}
{isFromAI && chatAction ? (
<ChatActionCard
action={chatAction}
onViewed={onChatActionViewed}
onActivate={onChatAction}
onDismiss={onChatActionDismiss}
/>
) : isFromAI && commercialAction ? (
<CommercialActionCard
action={commercialAction}
onActivate={onCommercialAction}
onDismiss={onCommercialActionDismiss}
/>
) : null}
</div>
);
}