import React, { useState, useEffect, Suspense } from "react";
import {
Box,
VStack,
HStack,
Text,
Button,
Image
} from "@chakra-ui/react";
import { Message } from "~/types/chat";
import { BubbleMessage } from "./components/BubbleMessage";
import ProductGrid from "./components/ProductGrid";
import { SuggestionsList } from "./components/SuggestionsList";
import { useTypewriter } from "~/hooks/useTypewriter";
// Create a component that will be lazy loaded with react-markdown
const MarkdownRenderer = React.lazy(() =>
import('./components/MarkdownRenderer').then(module => ({
default: module.MarkdownRenderer
}))
);
// Simple loading fallback
const MarkdownLoadingFallback = () => بارگزاری;
/**
* Skip animation for historical messages restored from sessionStorage —
* only fade-in messages created within the last 2 seconds.
*/
function useFadeIn(messageTimestamp: Date) {
const isRecent = Date.now() - new Date(messageTimestamp).getTime() < 2000;
const [visible, setVisible] = useState(isRecent ? false : true);
useEffect(() => {
if (!isRecent) return;
const frame = requestAnimationFrame(() => setVisible(true));
return () => cancelAnimationFrame(frame);
}, [isRecent]);
return visible;
}
interface MessageRendererProps {
message: Message;
source?: "widget" | "embedded";
isStreaming?: boolean;
}
export const MessageRenderer: React.FC = ({
message,
source = "widget",
isStreaming = false,
}) => {
const [isMarkdownLoaded, setIsMarkdownLoaded] = useState(false);
const fadeVisible = useFadeIn(message.timestamp);
const displayedContent = useTypewriter(message.content, isStreaming);
// Preload markdown when component mounts
React.useEffect(() => {
import('./components/MarkdownRenderer').then(() => {
setIsMarkdownLoaded(true);
});
}, []);
if (message.type === "welcome") {
return ;
}
if (message.type === "loading") {
return (
message.content && (
)
);
}
if (message.type === "user") {
// Use displayContent if available (for messages with hidden context), otherwise use content
const displayText = message.displayContent || message.content;
return (
{message.imageUrl && (
)}
{(displayText.length > 0 || message.repliedProduct) && (
{message.repliedProduct && (
{message.repliedProduct.name}
)}
{isMarkdownLoaded ? (
}>
) : (
{displayText}
)}
)}
);
}
if (message.type === "products") {
return (
);
}
if (message.type === "suggestions") {
return source === "widget" ? : [];
}
// Bot ("mori") messages — use typewriter for streaming, fade-in on mount
const hasTextContent =
typeof message.content === "string" && message.content.trim().length > 0;
const renderedContent = isStreaming ? displayedContent : message.content;
return (
{hasTextContent && (
source === "widget" ? (
{isMarkdownLoaded ? (
}>
) : (
{renderedContent}
)}
) : (
{isMarkdownLoaded ? (
}>
) : (
{renderedContent}
)}
)
)}
{message.products && message.products.length > 0 && (
)}
{message.actions && (
{message.actions.map((action, index) => (
))}
)}
);
};