UI update with sidebar and settings tabs (#3288)

Co-authored-by: Nahiyan Khan <nahiyan@squareup.com>
Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
Co-authored-by: Lily Delalande <119957291+lily-de@users.noreply.github.com>
Co-authored-by: Spence <spencrmartin@gmail.com>
Co-authored-by: spencrmartin <spencermartin@squareup.com>
Co-authored-by: Judson Stephenson <Jud@users.noreply.github.com>
Co-authored-by: Max Novich <mnovich@squareup.com>
Co-authored-by: Best Codes <106822363+The-Best-Codes@users.noreply.github.com>
Co-authored-by: caroline-a-mckenzie <cmckenzie@squareup.com>
Co-authored-by: Michael Neale <michael.neale@gmail.com>
This commit is contained in:
Zane
2025-07-15 17:24:41 -07:00
committed by GitHub
parent b22f50d1a1
commit 77ea27f5f5
195 changed files with 20633 additions and 7238 deletions
@@ -1,6 +1,7 @@
import React, { useState, useRef, useEffect } from 'react';
import { Message } from '../../types/message';
import { useChatContextManager } from './ChatContextManager';
import { Button } from '../ui/button';
interface ContextHandlerProps {
messages: Message[];
@@ -8,6 +9,7 @@ interface ContextHandlerProps {
chatId: string;
workingDir: string;
contextType: 'contextLengthExceeded' | 'summarizationRequested';
onSummaryComplete?: () => void; // Add callback for when summary is complete
}
export const ContextHandler: React.FC<ContextHandlerProps> = ({
@@ -16,6 +18,7 @@ export const ContextHandler: React.FC<ContextHandlerProps> = ({
chatId,
workingDir,
contextType,
onSummaryComplete,
}) => {
const {
summaryContent,
@@ -39,6 +42,33 @@ export const ContextHandler: React.FC<ContextHandlerProps> = ({
// Use a ref to track if we've started the fetch
const fetchStartedRef = useRef(false);
const hasCalledSummaryComplete = useRef(false);
// Call onSummaryComplete when summary is ready
useEffect(() => {
if (summaryContent && shouldAllowSummaryInteraction && !hasCalledSummaryComplete.current) {
hasCalledSummaryComplete.current = true;
// Delay the scroll slightly to ensure the content is rendered
setTimeout(() => {
onSummaryComplete?.();
}, 100);
}
// Reset the flag when summary is cleared
if (!summaryContent) {
hasCalledSummaryComplete.current = false;
}
}, [summaryContent, shouldAllowSummaryInteraction, onSummaryComplete]);
// Scroll when summarization starts (loading state)
useEffect(() => {
if (isLoadingSummary && shouldAllowSummaryInteraction) {
// Delay the scroll slightly to ensure the loading content is rendered
setTimeout(() => {
onSummaryComplete?.();
}, 100);
}
}, [isLoadingSummary, shouldAllowSummaryInteraction, onSummaryComplete]);
// Function to trigger the async operation properly
const triggerContextLengthExceeded = () => {
@@ -122,12 +152,9 @@ export const ContextHandler: React.FC<ContextHandlerProps> = ({
? `This conversation has too much information to continue. Extension data often takes up significant space.`
: `Summarization failed. Continue chatting or start a new session.`}
</span>
<button
onClick={openNewSession}
className="text-xs text-textStandard hover:text-textSubtle transition-colors mt-1 flex items-center"
>
<Button onClick={openNewSession} className="text-xs transition-colors mt-1 flex items-center">
Click here to start a new session
</button>
</Button>
</>
);
@@ -138,12 +165,9 @@ export const ContextHandler: React.FC<ContextHandlerProps> = ({
? `Your conversation has exceeded the model's context capacity`
: `Summarization requested`}
</span>
<button
onClick={handleRetry}
className="text-xs text-textStandard hover:text-textSubtle transition-colors mt-1 flex items-center"
>
<Button onClick={handleRetry} className="text-xs transition-colors mt-1 flex items-center">
Retry loading summary
</button>
</Button>
</>
);
@@ -160,15 +184,15 @@ export const ContextHandler: React.FC<ContextHandlerProps> = ({
: `This summary includes key points from your conversation.`}
</span>
{shouldAllowSummaryInteraction && (
<button
<Button
onClick={openSummaryModal}
className="text-xs text-textStandard hover:text-textSubtle transition-colors mt-1 flex items-center"
className="text-xs transition-colors mt-1 flex items-center"
>
View or edit summary{' '}
{isContextLengthExceeded
? '(you may continue your conversation based on the summary)'
: ''}
</button>
</Button>
)}
</>
);
@@ -1,7 +1,16 @@
import React, { useState } from 'react';
import { ScrollText } from 'lucide-react';
import Modal from '../Modal';
import { cn } from '../../utils';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '../ui/dialog';
import { Button } from '../ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/Tooltip';
import { useChatContextManager } from './ChatContextManager';
import { Message } from '../../types/message';
@@ -34,64 +43,66 @@ export const ManualSummarizeButton: React.FC<ManualSummarizeButtonProps> = ({
}
};
// Footer content for the confirmation modal
const footerContent = (
<>
<Button
onClick={handleSummarize}
className="w-full h-[60px] rounded-none border-b border-borderSubtle bg-transparent hover:bg-bgSubtle text-textProminent font-medium text-large"
>
Summarize
</Button>
<Button
onClick={() => setIsConfirmationOpen(false)}
variant="ghost"
className="w-full h-[60px] rounded-none hover:bg-bgSubtle text-textSubtle hover:text-textStandard text-large font-regular"
>
Cancel
</Button>
</>
);
const handleClose = () => {
setIsConfirmationOpen(false);
};
return (
<>
<div className="w-px h-4 bg-border-default mx-2" />
<div className="relative flex items-center">
<button
className={`flex items-center justify-center text-textSubtle hover:text-textStandard h-6 [&_svg]:size-4 ${
isLoadingSummary || isLoading ? 'opacity-50 cursor-not-allowed' : ''
}`}
onClick={handleClick}
disabled={isLoadingSummary || isLoading}
title="Summarize conversation context"
>
<ScrollText size={16} />
</button>
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
className={cn(
'flex items-center justify-center text-text-default/70 hover:text-text-default text-xs cursor-pointer transition-colors',
(isLoadingSummary || isLoading) &&
'cursor-not-allowed text-text-default/30 hover:text-text-default/30 opacity-50'
)}
onClick={handleClick}
disabled={isLoadingSummary || isLoading}
>
<ScrollText size={16} />
</button>
</TooltipTrigger>
<TooltipContent>
{isLoadingSummary ? 'Summarizing conversation...' : 'Summarize conversation context'}
</TooltipContent>
</Tooltip>
</div>
{/* Confirmation Modal */}
{isConfirmationOpen && (
<Modal footer={footerContent} onClose={() => setIsConfirmationOpen(false)}>
<div className="flex flex-col mb-6">
<div>
<Dialog open={isConfirmationOpen} onOpenChange={handleClose}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<ScrollText className="text-iconStandard" size={24} />
</div>
<div className="mt-2">
<h2 className="text-2xl font-regular text-textStandard">Summarize Conversation</h2>
</div>
</div>
<div className="mb-6">
<p className="text-textStandard mb-4">
Summarize Conversation
</DialogTitle>
<DialogDescription>
This will summarize your conversation history to save context space.
</p>
</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>
</Modal>
)}
<DialogFooter className="pt-2">
<Button type="button" variant="outline" onClick={handleClose}>
Cancel
</Button>
<Button type="button" onClick={handleSummarize}>
Summarize
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
);
};
@@ -1,6 +1,14 @@
import React, { useRef, useEffect } from 'react';
import { Card } from '../ui/card';
import { useRef, useEffect } from 'react';
import { Geese } from '../icons/Geese';
import { Button } from '../ui/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '../ui/dialog';
interface SessionSummaryModalProps {
isOpen: boolean;
@@ -9,40 +17,6 @@ interface SessionSummaryModalProps {
summaryContent: string;
}
// This is a specialized version of BaseModal that's wider just for the SessionSummaryModal
function WiderBaseModal({
isOpen,
title,
children,
actions,
}: {
isOpen: boolean;
title: string;
children: React.ReactNode;
actions: React.ReactNode; // Buttons for actions
}) {
if (!isOpen) return null;
return (
<div className="fixed inset-0 bg-black/20 backdrop-blur-sm z-[9999] flex items-center justify-center overflow-y-auto">
<Card className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[640px] max-h-[85vh] bg-white dark:bg-gray-800 rounded-xl shadow-xl overflow-hidden p-[16px] pt-[24px] pb-0 flex flex-col">
<div className="px-4 pb-0 space-y-8 flex-grow overflow-hidden">
{/* Header */}
<div className="flex">
<h2 className="text-2xl font-regular dark:text-white text-gray-900">{title}</h2>
</div>
{/* Content - Make it scrollable */}
{children && <div className="px-2 overflow-y-auto max-h-[60vh]">{children}</div>}
{/* Actions */}
<div className="mt-[8px] ml-[-24px] mr-[-24px] pt-[16px]">{actions}</div>
</div>
</Card>
</div>
);
}
export function SessionSummaryModal({
isOpen,
onClose,
@@ -65,68 +39,49 @@ export function SessionSummaryModal({
onSave(currentText);
};
// Header Component - Icon, Title, and Description
const Header = () => (
<div className="flex flex-col items-center text-center mb-6">
{/* Icon */}
<div className="mb-4">
<Geese width="48" height="50" />
</div>
{/* Title */}
<h2 className="text-xl font-medium text-gray-900 dark:text-white mb-2">Session Summary</h2>
{/* Description */}
<p className="text-sm text-gray-600 dark:text-gray-400 mb-0 max-w-md">
This summary was created to manage your context limit. Review and edit to keep your session
running smoothly with the information that matters most.
</p>
</div>
);
// Uncontrolled Summary Content Component
const SummaryContent = () => (
<div className="w-full mb-6">
<h3 className="text-base font-medium text-gray-900 dark:text-white mb-3">Summarization</h3>
<textarea
ref={textareaRef}
defaultValue={summaryContent}
className="bg-gray-50 dark:bg-gray-800 p-4 rounded-lg text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-gray-700 text-sm w-full min-h-[200px] focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
style={{
textRendering: 'optimizeLegibility',
WebkitFontSmoothing: 'antialiased',
MozOsxFontSmoothing: 'grayscale',
transform: 'translateZ(0)', // Force hardware acceleration
}}
/>
</div>
);
// Footer Buttons
const modalActions = (
<div>
<button
onClick={handleSave}
className="w-full h-[60px] text-gray-900 dark:text-white font-medium text-base hover:bg-gray-50 dark:hover:bg-gray-800 border-t border-gray-200 dark:border-gray-700"
>
Save and Continue
</button>
<button
onClick={onClose}
className="w-full h-[60px] text-gray-500 dark:text-gray-400 font-medium text-base hover:text-gray-900 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-gray-800 border-t border-gray-200 dark:border-gray-700"
>
Cancel
</button>
</div>
);
return (
<WiderBaseModal isOpen={isOpen} title="" actions={modalActions}>
<div className="flex flex-col w-full">
<Header />
<SummaryContent />
</div>
</WiderBaseModal>
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
<DialogContent className="sm:max-w-[640px] max-h-[85vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex flex-col items-center text-center">
<div className="mb-4">
<Geese width="48" height="50" />
</div>
Session Summary
</DialogTitle>
<DialogDescription className="text-center max-w-md">
This summary was created to manage your context limit. Review and edit to keep your
session running smoothly with the information that matters most.
</DialogDescription>
</DialogHeader>
<div className="py-4">
<div className="w-full">
<h3 className="text-base font-medium text-gray-900 dark:text-white mb-3">
Summarization
</h3>
<textarea
ref={textareaRef}
defaultValue={summaryContent}
className="bg-gray-50 dark:bg-gray-800 p-4 rounded-lg text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-gray-700 text-sm w-full min-h-[200px] focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
style={{
textRendering: 'optimizeLegibility',
WebkitFontSmoothing: 'antialiased',
MozOsxFontSmoothing: 'grayscale',
transform: 'translateZ(0)', // Force hardware acceleration
}}
/>
</div>
</div>
<DialogFooter className="pt-2">
<Button variant="outline" onClick={onClose}>
Cancel
</Button>
<Button onClick={handleSave}>Save and Continue</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}