import { addToast, cn } from '@heroui/react'; import { type Message as BaseMessage, useMujian } from '@mujian/js-sdk/react'; import React, { type MouseEventHandler } from 'react'; import { mjChatCls } from '@/utils/cls'; import { EditActions } from './components/EditActions'; import { MessageActions } from './components/MessageActions'; import { MessageBubble } from './components/MessageBubble'; export type MessageItemProps = { message: BaseMessage; originalMessage: BaseMessage; index: number; avatar: string; name: string; isLastMsg?: boolean; isFirstMsg?: boolean; onRegenerate?: () => Promise; onContinue?: () => Promise; onDelete: (messageId: string) => Promise; onEdit: (messageId: string, content: string) => Promise; onSwipe: (messageId: string, swipeId: number) => Promise; sendMessage?: (message: string) => Promise; }; // Add comparison function for React.memo const arePropsEqual = ( prevProps: MessageItemProps, nextProps: MessageItemProps, ) => { // 如果 swipes 长度不同,直接返回 false if (prevProps.message.swipes?.length !== nextProps.message.swipes?.length) { return false; } // 如果 swipes 内容不同,直接返回 false if ( prevProps.message.swipes?.some( (swipe, index) => swipe !== nextProps.message.swipes[index], ) ) { return false; } // 其他属性比较 return ( prevProps.avatar === nextProps.avatar && prevProps.message.content === nextProps.message.content && prevProps.message.role === nextProps.message.role && prevProps.message.isStreaming === nextProps.message.isStreaming && prevProps.name === nextProps.name && prevProps.isLastMsg === nextProps.isLastMsg && prevProps.message.sendAt === nextProps.message.sendAt && prevProps.message.activeSwipeId === nextProps.message.activeSwipeId && prevProps.message.id === nextProps.message.id && prevProps.index === nextProps.index ); }; export const MessageItem = React.memo((props: MessageItemProps) => { const { message, originalMessage, // index, // avatar, // name, isLastMsg, isFirstMsg, onRegenerate, onContinue, onDelete, onEdit, onSwipe, } = props; const { id, role, isStreaming, content, swipes = [], activeSwipeId, // sendAt, } = message; const isUser = role === 'user'; const messageRef = React.useRef(null); // 使用 useRef 获取当前组件的引用 const [isEditing, setIsEditing] = React.useState(false); // State to toggle edit mode const [editedMessage, setEditedMessage] = React.useState(content); const mujian = useMujian(); const renderedMessage = isUser ? content : swipes[activeSwipeId]; // 滚动到消息可见位置的通用函数 const scrollMessageIntoView = ( alignToBlock: ScrollLogicalPosition = 'end', behavior: ScrollBehavior = 'instant', ) => { if (messageRef.current) { messageRef.current.scrollIntoView({ behavior: behavior, block: alignToBlock, }); } }; const handleSaveEdit = async () => { if (id && activeSwipeId !== undefined) { await onEdit(id, editedMessage); setIsEditing(false); setTimeout(() => scrollMessageIntoView('end', 'smooth'), 100); } }; const handleCopy = async () => { try { await mujian.utils.clipboard.writeText(originalMessage.content); addToast({ color: 'success', description: '复制成功', timeout: 2000, }); } catch (error) { console.error(error); addToast({ description: '复制失败,请重试。', }); } }; // Separate function to handle message deletion const handleDeleteMessage = async () => { if (id) { onDelete(id); } }; // console.log("content", index, content); // 确保 currentSwipeId 在有效范围内 const validSwipeId = Math.min(Math.max(0, activeSwipeId), swipes.length - 1); const handleSwipe = async (direction: 'prev' | 'next') => { if (swipes.length <= 1) return; // 简化计算逻辑,直接使用模运算确保能够循环浏览所有页面 const newSwipeId = direction === 'prev' ? (activeSwipeId - 1 + swipes.length) % swipes.length : (activeSwipeId + 1) % swipes.length; await onSwipe(id, newSwipeId); scrollMessageIntoView(); }; // Stop propagation for message text clicks const handleMessageTextClick: MouseEventHandler = (e) => { e.stopPropagation(); }; const handleEditButtonClick = async () => { setIsEditing(true); if (isUser) { setEditedMessage(originalMessage.content); } else { setEditedMessage(originalMessage.swipes[validSwipeId]); } }; return (
{ // 按ctrl点击时打印ID if (event.ctrlKey) { event.preventDefault(); console.log('Official Chat Message ID:', id); } }} > scrollMessageIntoView()} /> { setIsEditing(false); setEditedMessage( isUser ? originalMessage.content : originalMessage.swipes[activeSwipeId], ); // 滚动到消息位置 setTimeout(() => scrollMessageIntoView('end', 'smooth'), 100); }} />
); }, arePropsEqual); MessageItem.displayName = 'Message';