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
+52 -126
View File
@@ -20,7 +20,6 @@
* - Integrates with multiple custom hooks for separation of concerns:
* - useChatEngine: Core chat functionality and API integration
* - useRecipeManager: Recipe/agent configuration management
* - useSessionContinuation: Session persistence and resumption
* - useFileDrop: Drag-and-drop file handling with previews
* - useCostTracking: Token usage and cost calculation
*
@@ -51,12 +50,8 @@ import LoadingGoose from './LoadingGoose';
import RecipeActivities from './RecipeActivities';
import PopularChatTopics from './PopularChatTopics';
import ProgressiveMessageList from './ProgressiveMessageList';
import { SessionSummaryModal } from './context_management/SessionSummaryModal';
import {
ChatContextManagerProvider,
useChatContextManager,
} from './context_management/ChatContextManager';
import { View, ViewOptions } from '../utils/navigationUtils';
import { ContextManagerProvider, useContextManager } from './context_management/ContextManager';
import { MainPanelLayout } from './Layout/MainPanelLayout';
import ChatInput from './ChatInput';
import { ScrollArea, ScrollAreaHandle } from './ui/scroll-area';
@@ -64,7 +59,6 @@ import { RecipeWarningModal } from './ui/RecipeWarningModal';
import ParameterInputModal from './ParameterInputModal';
import { useChatEngine } from '../hooks/useChatEngine';
import { useRecipeManager } from '../hooks/useRecipeManager';
import { useSessionContinuation } from '../hooks/useSessionContinuation';
import { useFileDrop } from '../hooks/useFileDrop';
import { useCostTracking } from '../hooks/useCostTracking';
import { Message } from '../types/message';
@@ -125,21 +119,12 @@ function BaseChatContent({
const [hasStartedUsingRecipe, setHasStartedUsingRecipe] = React.useState(false);
const [currentRecipeTitle, setCurrentRecipeTitle] = React.useState<string | null>(null);
const {
summaryContent,
summarizedThread,
isSummaryModalOpen,
isLoadingCompaction,
resetMessagesWithSummary,
closeSummaryModal,
updateSummary,
} = useChatContextManager();
const { isCompacting, handleManualCompaction } = useContextManager();
// Use shared chat engine
const {
messages,
filteredMessages,
ancestorMessages,
setAncestorMessages,
append,
chatState,
@@ -156,7 +141,6 @@ function BaseChatContent({
localOutputTokens,
commandHistory,
toolCallNotifications,
updateMessageStreamBody,
sessionMetadata,
isUserMessage,
clearError,
@@ -173,9 +157,6 @@ function BaseChatContent({
if (recipeConfig) {
setHasStartedUsingRecipe(true);
}
// Create new session after message is sent if needed
createNewSessionIfNeeded();
},
enableLocalStorage,
});
@@ -233,14 +214,6 @@ function BaseChatContent({
});
}, [handleAutoExecution, append, chatState]);
// Use shared session continuation
const { createNewSessionIfNeeded } = useSessionContinuation({
chat,
setChat,
summarizedThread,
updateMessageStreamBody,
});
// Use shared file drop
const { droppedFiles, setDroppedFiles, handleDrop, handleDragOver } = useFileDrop();
@@ -260,7 +233,7 @@ function BaseChatContent({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // Empty dependency array means this runs once on mount
// Handle submit with summary reset support
// Handle submit
const handleSubmit = (e: React.FormEvent) => {
const customEvent = e as unknown as CustomEvent;
const combinedTextFromInput = customEvent.detail?.value || '';
@@ -270,35 +243,12 @@ function BaseChatContent({
setHasStartedUsingRecipe(true);
}
const onSummaryReset =
summarizedThread.length > 0
? () => {
resetMessagesWithSummary(
messages,
setMessages,
ancestorMessages,
setAncestorMessages,
summaryContent
);
}
: undefined;
// Call the callback if provided (for Hub to handle navigation)
if (onMessageSubmit && combinedTextFromInput.trim()) {
onMessageSubmit(combinedTextFromInput);
}
engineHandleSubmit(combinedTextFromInput, onSummaryReset);
// Auto-scroll to bottom after submitting
if (onSummaryReset) {
// If we're resetting with summary, delay the scroll a bit more
setTimeout(() => {
if (scrollRef.current?.scrollToBottom) {
scrollRef.current.scrollToBottom();
}
}, 200);
}
engineHandleSubmit(combinedTextFromInput);
};
// Wrapper for append that tracks recipe usage
@@ -433,67 +383,49 @@ function BaseChatContent({
</SearchView>
)}
{error &&
!(error as Error & { isTokenLimitError?: boolean }).isTokenLimitError && (
<>
<div className="flex flex-col items-center justify-center p-4">
<div className="text-red-700 dark:text-red-300 bg-red-400/50 p-3 rounded-lg mb-2">
{error.message || 'Honk! Goose experienced an error while responding'}
{error && (
<>
<div className="flex flex-col items-center justify-center p-4">
<div className="text-red-700 dark:text-red-300 bg-red-400/50 p-3 rounded-lg mb-2">
{error.message || 'Honk! Goose experienced an error while responding'}
</div>
{/* Action buttons for all errors including token limit errors */}
<div className="flex gap-2 mt-2">
<div
className="px-3 py-2 text-center whitespace-nowrap cursor-pointer text-textStandard border border-borderSubtle hover:bg-bgSubtle rounded-full inline-block transition-all duration-150"
onClick={async () => {
clearError();
await handleManualCompaction(
messages,
setMessages,
append,
setAncestorMessages
);
}}
>
Summarize Conversation
</div>
{/* Action buttons for non-token-limit errors */}
<div className="flex gap-2 mt-2">
<div
className="px-3 py-2 text-center whitespace-nowrap cursor-pointer text-textStandard border border-borderSubtle hover:bg-bgSubtle rounded-full inline-block transition-all duration-150"
onClick={async () => {
// Create a contextLengthExceeded message similar to token limit errors
const contextMessage: Message = {
id: `context-${Date.now()}`,
role: 'assistant',
created: Math.floor(Date.now() / 1000),
content: [
{
type: 'contextLengthExceeded',
msg: 'Summarization requested due to error. Creating summary to help resolve the issue.',
},
],
display: true,
sendToLLM: false,
};
// Add the context message to trigger ContextHandler
const updatedMessages = [...messages, contextMessage];
setMessages(updatedMessages);
// Clear the error state since we're handling it with summarization
clearError();
}}
>
Summarize Conversation
</div>
<div
className="px-3 py-2 text-center whitespace-nowrap cursor-pointer text-textStandard border border-borderSubtle hover:bg-bgSubtle rounded-full inline-block transition-all duration-150"
onClick={async () => {
// Find the last user message
const lastUserMessage = messages.reduceRight(
(found, m) => found || (m.role === 'user' ? m : null),
null as Message | null
);
if (lastUserMessage) {
append(lastUserMessage);
}
}}
>
Retry Last Message
</div>
<div
className="px-3 py-2 text-center whitespace-nowrap cursor-pointer text-textStandard border border-borderSubtle hover:bg-bgSubtle rounded-full inline-block transition-all duration-150"
onClick={async () => {
// Find the last user message
const lastUserMessage = messages.reduceRight(
(found, m) => found || (m.role === 'user' ? m : null),
null as Message | null
);
if (lastUserMessage) {
append(lastUserMessage);
}
}}
>
Retry Last Message
</div>
</div>
</>
)}
{/* Token limit errors should be handled by ContextHandler, not shown here */}
{error &&
(error as Error & { isTokenLimitError?: boolean }).isTokenLimitError && <></>}
</div>
</>
)}
<div className="block h-8" />
</>
) : showPopularTopics ? (
@@ -507,10 +439,10 @@ function BaseChatContent({
</ScrollArea>
{/* Fixed loading indicator at bottom left of chat container */}
{chatState !== ChatState.Idle && (
{(chatState !== ChatState.Idle || isCompacting) && (
<div className="absolute bottom-1 left-4 z-20 pointer-events-none">
<LoadingGoose
message={isLoadingCompaction ? 'summarizing conversation' : undefined}
message={isCompacting ? 'goose is compacting the conversation...' : undefined}
chatState={chatState}
/>
</div>
@@ -541,21 +473,13 @@ function BaseChatContent({
recipeAccepted={recipeAccepted}
initialPrompt={initialPrompt}
autoSubmit={autoSubmit}
setAncestorMessages={setAncestorMessages}
append={append}
{...customChatInputProps}
/>
</div>
</MainPanelLayout>
<SessionSummaryModal
isOpen={isSummaryModalOpen}
onClose={closeSummaryModal}
onSave={(editedContent) => {
updateSummary(editedContent);
closeSummaryModal();
}}
summaryContent={summaryContent}
/>
{/* Recipe Warning Modal */}
<RecipeWarningModal
isOpen={isRecipeWarningModalOpen}
@@ -595,14 +519,16 @@ function BaseChatContent({
</div>
</div>
)}
{/* No modals needed for the new simplified context manager */}
</div>
);
}
export default function BaseChat(props: BaseChatProps) {
return (
<ChatContextManagerProvider>
<ContextManagerProvider>
<BaseChatContent {...props} />
</ChatContextManagerProvider>
</ContextManagerProvider>
);
}