import { useTranslation } from 'react-i18next'; import { useCallback, useRef } from 'react'; import type { Dispatch, RefObject, SetStateAction } from 'react'; import type { ChatMessage } from '../../types/types'; import type { Project, ProjectSession, SessionProvider } from '../../../../types/app'; import { getIntrinsicMessageKey } from '../../utils/messageKeys'; import MessageComponent from './MessageComponent'; import ProviderSelectionEmptyState from './ProviderSelectionEmptyState'; interface ChatMessagesPaneProps { scrollContainerRef: RefObject; onWheel: () => void; onTouchMove: () => void; isLoadingSessionMessages: boolean; chatMessages: ChatMessage[]; selectedSession: ProjectSession | null; currentSessionId: string | null; provider: SessionProvider; setProvider: (provider: SessionProvider) => void; textareaRef: RefObject; claudeModel: string; setClaudeModel: (model: string) => void; cursorModel: string; setCursorModel: (model: string) => void; codexModel: string; setCodexModel: (model: string) => void; geminiModel: string; setGeminiModel: (model: string) => void; tasksEnabled: boolean; isTaskMasterInstalled: boolean | null; onShowAllTasks?: (() => void) | null; setInput: Dispatch>; isLoadingMoreMessages: boolean; hasMoreMessages: boolean; totalMessages: number; sessionMessagesCount: number; visibleMessageCount: number; visibleMessages: ChatMessage[]; loadEarlierMessages: () => void; loadAllMessages: () => void; allMessagesLoaded: boolean; isLoadingAllMessages: boolean; loadAllJustFinished: boolean; showLoadAllOverlay: boolean; createDiff: any; onFileOpen?: (filePath: string, diffInfo?: unknown) => void; onShowSettings?: () => void; onGrantToolPermission: (suggestion: { entry: string; toolName: string }) => { success: boolean }; autoExpandTools?: boolean; showRawParameters?: boolean; showThinking?: boolean; selectedProject: Project; } export default function ChatMessagesPane({ scrollContainerRef, onWheel, onTouchMove, isLoadingSessionMessages, chatMessages, selectedSession, currentSessionId, provider, setProvider, textareaRef, claudeModel, setClaudeModel, cursorModel, setCursorModel, codexModel, setCodexModel, geminiModel, setGeminiModel, tasksEnabled, isTaskMasterInstalled, onShowAllTasks, setInput, isLoadingMoreMessages, hasMoreMessages, totalMessages, sessionMessagesCount, visibleMessageCount, visibleMessages, loadEarlierMessages, loadAllMessages, allMessagesLoaded, isLoadingAllMessages, loadAllJustFinished, showLoadAllOverlay, createDiff, onFileOpen, onShowSettings, onGrantToolPermission, autoExpandTools, showRawParameters, showThinking, selectedProject, }: ChatMessagesPaneProps) { const { t } = useTranslation('chat'); const messageKeyMapRef = useRef>(new WeakMap()); const allocatedKeysRef = useRef>(new Set()); const generatedMessageKeyCounterRef = useRef(0); // Keep keys stable across prepends so existing MessageComponent instances retain local state. const getMessageKey = useCallback((message: ChatMessage) => { const existingKey = messageKeyMapRef.current.get(message); if (existingKey) { return existingKey; } const intrinsicKey = getIntrinsicMessageKey(message); let candidateKey = intrinsicKey; if (!candidateKey || allocatedKeysRef.current.has(candidateKey)) { do { generatedMessageKeyCounterRef.current += 1; candidateKey = intrinsicKey ? `${intrinsicKey}-${generatedMessageKeyCounterRef.current}` : `message-generated-${generatedMessageKeyCounterRef.current}`; } while (allocatedKeysRef.current.has(candidateKey)); } allocatedKeysRef.current.add(candidateKey); messageKeyMapRef.current.set(message, candidateKey); return candidateKey; }, []); return (
{isLoadingSessionMessages && chatMessages.length === 0 ? (

{t('session.loading.sessionMessages')}

) : chatMessages.length === 0 ? ( ) : ( <> {/* Loading indicator for older messages (hide when load-all is active) */} {isLoadingMoreMessages && !isLoadingAllMessages && !allMessagesLoaded && (

{t('session.loading.olderMessages')}

)} {/* Indicator showing there are more messages to load (hide when all loaded) */} {hasMoreMessages && !isLoadingMoreMessages && !allMessagesLoaded && (
{totalMessages > 0 && ( {t('session.messages.showingOf', { shown: sessionMessagesCount, total: totalMessages })}{' '} {t('session.messages.scrollToLoad')} )}
)} {/* Floating "Load all messages" overlay */} {(showLoadAllOverlay || isLoadingAllMessages || loadAllJustFinished) && (
{loadAllJustFinished ? (
{t('session.messages.allLoaded')}
) : ( )}
)} {/* Performance warning when all messages are loaded */} {allMessagesLoaded && (
{t('session.messages.perfWarning')}
)} {/* Legacy message count indicator (for non-paginated view) */} {!hasMoreMessages && chatMessages.length > visibleMessageCount && (
{t('session.messages.showingLast', { count: visibleMessageCount, total: chatMessages.length })} | {' | '}
)} {visibleMessages.map((message, index) => { const prevMessage = index > 0 ? visibleMessages[index - 1] : null; return ( ); })} )}
); }