Goose Simple Compact UX (#4202)

Co-authored-by: David Katz <dkatz@squareup.com>
This commit is contained in:
Alex Hancock
2025-08-26 18:12:02 -04:00
committed by GitHub
parent a3097f5250
commit a4fc5ec4f1
27 changed files with 2608 additions and 1127 deletions
+28 -53
View File
@@ -21,7 +21,7 @@ import { WaveformVisualizer } from './WaveformVisualizer';
import { toastError } from '../toasts';
import MentionPopover, { FileItemWithMatch } from './MentionPopover';
import { useDictationSettings } from '../hooks/useDictationSettings';
import { useChatContextManager } from './context_management/ChatContextManager';
import { useContextManager } from './context_management/ContextManager';
import { useChatContext } from '../contexts/ChatContext';
import { COST_TRACKING_ENABLED } from '../updates';
import { CostTracker } from './bottom_menu/CostTracker';
@@ -50,7 +50,6 @@ const MAX_IMAGE_SIZE_MB = 5;
// Constants for token and tool alerts
const TOKEN_LIMIT_DEFAULT = 128000; // fallback for custom models that the backend doesn't know about
const TOKEN_WARNING_THRESHOLD = 0.8; // warning shows at 80% of the token limit
const TOOLS_MAX_SUGGESTED = 60; // max number of tools before we show a warning
interface ModelLimit {
@@ -85,6 +84,8 @@ interface ChatInputProps {
recipeAccepted?: boolean;
initialPrompt?: string;
autoSubmit: boolean;
setAncestorMessages?: (messages: Message[]) => void;
append?: (message: Message) => void;
}
export default function ChatInput({
@@ -108,6 +109,8 @@ export default function ChatInput({
recipeAccepted,
initialPrompt,
autoSubmit = false,
append,
setAncestorMessages,
}: ChatInputProps) {
const [_value, setValue] = useState(initialValue);
const [displayValue, setDisplayValue] = useState(initialValue); // For immediate visual feedback
@@ -129,7 +132,7 @@ export default function ChatInput({
null
) as React.RefObject<HTMLDivElement>;
const toolCount = useToolCount();
const { isLoadingCompaction, handleManualCompaction } = useChatContextManager();
const { isCompacting, handleManualCompaction } = useContextManager();
const { getProviders, read } = useConfig();
const { getCurrentModelAndProvider, currentModel, currentProvider } = useModelAndProvider();
const [tokenLimit, setTokenLimit] = useState<number>(TOKEN_LIMIT_DEFAULT);
@@ -511,55 +514,27 @@ export default function ChatInput({
useEffect(() => {
clearAlerts();
// Always show token alerts if we have loaded the real token limit and have tokens
if (isTokenLimitLoaded && tokenLimit && numTokens && numTokens > 0) {
if (numTokens >= tokenLimit) {
// Only show error alert when limit reached
addAlert({
type: AlertType.Error,
message: `Token limit reached (${numTokens.toLocaleString()}/${tokenLimit.toLocaleString()}) \n You've reached the model's conversation limit. The session will be saved — copy anything important and start a new one to continue.`,
autoShow: true, // Auto-show token limit errors
});
} else if (numTokens >= tokenLimit * TOKEN_WARNING_THRESHOLD) {
// Only show warning alert when approaching limit
addAlert({
type: AlertType.Warning,
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
});
} else {
// Show info alert with summarize button
addAlert({
type: AlertType.Info,
message: 'Context window',
progress: {
current: numTokens,
total: tokenLimit,
},
showSummarizeButton: true,
onSummarize: () => {
handleManualCompaction(messages, setMessages);
},
summarizeIcon: <ScrollText size={12} />,
});
}
} else if (isTokenLimitLoaded && tokenLimit) {
// Always show context window info even when no tokens are present (start of conversation)
// Show alert when either there is registered token usage, or we know the limit
if ((numTokens && numTokens > 0) || (isTokenLimitLoaded && tokenLimit)) {
// in these conditions we want it to be present but disabled
const compactButtonDisabled = !numTokens || isCompacting;
addAlert({
type: AlertType.Info,
message: 'Context window',
progress: {
current: 0,
current: numTokens || 0,
total: tokenLimit,
},
showSummarizeButton: messages.length > 0,
onSummarize:
messages.length > 0
? () => {
handleManualCompaction(messages, setMessages);
}
: undefined,
summarizeIcon: messages.length > 0 ? <ScrollText size={12} /> : undefined,
showCompactButton: true,
compactButtonDisabled,
onCompact: () => {
// Hide the alert popup by dispatching a custom event that the popover can listen to
// Importantly, this leaves the alert so the dot still shows up, but hides the popover
window.dispatchEvent(new CustomEvent('hide-alert-popover'));
handleManualCompaction(messages, setMessages, append, setAncestorMessages);
},
compactIcon: <ScrollText size={12} />,
});
}
@@ -577,7 +552,7 @@ export default function ChatInput({
}
// We intentionally omit setView as it shouldn't trigger a re-render of alerts
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [numTokens, toolCount, tokenLimit, isTokenLimitLoaded, addAlert, clearAlerts]);
}, [numTokens, toolCount, tokenLimit, isTokenLimitLoaded, addAlert, isCompacting, clearAlerts]);
// Cleanup effect for component unmount - prevent memory leaks
useEffect(() => {
@@ -1086,7 +1061,7 @@ export default function ChatInput({
const canSubmit =
!isLoading &&
!isLoadingCompaction &&
!isCompacting &&
agentIsReady &&
(displayValue.trim() ||
pastedImages.some((img) => img.filePath && !img.error && !img.isLoading) ||
@@ -1101,7 +1076,7 @@ export default function ChatInput({
e.preventDefault();
const canSubmit =
!isLoading &&
!isLoadingCompaction &&
!isCompacting &&
agentIsReady &&
(displayValue.trim() ||
pastedImages.some((img) => img.filePath && !img.error && !img.isLoading) ||
@@ -1380,7 +1355,7 @@ export default function ChatInput({
isAnyDroppedFileLoading ||
isRecording ||
isTranscribing ||
isLoadingCompaction ||
isCompacting ||
!agentIsReady
}
className={`rounded-full px-10 py-2 flex items-center gap-2 ${
@@ -1389,7 +1364,7 @@ export default function ChatInput({
isAnyDroppedFileLoading ||
isRecording ||
isTranscribing ||
isLoadingCompaction ||
isCompacting ||
!agentIsReady
? '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'
@@ -1402,8 +1377,8 @@ export default function ChatInput({
</TooltipTrigger>
<TooltipContent>
<p>
{isLoadingCompaction
? 'Summarizing conversation...'
{isCompacting
? 'Compacting conversation...'
: isAnyImageLoading
? 'Waiting for images to save...'
: isAnyDroppedFileLoading