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:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user