Tell the user to hit compact (#3851)

Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
Douwe Osinga
2025-08-05 18:12:16 +02:00
committed by GitHub
parent 0220880ea1
commit 861bd0a71d
5 changed files with 38 additions and 40 deletions
+2 -2
View File
@@ -127,7 +127,7 @@ function BaseChatContent({
summaryContent, summaryContent,
summarizedThread, summarizedThread,
isSummaryModalOpen, isSummaryModalOpen,
isLoadingSummary, isLoadingCompaction,
resetMessagesWithSummary, resetMessagesWithSummary,
closeSummaryModal, closeSummaryModal,
updateSummary, updateSummary,
@@ -513,7 +513,7 @@ function BaseChatContent({
{chatState !== ChatState.Idle && ( {chatState !== ChatState.Idle && (
<div className="absolute bottom-1 left-4 z-20 pointer-events-none"> <div className="absolute bottom-1 left-4 z-20 pointer-events-none">
<LoadingGoose <LoadingGoose
message={isLoadingSummary ? 'summarizing conversation…' : undefined} message={isLoadingCompaction ? 'summarizing conversation…' : undefined}
chatState={chatState} chatState={chatState}
/> />
</div> </div>
+9 -9
View File
@@ -12,7 +12,7 @@ import { Message } from '../types/message';
import { DirSwitcher } from './bottom_menu/DirSwitcher'; import { DirSwitcher } from './bottom_menu/DirSwitcher';
import ModelsBottomBar from './settings/models/bottom_bar/ModelsBottomBar'; import ModelsBottomBar from './settings/models/bottom_bar/ModelsBottomBar';
import { BottomMenuModeSelection } from './bottom_menu/BottomMenuModeSelection'; import { BottomMenuModeSelection } from './bottom_menu/BottomMenuModeSelection';
import { ManualSummarizeButton } from './context_management/ManualSummaryButton'; import { ManualCompactButton } from './context_management/ManualCompactButton';
import { AlertType, useAlerts } from './alerts'; import { AlertType, useAlerts } from './alerts';
import { useToolCount } from './alerts/useToolCount'; import { useToolCount } from './alerts/useToolCount';
import { useConfig } from './ConfigContext'; import { useConfig } from './ConfigContext';
@@ -110,7 +110,7 @@ export default function ChatInput({
const { alerts, addAlert, clearAlerts } = useAlerts(); const { alerts, addAlert, clearAlerts } = useAlerts();
const dropdownRef = useRef<HTMLDivElement>(null); const dropdownRef = useRef<HTMLDivElement>(null);
const toolCount = useToolCount(); const toolCount = useToolCount();
const { isLoadingSummary } = useChatContextManager(); const { isLoadingCompaction } = useChatContextManager();
const { getProviders, read } = useConfig(); const { getProviders, read } = useConfig();
const { getCurrentModelAndProvider, currentModel, currentProvider } = useModelAndProvider(); const { getCurrentModelAndProvider, currentModel, currentProvider } = useModelAndProvider();
const [tokenLimit, setTokenLimit] = useState<number>(TOKEN_LIMIT_DEFAULT); const [tokenLimit, setTokenLimit] = useState<number>(TOKEN_LIMIT_DEFAULT);
@@ -416,7 +416,7 @@ export default function ChatInput({
// Only show warning alert when approaching limit // Only show warning alert when approaching limit
addAlert({ addAlert({
type: AlertType.Warning, type: AlertType.Warning,
message: `Approaching token limit (${numTokens.toLocaleString()}/${tokenLimit.toLocaleString()}) \n You're reaching the model's conversation limit. The session will be saved — copy anything important and start a new one to continue.`, message: `Approaching token limit (${numTokens.toLocaleString()}/${tokenLimit.toLocaleString()}) \n You're reaching the model's conversation limit. Consider compacting the conversation to continue.`,
autoShow: true, // Auto-show token limit warnings autoShow: true, // Auto-show token limit warnings
}); });
} else { } else {
@@ -880,7 +880,7 @@ export default function ChatInput({
evt.preventDefault(); evt.preventDefault();
const canSubmit = const canSubmit =
!isLoading && !isLoading &&
!isLoadingSummary && !isLoadingCompaction &&
(displayValue.trim() || (displayValue.trim() ||
pastedImages.some((img) => img.filePath && !img.error && !img.isLoading) || pastedImages.some((img) => img.filePath && !img.error && !img.isLoading) ||
allDroppedFiles.some((file) => !file.error && !file.isLoading)); allDroppedFiles.some((file) => !file.error && !file.isLoading));
@@ -894,7 +894,7 @@ export default function ChatInput({
e.preventDefault(); e.preventDefault();
const canSubmit = const canSubmit =
!isLoading && !isLoading &&
!isLoadingSummary && !isLoadingCompaction &&
(displayValue.trim() || (displayValue.trim() ||
pastedImages.some((img) => img.filePath && !img.error && !img.isLoading) || pastedImages.some((img) => img.filePath && !img.error && !img.isLoading) ||
allDroppedFiles.some((file) => !file.error && !file.isLoading)); allDroppedFiles.some((file) => !file.error && !file.isLoading));
@@ -1073,7 +1073,7 @@ export default function ChatInput({
isAnyDroppedFileLoading || isAnyDroppedFileLoading ||
isRecording || isRecording ||
isTranscribing || isTranscribing ||
isLoadingSummary isLoadingCompaction
} }
className={`rounded-full px-10 py-2 flex items-center gap-2 ${ className={`rounded-full px-10 py-2 flex items-center gap-2 ${
!hasSubmittableContent || !hasSubmittableContent ||
@@ -1081,12 +1081,12 @@ export default function ChatInput({
isAnyDroppedFileLoading || isAnyDroppedFileLoading ||
isRecording || isRecording ||
isTranscribing || isTranscribing ||
isLoadingSummary isLoadingCompaction
? 'bg-slate-600 text-white cursor-not-allowed opacity-50 border-slate-600' ? 'bg-slate-600 text-white cursor-not-allowed opacity-50 border-slate-600'
: 'bg-slate-600 text-white hover:bg-slate-700 border-slate-600 hover:cursor-pointer' : 'bg-slate-600 text-white hover:bg-slate-700 border-slate-600 hover:cursor-pointer'
}`} }`}
title={ title={
isLoadingSummary isLoadingCompaction
? 'Summarizing conversation...' ? 'Summarizing conversation...'
: isAnyImageLoading : isAnyImageLoading
? 'Waiting for images to save...' ? 'Waiting for images to save...'
@@ -1287,7 +1287,7 @@ export default function ChatInput({
<div className="w-px h-4 bg-border-default mx-2" /> <div className="w-px h-4 bg-border-default mx-2" />
<BottomMenuModeSelection /> <BottomMenuModeSelection />
{messages.length > 0 && ( {messages.length > 0 && (
<ManualSummarizeButton <ManualCompactButton
messages={messages} messages={messages}
isLoading={isLoading} isLoading={isLoading}
setMessages={setMessages} setMessages={setMessages}
@@ -11,7 +11,7 @@ interface ChatContextManagerState {
summaryContent: string; summaryContent: string;
summarizedThread: Message[]; summarizedThread: Message[];
isSummaryModalOpen: boolean; isSummaryModalOpen: boolean;
isLoadingSummary: boolean; isLoadingCompaction: boolean;
errorLoadingSummary: boolean; errorLoadingSummary: boolean;
preparingManualSummary: boolean; preparingManualSummary: boolean;
} }
@@ -32,10 +32,7 @@ interface ChatContextManagerActions {
hasSummarizationRequestedContent: (message: Message) => boolean; hasSummarizationRequestedContent: (message: Message) => boolean;
getContextHandlerType: (message: Message) => 'contextLengthExceeded' | 'summarizationRequested'; getContextHandlerType: (message: Message) => 'contextLengthExceeded' | 'summarizationRequested';
handleContextLengthExceeded: (messages: Message[]) => Promise<void>; handleContextLengthExceeded: (messages: Message[]) => Promise<void>;
handleManualSummarization: ( handleManualCompaction: (messages: Message[], setMessages: (messages: Message[]) => void) => void;
messages: Message[],
setMessages: (messages: Message[]) => void
) => void;
} }
// Create the context // Create the context
@@ -50,12 +47,12 @@ export const ChatContextManagerProvider: React.FC<{ children: React.ReactNode }>
const [summaryContent, setSummaryContent] = useState<string>(''); const [summaryContent, setSummaryContent] = useState<string>('');
const [summarizedThread, setSummarizedThread] = useState<Message[]>([]); const [summarizedThread, setSummarizedThread] = useState<Message[]>([]);
const [isSummaryModalOpen, setIsSummaryModalOpen] = useState<boolean>(false); const [isSummaryModalOpen, setIsSummaryModalOpen] = useState<boolean>(false);
const [isLoadingSummary, setIsLoadingSummary] = useState<boolean>(false); const [isLoadingCompaction, setIsLoadingCompaction] = useState<boolean>(false);
const [errorLoadingSummary, setErrorLoadingSummary] = useState<boolean>(false); const [errorLoadingSummary, setErrorLoadingSummary] = useState<boolean>(false);
const [preparingManualSummary, setPreparingManualSummary] = useState<boolean>(false); const [preparingManualSummary, setPreparingManualSummary] = useState<boolean>(false);
const handleContextLengthExceeded = async (messages: Message[]): Promise<void> => { const handleContextLengthExceeded = async (messages: Message[]): Promise<void> => {
setIsLoadingSummary(true); setIsLoadingCompaction(true);
setErrorLoadingSummary(false); setErrorLoadingSummary(false);
setPreparingManualSummary(true); setPreparingManualSummary(true);
@@ -79,17 +76,17 @@ export const ChatContextManagerProvider: React.FC<{ children: React.ReactNode }>
setSummarizedThread(convertedMessages); setSummarizedThread(convertedMessages);
} }
setIsLoadingSummary(false); setIsLoadingCompaction(false);
} catch (err) { } catch (err) {
console.error('Error handling context length exceeded:', err); console.error('Error handling context length exceeded:', err);
setErrorLoadingSummary(true); setErrorLoadingSummary(true);
setIsLoadingSummary(false); setIsLoadingCompaction(false);
} finally { } finally {
setPreparingManualSummary(false); setPreparingManualSummary(false);
} }
}; };
const handleManualSummarization = ( const handleManualCompaction = (
messages: Message[], messages: Message[],
setMessages: (messages: Message[]) => void setMessages: (messages: Message[]) => void
): void => { ): void => {
@@ -242,7 +239,7 @@ export const ChatContextManagerProvider: React.FC<{ children: React.ReactNode }>
summaryContent, summaryContent,
summarizedThread, summarizedThread,
isSummaryModalOpen, isSummaryModalOpen,
isLoadingSummary, isLoadingCompaction,
errorLoadingSummary, errorLoadingSummary,
preparingManualSummary, preparingManualSummary,
@@ -256,7 +253,7 @@ export const ChatContextManagerProvider: React.FC<{ children: React.ReactNode }>
hasSummarizationRequestedContent, hasSummarizationRequestedContent,
getContextHandlerType, getContextHandlerType,
handleContextLengthExceeded, handleContextLengthExceeded,
handleManualSummarization, handleManualCompaction,
}; };
return ( return (
@@ -22,7 +22,7 @@ export const ContextHandler: React.FC<ContextHandlerProps> = ({
}) => { }) => {
const { const {
summaryContent, summaryContent,
isLoadingSummary, isLoadingCompaction,
errorLoadingSummary, errorLoadingSummary,
openSummaryModal, openSummaryModal,
handleContextLengthExceeded, handleContextLengthExceeded,
@@ -62,13 +62,13 @@ export const ContextHandler: React.FC<ContextHandlerProps> = ({
// Scroll when summarization starts (loading state) // Scroll when summarization starts (loading state)
useEffect(() => { useEffect(() => {
if (isLoadingSummary && shouldAllowSummaryInteraction) { if (isLoadingCompaction && shouldAllowSummaryInteraction) {
// Delay the scroll slightly to ensure the loading content is rendered // Delay the scroll slightly to ensure the loading content is rendered
setTimeout(() => { setTimeout(() => {
onSummaryComplete?.(); onSummaryComplete?.();
}, 100); }, 100);
} }
}, [isLoadingSummary, shouldAllowSummaryInteraction, onSummaryComplete]); }, [isLoadingCompaction, shouldAllowSummaryInteraction, onSummaryComplete]);
// Function to trigger the async operation properly // Function to trigger the async operation properly
const triggerContextLengthExceeded = () => { const triggerContextLengthExceeded = () => {
@@ -234,7 +234,7 @@ export const ContextHandler: React.FC<ContextHandlerProps> = ({
<div className="flex-grow border-t border-gray-300"></div> <div className="flex-grow border-t border-gray-300"></div>
</div> </div>
{isLoadingSummary && shouldAllowSummaryInteraction {isLoadingCompaction && shouldAllowSummaryInteraction
? renderLoadingState() ? renderLoadingState()
: renderContentState()} : renderContentState()}
</div> </div>
@@ -14,18 +14,18 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/Tooltip';
import { useChatContextManager } from './ChatContextManager'; import { useChatContextManager } from './ChatContextManager';
import { Message } from '../../types/message'; import { Message } from '../../types/message';
interface ManualSummarizeButtonProps { interface ManualCompactButtonProps {
messages: Message[]; messages: Message[];
isLoading?: boolean; // need this prop to know if Goose is responding isLoading?: boolean; // need this prop to know if Goose is responding
setMessages: (messages: Message[]) => void; // context management is triggered via special message content types setMessages: (messages: Message[]) => void; // context management is triggered via special message content types
} }
export const ManualSummarizeButton: React.FC<ManualSummarizeButtonProps> = ({ export const ManualCompactButton: React.FC<ManualCompactButtonProps> = ({
messages, messages,
isLoading = false, isLoading = false,
setMessages, setMessages,
}) => { }) => {
const { handleManualSummarization, isLoadingSummary } = useChatContextManager(); const { handleManualCompaction, isLoadingCompaction } = useChatContextManager();
const [isConfirmationOpen, setIsConfirmationOpen] = useState(false); const [isConfirmationOpen, setIsConfirmationOpen] = useState(false);
@@ -33,13 +33,13 @@ export const ManualSummarizeButton: React.FC<ManualSummarizeButtonProps> = ({
setIsConfirmationOpen(true); setIsConfirmationOpen(true);
}; };
const handleSummarize = async () => { const handleCompaction = async () => {
setIsConfirmationOpen(false); setIsConfirmationOpen(false);
try { try {
handleManualSummarization(messages, setMessages); handleManualCompaction(messages, setMessages);
} catch (error) { } catch (error) {
console.error('Error in handleSummarize:', error); console.error('Error in handleCompaction:', error);
} }
}; };
@@ -57,17 +57,17 @@ export const ManualSummarizeButton: React.FC<ManualSummarizeButtonProps> = ({
type="button" type="button"
className={cn( className={cn(
'flex items-center justify-center text-text-default/70 hover:text-text-default text-xs cursor-pointer transition-colors', 'flex items-center justify-center text-text-default/70 hover:text-text-default text-xs cursor-pointer transition-colors',
(isLoadingSummary || isLoading) && (isLoadingCompaction || isLoading) &&
'cursor-not-allowed text-text-default/30 hover:text-text-default/30 opacity-50' 'cursor-not-allowed text-text-default/30 hover:text-text-default/30 opacity-50'
)} )}
onClick={handleClick} onClick={handleClick}
disabled={isLoadingSummary || isLoading} disabled={isLoadingCompaction || isLoading}
> >
<ScrollText size={16} /> <ScrollText size={16} />
</button> </button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent> <TooltipContent>
{isLoadingSummary ? 'Summarizing conversation...' : 'Summarize conversation context'} {isLoadingCompaction ? 'Compacting conversation...' : 'Compact conversation context'}
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</div> </div>
@@ -78,10 +78,11 @@ export const ManualSummarizeButton: React.FC<ManualSummarizeButtonProps> = ({
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center gap-2"> <DialogTitle className="flex items-center gap-2">
<ScrollText className="text-iconStandard" size={24} /> <ScrollText className="text-iconStandard" size={24} />
Summarize Conversation Compact Conversation
</DialogTitle> </DialogTitle>
<DialogDescription> <DialogDescription>
This will summarize your conversation history to save context space. This will compact your conversation by summarizing the context into a single message
and will help you save context space for future interactions.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
@@ -97,8 +98,8 @@ export const ManualSummarizeButton: React.FC<ManualSummarizeButtonProps> = ({
<Button type="button" variant="outline" onClick={handleClose}> <Button type="button" variant="outline" onClick={handleClose}>
Cancel Cancel
</Button> </Button>
<Button type="button" onClick={handleSummarize}> <Button type="button" onClick={handleCompaction}>
Summarize Compact Conversation
</Button> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>