Goose Simple Compact UX (#4202)
Co-authored-by: David Katz <dkatz@squareup.com>
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user