/* * @Author: ZhaoYing * @Date: 2025-12-10 16:46:17 * @Last Modified by: ZhaoYing * @Last Modified time: 2026-03-17 14:11:24 */ import { type FC, useRef, useEffect, useState } from 'react' import clsx from 'clsx' import Markdown from '@/components/Markdown' import type { ChatContentProps } from './types' import { Spin, Divider, Space } from 'antd' import { SoundOutlined } from '@ant-design/icons' /** * Chat Content Display Component * Responsible for rendering chat message list, supports different role message styles and auto-scrolling */ const ChatContent: FC = ({ classNames, contentClassNames, data = [], streamLoading = false, empty, labelPosition = 'bottom', labelFormat, errorDesc, renderRuntime }) => { // Scroll container reference for controlling auto-scroll to bottom const scrollContainerRef = useRef<(HTMLDivElement | null)>(null) const prevDataLengthRef = useRef(data.length); const isScrolledToBottomRef = useRef(true); const audioRef = useRef(null) const [playingIndex, setPlayingIndex] = useState(null) const handlePlay = (index: number, audioUrl: string) => { if (playingIndex === index) { audioRef.current?.pause() setPlayingIndex(null) return } if (audioRef.current) { audioRef.current.pause() } const audio = new Audio(audioUrl) audioRef.current = audio audio.play() setPlayingIndex(index) audio.onended = () => setPlayingIndex(null) } // Track scroll position to determine if user is at bottom useEffect(() => { const handleScroll = () => { if (scrollContainerRef.current) { const { scrollTop, scrollHeight, clientHeight } = scrollContainerRef.current; // Consider user is at bottom if within 20px of the bottom isScrolledToBottomRef.current = scrollHeight - scrollTop - clientHeight < 20; } }; const container = scrollContainerRef.current; if (container) { container.addEventListener('scroll', handleScroll); // Initial check handleScroll(); } return () => { if (container) { container.removeEventListener('scroll', handleScroll); } }; }, []); // Auto-scroll to bottom when data changes to show latest messages // When data array length remains unchanged, if data is updated and user manually scrolled up, don't auto-scroll to bottom // When data array length changes, auto-scroll to bottom // If already scrolled to bottom, will auto-scroll to bottom useEffect(() => { setTimeout(() => { if (scrollContainerRef.current) { // Auto-scroll if data length changed OR user is currently at bottom if (data.length !== prevDataLengthRef.current || isScrolledToBottomRef.current) { scrollContainerRef.current.scrollTop = scrollContainerRef.current.scrollHeight; } prevDataLengthRef.current = data.length; } }, 0); }, [data]) return (
{data.length === 0 ? empty // Display empty state : data.map((item, index) => (
{/* Don't display if streaming and content is empty */} {streamLoading && item.content === '' && !renderRuntime ? : <> {/* Top label (such as timestamp, username, etc.) */} {labelPosition === 'top' &&
{labelFormat(item)}
} {/* Message bubble */}
{item.subContent && renderRuntime && renderRuntime(item, index)} {/* Render message content using Markdown component */} {item.audioUrl && <> {playingIndex !== index ? handlePlay(index, item.audioUrl!)} /> :
handlePlay(index, item.audioUrl!)} /> } }
{/* Bottom label (such as timestamp, username, etc.) */} {labelPosition === 'bottom' &&
{labelFormat(item)}
} }
)) }
) } export default ChatContent