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 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 { Tooltip, TooltipContent, TooltipTrigger } from './ui/Tooltip';
import { Button } from './ui/button'; import { Button } from './ui/button';
import type { View } from '../App'; import type { View } from '../App';
@@ -12,7 +12,6 @@ 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 { 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 +109,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 { isLoadingCompaction } = useChatContextManager(); const { isLoadingCompaction, handleManualCompaction } = 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);
@@ -420,7 +419,7 @@ export default function ChatInput({
autoShow: true, // Auto-show token limit warnings autoShow: true, // Auto-show token limit warnings
}); });
} else { } else {
// Show info alert only when not in warning/error state // Show info alert with summarize button
addAlert({ addAlert({
type: AlertType.Info, type: AlertType.Info,
message: 'Context window', message: 'Context window',
@@ -428,6 +427,11 @@ export default function ChatInput({
current: numTokens, current: numTokens,
total: tokenLimit, total: tokenLimit,
}, },
showSummarizeButton: true,
onSummarize: () => {
handleManualCompaction(messages, setMessages);
},
summarizeIcon: <ScrollText size={12} />,
}); });
} }
} else if (isTokenLimitLoaded && tokenLimit) { } else if (isTokenLimitLoaded && tokenLimit) {
@@ -439,6 +443,14 @@ export default function ChatInput({
current: 0, current: 0,
total: tokenLimit, 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> </Tooltip>
<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 && (
<ManualCompactButton
messages={messages}
isLoading={isLoading}
setMessages={setMessages}
/>
)}
<div className="w-px h-4 bg-border-default mx-2" /> <div className="w-px h-4 bg-border-default mx-2" />
<div className="flex items-center h-full"> <div className="flex items-center h-full">
<Tooltip> <Tooltip>
@@ -60,6 +60,19 @@ export const AlertBox = ({ alert, className }: AlertBoxProps) => {
: alert.progress!.total} : alert.progress!.total}
</span> </span>
</div> </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> </div>
) : ( ) : (
<> <>
@@ -16,4 +16,7 @@ export interface Alert {
current: number; current: number;
total: number; total: number;
}; };
showSummarizeButton?: boolean;
onSummarize?: () => void;
summarizeIcon?: React.ReactNode;
} }
@@ -1,10 +1,20 @@
import React, { createContext, useContext, useState } from 'react'; import React, { createContext, useContext, useState } from 'react';
import { ScrollText } from 'lucide-react';
import { Message } from '../../types/message'; import { Message } from '../../types/message';
import { import {
manageContextFromBackend, manageContextFromBackend,
convertApiMessageToFrontendMessage, convertApiMessageToFrontendMessage,
createSummarizationRequestMessage, createSummarizationRequestMessage,
} from './index'; } from './index';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '../ui/dialog';
import { Button } from '../ui/button';
// Define the context management interface // Define the context management interface
interface ChatContextManagerState { interface ChatContextManagerState {
@@ -14,6 +24,8 @@ interface ChatContextManagerState {
isLoadingCompaction: boolean; isLoadingCompaction: boolean;
errorLoadingSummary: boolean; errorLoadingSummary: boolean;
preparingManualSummary: boolean; preparingManualSummary: boolean;
isConfirmationOpen: boolean;
pendingCompactionData: { messages: Message[]; setMessages: (messages: Message[]) => void } | null;
} }
interface ChatContextManagerActions { interface ChatContextManagerActions {
@@ -50,6 +62,11 @@ export const ChatContextManagerProvider: React.FC<{ children: React.ReactNode }>
const [isLoadingCompaction, setIsLoadingCompaction] = 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 [isConfirmationOpen, setIsConfirmationOpen] = useState<boolean>(false);
const [pendingCompactionData, setPendingCompactionData] = useState<{
messages: Message[];
setMessages: (messages: Message[]) => void;
} | null>(null);
const handleContextLengthExceeded = async (messages: Message[]): Promise<void> => { const handleContextLengthExceeded = async (messages: Message[]): Promise<void> => {
setIsLoadingCompaction(true); setIsLoadingCompaction(true);
@@ -90,6 +107,16 @@ export const ChatContextManagerProvider: React.FC<{ children: React.ReactNode }>
messages: Message[], messages: Message[],
setMessages: (messages: Message[]) => void setMessages: (messages: Message[]) => void
): 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 // add some messages to the message thread
// these messages will be filtered out in chat view // 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 // 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 // add the message to the message thread
setMessages([...messages, summarizationRequest]); setMessages([...messages, summarizationRequest]);
setIsConfirmationOpen(false);
setPendingCompactionData(null);
};
const handleCompactionCancel = () => {
setIsConfirmationOpen(false);
setPendingCompactionData(null);
}; };
const updateSummary = (newSummaryContent: string) => { const updateSummary = (newSummaryContent: string) => {
@@ -242,6 +277,8 @@ export const ChatContextManagerProvider: React.FC<{ children: React.ReactNode }>
isLoadingCompaction, isLoadingCompaction,
errorLoadingSummary, errorLoadingSummary,
preparingManualSummary, preparingManualSummary,
isConfirmationOpen,
pendingCompactionData,
// Actions // Actions
updateSummary, updateSummary,
@@ -259,6 +296,39 @@ export const ChatContextManagerProvider: React.FC<{ children: React.ReactNode }>
return ( return (
<ChatContextManagerContext.Provider value={value}> <ChatContextManagerContext.Provider value={value}>
{children} {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> </ChatContextManagerContext.Provider>
); );
}; };
@@ -72,7 +72,7 @@ export const ManualCompactButton: React.FC<ManualCompactButtonProps> = ({
</Tooltip> </Tooltip>
</div> </div>
{/* Confirmation Modal */} {/* Summarization Confirmation Modal */}
<Dialog open={isConfirmationOpen} onOpenChange={handleClose}> <Dialog open={isConfirmationOpen} onOpenChange={handleClose}>
<DialogContent className="sm:max-w-[500px]"> <DialogContent className="sm:max-w-[500px]">
<DialogHeader> <DialogHeader>