mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-07-01 01:23:06 +08:00
fix(chat): refine load all overlay behavior
This commit is contained in:
@@ -338,12 +338,36 @@ export function useChatSessionState({
|
||||
const slot = await sessionStore.fetchMore(selectedSession.id, {
|
||||
limit: MESSAGES_PER_PAGE,
|
||||
});
|
||||
if (!slot || slot.serverMessages.length === 0) return false;
|
||||
if (!slot) return false;
|
||||
if (slot.serverMessages.length === 0) {
|
||||
if (!slot.hasMore) {
|
||||
setHasMoreMessages(false);
|
||||
allMessagesLoadedRef.current = true;
|
||||
setAllMessagesLoaded(true);
|
||||
if (!loadAllOverlayTimerRef.current) {
|
||||
loadAllOverlayTimerRef.current = setTimeout(() => {
|
||||
setShowLoadAllOverlay(false);
|
||||
loadAllOverlayTimerRef.current = null;
|
||||
}, 2500);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
pendingScrollRestoreRef.current = { height: previousScrollHeight, top: previousScrollTop };
|
||||
setHasMoreMessages(slot.hasMore);
|
||||
setTotalMessages(slot.total);
|
||||
setVisibleMessageCount((prev) => prev + MESSAGES_PER_PAGE);
|
||||
if (!slot.hasMore) {
|
||||
allMessagesLoadedRef.current = true;
|
||||
setAllMessagesLoaded(true);
|
||||
if (!loadAllOverlayTimerRef.current) {
|
||||
loadAllOverlayTimerRef.current = setTimeout(() => {
|
||||
setShowLoadAllOverlay(false);
|
||||
loadAllOverlayTimerRef.current = null;
|
||||
}, 2500);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} finally {
|
||||
isLoadingMoreRef.current = false;
|
||||
@@ -371,7 +395,7 @@ export function useChatSessionState({
|
||||
loadAllOverlayTimerRef.current = setTimeout(() => {
|
||||
setShowLoadAllOverlay(false);
|
||||
loadAllOverlayTimerRef.current = null;
|
||||
}, 5000);
|
||||
}, 2500);
|
||||
}
|
||||
} else if (!scrolledNearTop) {
|
||||
wasNearTopRef.current = false;
|
||||
@@ -786,7 +810,7 @@ export function useChatSessionState({
|
||||
setLoadAllJustFinished(false);
|
||||
setShowLoadAllOverlay(false);
|
||||
loadAllFinishedTimerRef.current = null;
|
||||
}, 1000);
|
||||
}, 2500);
|
||||
} else {
|
||||
allMessagesLoadedRef.current = false;
|
||||
setShowLoadAllOverlay(false);
|
||||
|
||||
@@ -15,6 +15,7 @@ import { groupConsecutiveTools, isToolGroupItem } from '../../utils/toolGrouping
|
||||
import MessageComponent from './MessageComponent';
|
||||
import ProviderSelectionEmptyState from './ProviderSelectionEmptyState';
|
||||
import ToolGroupContainer from './ToolGroupContainer';
|
||||
import LoadAllMessagesOverlay from './LoadAllMessagesOverlay';
|
||||
|
||||
interface ChatMessagesPaneProps {
|
||||
scrollContainerRef: RefObject<HTMLDivElement>;
|
||||
@@ -219,35 +220,13 @@ function ChatMessagesPane({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Floating "Load all messages" overlay */}
|
||||
{(showLoadAllOverlay || isLoadingAllMessages || loadAllJustFinished) && (
|
||||
<div className="pointer-events-none sticky top-2 z-20 flex justify-center">
|
||||
{loadAllJustFinished ? (
|
||||
<div className="flex items-center space-x-2 rounded-full bg-green-600 px-4 py-1.5 text-xs font-medium text-white shadow-lg dark:bg-green-500">
|
||||
<svg className="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
<span>{t('session.messages.allLoaded')}</span>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
className="pointer-events-auto flex items-center space-x-2 rounded-full bg-blue-600 px-4 py-1.5 text-xs font-medium text-white shadow-lg transition-all duration-200 hover:scale-105 hover:bg-blue-700 disabled:cursor-wait disabled:opacity-75 dark:bg-blue-500 dark:hover:bg-blue-600"
|
||||
onClick={loadAllMessages}
|
||||
disabled={isLoadingAllMessages}
|
||||
>
|
||||
{isLoadingAllMessages && (
|
||||
<div className="h-3 w-3 animate-spin rounded-full border-2 border-white/30 border-t-white" />
|
||||
)}
|
||||
<span>
|
||||
{isLoadingAllMessages
|
||||
? t('session.messages.loadingAll')
|
||||
: <>{t('session.messages.loadAll')} {totalMessages > 0 && `(${totalMessages})`}</>
|
||||
}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<LoadAllMessagesOverlay
|
||||
showLoadAllOverlay={showLoadAllOverlay}
|
||||
isLoadingAllMessages={isLoadingAllMessages}
|
||||
loadAllJustFinished={loadAllJustFinished}
|
||||
totalMessages={totalMessages}
|
||||
onLoadAllMessages={loadAllMessages}
|
||||
/>
|
||||
|
||||
{/* Legacy message count indicator (for non-paginated view) */}
|
||||
{!hasMoreMessages && chatMessages.length > visibleMessageCount && (
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const loadAllOverlayAnimationStyle = `
|
||||
@keyframes loadAllOverlayAutoFade {
|
||||
0%, 80% { opacity: 1; }
|
||||
100% { opacity: 0; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.load-all-overlay-auto-fade {
|
||||
animation: none !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface LoadAllMessagesOverlayProps {
|
||||
showLoadAllOverlay: boolean;
|
||||
isLoadingAllMessages: boolean;
|
||||
loadAllJustFinished: boolean;
|
||||
totalMessages: number;
|
||||
onLoadAllMessages: () => void;
|
||||
}
|
||||
|
||||
export default function LoadAllMessagesOverlay({
|
||||
showLoadAllOverlay,
|
||||
isLoadingAllMessages,
|
||||
loadAllJustFinished,
|
||||
totalMessages,
|
||||
onLoadAllMessages,
|
||||
}: LoadAllMessagesOverlayProps) {
|
||||
const { t } = useTranslation('chat');
|
||||
|
||||
if (!showLoadAllOverlay && !isLoadingAllMessages && !loadAllJustFinished) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`pointer-events-none sticky top-2 z-20 flex justify-center ${!isLoadingAllMessages ? 'load-all-overlay-auto-fade' : ''}`}
|
||||
style={!isLoadingAllMessages ? { animation: 'loadAllOverlayAutoFade 2500ms ease forwards' } : undefined}
|
||||
>
|
||||
<style>{loadAllOverlayAnimationStyle}</style>
|
||||
{loadAllJustFinished ? (
|
||||
<div className="flex items-center space-x-2 rounded-full bg-green-600 px-4 py-1.5 text-xs font-medium text-white shadow-lg dark:bg-green-500">
|
||||
<svg className="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
<span>{t('session.messages.allLoaded')}</span>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
className="pointer-events-auto flex items-center space-x-2 rounded-full bg-blue-600 px-4 py-1.5 text-xs font-medium text-white shadow-lg transition-all duration-200 hover:scale-105 hover:bg-blue-700 disabled:cursor-wait disabled:opacity-75 dark:bg-blue-500 dark:hover:bg-blue-600"
|
||||
onClick={onLoadAllMessages}
|
||||
disabled={isLoadingAllMessages}
|
||||
>
|
||||
{isLoadingAllMessages && (
|
||||
<div className="h-3 w-3 animate-spin rounded-full border-2 border-white/30 border-t-white" />
|
||||
)}
|
||||
<span>
|
||||
{isLoadingAllMessages
|
||||
? t('session.messages.loadingAll')
|
||||
: <>{t('session.messages.loadAll')} {totalMessages > 0 && `(${totalMessages})`}</>}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user