Move summarize button inside of context view (#4015)

This commit is contained in:
David Katz
2025-08-12 11:36:29 -04:00
committed by GitHub
parent 3201381354
commit dab1415fdd
5 changed files with 103 additions and 12 deletions
+16 -11
View File
@@ -1,5 +1,5 @@
import React, { useRef, useState, useEffect, useMemo } from 'react';
import { FolderKey } from 'lucide-react';
import { FolderKey, ScrollText } from 'lucide-react';
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/Tooltip';
import { Button } from './ui/button';
import type { View } from '../App';
@@ -12,7 +12,6 @@ import { Message } from '../types/message';
import { DirSwitcher } from './bottom_menu/DirSwitcher';
import ModelsBottomBar from './settings/models/bottom_bar/ModelsBottomBar';
import { BottomMenuModeSelection } from './bottom_menu/BottomMenuModeSelection';
import { ManualCompactButton } from './context_management/ManualCompactButton';
import { AlertType, useAlerts } from './alerts';
import { useToolCount } from './alerts/useToolCount';
import { useConfig } from './ConfigContext';
@@ -110,7 +109,7 @@ export default function ChatInput({
const { alerts, addAlert, clearAlerts } = useAlerts();
const dropdownRef = useRef<HTMLDivElement>(null);
const toolCount = useToolCount();
const { isLoadingCompaction } = useChatContextManager();
const { isLoadingCompaction, handleManualCompaction } = useChatContextManager();
const { getProviders, read } = useConfig();
const { getCurrentModelAndProvider, currentModel, currentProvider } = useModelAndProvider();
const [tokenLimit, setTokenLimit] = useState<number>(TOKEN_LIMIT_DEFAULT);
@@ -420,7 +419,7 @@ export default function ChatInput({
autoShow: true, // Auto-show token limit warnings
});
} else {
// Show info alert only when not in warning/error state
// Show info alert with summarize button
addAlert({
type: AlertType.Info,
message: 'Context window',
@@ -428,6 +427,11 @@ export default function ChatInput({
current: numTokens,
total: tokenLimit,
},
showSummarizeButton: true,
onSummarize: () => {
handleManualCompaction(messages, setMessages);
},
summarizeIcon: <ScrollText size={12} />,
});
}
} else if (isTokenLimitLoaded && tokenLimit) {
@@ -439,6 +443,14 @@ export default function ChatInput({
current: 0,
total: tokenLimit,
},
showSummarizeButton: messages.length > 0,
onSummarize:
messages.length > 0
? () => {
handleManualCompaction(messages, setMessages);
}
: undefined,
summarizeIcon: messages.length > 0 ? <ScrollText size={12} /> : undefined,
});
}
@@ -1286,13 +1298,6 @@ export default function ChatInput({
</Tooltip>
<div className="w-px h-4 bg-border-default mx-2" />
<BottomMenuModeSelection />
{messages.length > 0 && (
<ManualCompactButton
messages={messages}
isLoading={isLoading}
setMessages={setMessages}
/>
)}
<div className="w-px h-4 bg-border-default mx-2" />
<div className="flex items-center h-full">
<Tooltip>
@@ -60,6 +60,19 @@ export const AlertBox = ({ alert, className }: AlertBoxProps) => {
: alert.progress!.total}
</span>
</div>
{alert.showSummarizeButton && alert.onSummarize && (
<button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
alert.onSummarize!();
}}
className="flex items-center gap-1.5 text-[11px] hover:opacity-80 cursor-pointer outline-none mt-1"
>
{alert.summarizeIcon}
<span>Summarize now</span>
</button>
)}
</div>
) : (
<>
@@ -16,4 +16,7 @@ export interface Alert {
current: number;
total: number;
};
showSummarizeButton?: boolean;
onSummarize?: () => void;
summarizeIcon?: React.ReactNode;
}
@@ -1,10 +1,20 @@
import React, { createContext, useContext, useState } from 'react';
import { ScrollText } from 'lucide-react';
import { Message } from '../../types/message';
import {
manageContextFromBackend,
convertApiMessageToFrontendMessage,
createSummarizationRequestMessage,
} from './index';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '../ui/dialog';
import { Button } from '../ui/button';
// Define the context management interface
interface ChatContextManagerState {
@@ -14,6 +24,8 @@ interface ChatContextManagerState {
isLoadingCompaction: boolean;
errorLoadingSummary: boolean;
preparingManualSummary: boolean;
isConfirmationOpen: boolean;
pendingCompactionData: { messages: Message[]; setMessages: (messages: Message[]) => void } | null;
}
interface ChatContextManagerActions {
@@ -50,6 +62,11 @@ export const ChatContextManagerProvider: React.FC<{ children: React.ReactNode }>
const [isLoadingCompaction, setIsLoadingCompaction] = useState<boolean>(false);
const [errorLoadingSummary, setErrorLoadingSummary] = useState<boolean>(false);
const [preparingManualSummary, setPreparingManualSummary] = useState<boolean>(false);
const [isConfirmationOpen, setIsConfirmationOpen] = useState<boolean>(false);
const [pendingCompactionData, setPendingCompactionData] = useState<{
messages: Message[];
setMessages: (messages: Message[]) => void;
} | null>(null);
const handleContextLengthExceeded = async (messages: Message[]): Promise<void> => {
setIsLoadingCompaction(true);
@@ -90,6 +107,16 @@ export const ChatContextManagerProvider: React.FC<{ children: React.ReactNode }>
messages: Message[],
setMessages: (messages: Message[]) => void
): void => {
// Store the pending compaction data and open confirmation dialog
setPendingCompactionData({ messages, setMessages });
setIsConfirmationOpen(true);
};
const handleCompactionConfirm = () => {
if (!pendingCompactionData) return;
const { messages, setMessages } = pendingCompactionData;
// add some messages to the message thread
// these messages will be filtered out in chat view
// but they will also be what allows us to render some text in the chatview itself, similar to CLE events
@@ -100,6 +127,14 @@ export const ChatContextManagerProvider: React.FC<{ children: React.ReactNode }>
// add the message to the message thread
setMessages([...messages, summarizationRequest]);
setIsConfirmationOpen(false);
setPendingCompactionData(null);
};
const handleCompactionCancel = () => {
setIsConfirmationOpen(false);
setPendingCompactionData(null);
};
const updateSummary = (newSummaryContent: string) => {
@@ -242,6 +277,8 @@ export const ChatContextManagerProvider: React.FC<{ children: React.ReactNode }>
isLoadingCompaction,
errorLoadingSummary,
preparingManualSummary,
isConfirmationOpen,
pendingCompactionData,
// Actions
updateSummary,
@@ -259,6 +296,39 @@ export const ChatContextManagerProvider: React.FC<{ children: React.ReactNode }>
return (
<ChatContextManagerContext.Provider value={value}>
{children}
{/* Confirmation Modal */}
<Dialog open={isConfirmationOpen} onOpenChange={handleCompactionCancel}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<ScrollText className="text-iconStandard" size={24} />
Compact Conversation
</DialogTitle>
<DialogDescription>
This will compact your conversation by summarizing the context into a single message
and will help you save context space for future interactions.
</DialogDescription>
</DialogHeader>
<div className="py-4">
<p className="text-textStandard">
Previous messages will remain visible but only the summary will be included in the
active context for Goose. This is useful for long conversations that are approaching
the context limit.
</p>
</div>
<DialogFooter className="pt-2">
<Button type="button" variant="outline" onClick={handleCompactionCancel}>
Cancel
</Button>
<Button type="button" onClick={handleCompactionConfirm}>
Compact Conversation
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</ChatContextManagerContext.Provider>
);
};
@@ -72,7 +72,7 @@ export const ManualCompactButton: React.FC<ManualCompactButtonProps> = ({
</Tooltip>
</div>
{/* Confirmation Modal */}
{/* Summarization Confirmation Modal */}
<Dialog open={isConfirmationOpen} onOpenChange={handleClose}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>