140 lines
4.9 KiB
TypeScript
140 lines
4.9 KiB
TypeScript
import { motion } from "motion/react";
|
|
import React from "react";
|
|
import { TypingText } from "./TypingText";
|
|
import { EmojiText } from "../chatbot/EmojiText";
|
|
import chatbotAvatarIcon from "../../../assets/chatbot-bot-avatar.png";
|
|
import { toPersianDigits } from "../../../utils/persianNumberUtils";
|
|
|
|
export interface ChatMessage {
|
|
id: string;
|
|
type: "user" | "other" | "loading";
|
|
content: string;
|
|
author?: string;
|
|
timestamp: string;
|
|
isTyping?: boolean;
|
|
}
|
|
|
|
interface ChatMessagesProps {
|
|
messages: ChatMessage[];
|
|
containerRef: React.RefObject<HTMLDivElement>;
|
|
endRef: React.RefObject<HTMLDivElement>;
|
|
onTyping?: () => void;
|
|
}
|
|
|
|
const messageStyles = {
|
|
user: {
|
|
background:
|
|
"linear-gradient(145deg, rgba(218, 94, 142, 0.96) 0%, rgba(162, 56, 110, 0.95) 100%)",
|
|
boxShadow:
|
|
"0 0 24px rgba(240,110,168,0.28), 0 12px 28px rgba(84, 22, 60, 0.38), inset 0 1px 0 rgba(255,255,255,0.16)",
|
|
border: "1px solid rgba(255, 178, 214, 0.58)",
|
|
backdropFilter: "blur(14px)",
|
|
WebkitBackdropFilter: "blur(14px)",
|
|
},
|
|
other: {
|
|
background:
|
|
"linear-gradient(145deg, rgba(52, 34, 76, 0.94) 0%, rgba(35, 24, 62, 0.94) 100%)",
|
|
backgroundImage:
|
|
"linear-gradient(145deg, rgba(52, 34, 76, 0.94) 0%, rgba(35, 24, 62, 0.94) 100%), linear-gradient(120deg, #7c3aed 0%, #f97316 58%, #facc15 100%)",
|
|
backgroundOrigin: "border-box",
|
|
backgroundClip: "padding-box, border-box",
|
|
boxShadow: "0 0 24px rgba(152,104,235,0.24), 0 12px 28px rgba(12, 8, 30, 0.4), inset 0 1px 0 rgba(255,255,255,0.12)",
|
|
border: "0.5px solid transparent",
|
|
backdropFilter: "blur(14px)",
|
|
WebkitBackdropFilter: "blur(14px)",
|
|
},
|
|
};
|
|
|
|
export function ChatMessages({
|
|
messages,
|
|
containerRef,
|
|
endRef,
|
|
onTyping,
|
|
}: ChatMessagesProps) {
|
|
return (
|
|
<div
|
|
ref={containerRef}
|
|
className="flex-1 overflow-y-auto px-4 pb-20"
|
|
dir="rtl"
|
|
>
|
|
<div className="space-y-3">
|
|
{messages.map((message) => {
|
|
const isUser = message.type === "user";
|
|
const isLoading = message.type === "loading";
|
|
|
|
return (
|
|
<motion.div
|
|
key={message.id}
|
|
initial={{ opacity: 0, y: 10 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
className={`flex ${isUser ? "justify-start" : "justify-end"} items-start gap-2`}
|
|
>
|
|
<div
|
|
className="relative max-w-[75%] rounded-[18px] px-4 py-3"
|
|
style={isUser ? messageStyles.user : messageStyles.other}
|
|
>
|
|
{message.author && (
|
|
<p
|
|
className="text-xs font-bold mb-1"
|
|
style={{ color: "#8ACEE0" }}
|
|
>
|
|
{message.author}
|
|
</p>
|
|
)}
|
|
|
|
{isLoading ? (
|
|
<div className="flex items-center gap-1">
|
|
<motion.span
|
|
className="w-2 h-2 rounded-full bg-white"
|
|
animate={{ opacity: [0.3, 1, 0.3] }}
|
|
transition={{ duration: 1.5, repeat: Infinity, delay: 0 }}
|
|
/>
|
|
<motion.span
|
|
className="w-2 h-2 rounded-full bg-white"
|
|
animate={{ opacity: [0.3, 1, 0.3] }}
|
|
transition={{ duration: 1.5, repeat: Infinity, delay: 0.2 }}
|
|
/>
|
|
<motion.span
|
|
className="w-2 h-2 rounded-full bg-white"
|
|
animate={{ opacity: [0.3, 1, 0.3] }}
|
|
transition={{ duration: 1.5, repeat: Infinity, delay: 0.4 }}
|
|
/>
|
|
</div>
|
|
) : message.type === "other" && message.isTyping ? (
|
|
<TypingText text={message.content} speed={30} onTyping={onTyping} />
|
|
) : (
|
|
<p className="text-white text-sm break-words whitespace-pre-wrap">
|
|
<EmojiText text={message.content} />
|
|
</p>
|
|
)}
|
|
|
|
<p className="text-white/60 text-xs mt-1 text-left">
|
|
{toPersianDigits(message.timestamp)}
|
|
</p>
|
|
</div>
|
|
{!isUser && (
|
|
<div
|
|
className="-mr-1 mt-1 flex h-11 w-11 flex-shrink-0 items-center justify-center rounded-full"
|
|
style={{
|
|
background: "radial-gradient(circle, rgba(255,104,205,0.18) 0%, transparent 68%)",
|
|
filter:
|
|
"drop-shadow(0 0 8px rgba(255, 104, 205, 0.55)) drop-shadow(0 0 16px rgba(255, 104, 205, 0.28))",
|
|
}}
|
|
>
|
|
<img
|
|
src={chatbotAvatarIcon}
|
|
alt="چتبات"
|
|
className="h-10 w-10 object-contain"
|
|
/>
|
|
</div>
|
|
)}
|
|
</motion.div>
|
|
);
|
|
})}
|
|
|
|
<div ref={endRef} />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|