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