import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'
import { Search, MessageSquare, AlertCircle, Loader2, RefreshCw, X, ChevronDown, Info, Calendar, Database, Hash, Play, Pause, Image as ImageIcon, Link } from 'lucide-react'
import { createPortal } from 'react-dom'
import { useChatStore } from '../stores/chatStore'
import type { ChatSession, Message } from '../types/models'
import { getEmojiPath } from 'wechat-emojis'
import { ImagePreview } from '../components/ImagePreview'
import { VoiceTranscribeDialog } from '../components/VoiceTranscribeDialog'
import { AnimatedStreamingText } from '../components/AnimatedStreamingText'
import * as configService from '../services/config'
import './ChatPage.scss'
// 系统消息类型常量
const SYSTEM_MESSAGE_TYPES = [
10000, // 系统消息
266287972401, // 拍一拍
]
// 判断是否为系统消息
function isSystemMessage(localType: number): boolean {
return SYSTEM_MESSAGE_TYPES.includes(localType)
}
interface ChatPageProps {
// 保留接口以备将来扩展
}
interface SessionDetail {
wxid: string
displayName: string
remark?: string
nickName?: string
alias?: string
avatarUrl?: string
messageCount: number
firstMessageTime?: number
latestMessageTime?: number
messageTables: { dbName: string; tableName: string; count: number }[]
}
// 全局头像加载队列管理器已移至 src/utils/AvatarLoadQueue.ts
// 全局头像加载队列管理器已移至 src/utils/AvatarLoadQueue.ts
import { avatarLoadQueue } from '../utils/AvatarLoadQueue'
import { Avatar } from '../components/Avatar'
// 头像组件 - 支持骨架屏加载和懒加载(优化:限制并发,使用 memo 避免不必要的重渲染)
// 会话项组件(使用 memo 优化,避免不必要的重渲染)
const SessionItem = React.memo(function SessionItem({
session,
isActive,
onSelect,
formatTime
}: {
session: ChatSession
isActive: boolean
onSelect: (session: ChatSession) => void
formatTime: (timestamp: number) => string
}) {
// 缓存格式化的时间
const timeText = useMemo(() =>
formatTime(session.lastTimestamp || session.sortTimestamp),
[formatTime, session.lastTimestamp, session.sortTimestamp]
)
return (
onSelect(session)}
>
{session.displayName || session.username}
{timeText}
{session.summary || '暂无消息'}
{session.unreadCount > 0 && (
{session.unreadCount > 99 ? '99+' : session.unreadCount}
)}
)
}, (prevProps, nextProps) => {
// 自定义比较:只在关键属性变化时重渲染
return (
prevProps.session.username === nextProps.session.username &&
prevProps.session.displayName === nextProps.session.displayName &&
prevProps.session.avatarUrl === nextProps.session.avatarUrl &&
prevProps.session.summary === nextProps.session.summary &&
prevProps.session.unreadCount === nextProps.session.unreadCount &&
prevProps.session.lastTimestamp === nextProps.session.lastTimestamp &&
prevProps.session.sortTimestamp === nextProps.session.sortTimestamp &&
prevProps.isActive === nextProps.isActive
)
})
function ChatPage(_props: ChatPageProps) {
const {
isConnected,
isConnecting,
connectionError,
sessions,
filteredSessions,
currentSessionId,
isLoadingSessions,
messages,
isLoadingMessages,
isLoadingMore,
hasMoreMessages,
searchKeyword,
setConnected,
setConnecting,
setConnectionError,
setSessions,
setFilteredSessions,
setCurrentSession,
setLoadingSessions,
setMessages,
appendMessages,
setLoadingMessages,
setLoadingMore,
setHasMoreMessages,
setSearchKeyword
} = useChatStore()
const messageListRef = useRef(null)
const searchInputRef = useRef(null)
const sidebarRef = useRef(null)
const initialRevealTimerRef = useRef(null)
const sessionListRef = useRef(null)
const [currentOffset, setCurrentOffset] = useState(0)
const [myAvatarUrl, setMyAvatarUrl] = useState(undefined)
const [myWxid, setMyWxid] = useState(undefined)
const [showScrollToBottom, setShowScrollToBottom] = useState(false)
const [sidebarWidth, setSidebarWidth] = useState(260)
const [isResizing, setIsResizing] = useState(false)
const [showDetailPanel, setShowDetailPanel] = useState(false)
const [sessionDetail, setSessionDetail] = useState(null)
const [isLoadingDetail, setIsLoadingDetail] = useState(false)
const [highlightedMessageKeys, setHighlightedMessageKeys] = useState([])
const [isRefreshingSessions, setIsRefreshingSessions] = useState(false)
const [hasInitialMessages, setHasInitialMessages] = useState(false)
const [showVoiceTranscribeDialog, setShowVoiceTranscribeDialog] = useState(false)
const [pendingVoiceTranscriptRequest, setPendingVoiceTranscriptRequest] = useState<{ sessionId: string; messageId: string } | null>(null)
// 联系人信息加载控制
const isEnrichingRef = useRef(false)
const enrichCancelledRef = useRef(false)
const isScrollingRef = useRef(false)
const sessionScrollTimeoutRef = useRef(null)
const highlightedMessageSet = useMemo(() => new Set(highlightedMessageKeys), [highlightedMessageKeys])
const messageKeySetRef = useRef>(new Set())
const lastMessageTimeRef = useRef(0)
const sessionMapRef = useRef