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 && ( Uploaded image )} {(displayText.length > 0 || message.repliedProduct) && ( {message.repliedProduct && ( product {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) => ( ))} )} ); };