mirror of
https://github.com/aljazceru/goose.git
synced 2026-02-02 05:04:23 +01:00
@@ -8,7 +8,7 @@ import Input from './components/Input';
|
||||
import LoadingGoose from './components/LoadingGoose';
|
||||
import MoreMenu from './components/MoreMenu';
|
||||
import { Card } from './components/ui/card';
|
||||
import { ScrollArea } from './components/ui/scroll-area';
|
||||
import { ScrollArea, ScrollAreaHandle } from './components/ui/scroll-area';
|
||||
import UserMessage from './components/UserMessage';
|
||||
import WingToWing, { Working } from './components/WingToWing';
|
||||
import { askAi } from './utils/askAI';
|
||||
@@ -22,7 +22,6 @@ import { useRecentModels } from './components/settings/models/RecentModels';
|
||||
import { createSelectedModel } from './components/settings/models/utils';
|
||||
import { getDefaultModel } from './components/settings/models/hardcoded_stuff';
|
||||
import Splash from './components/Splash';
|
||||
import { loadAndAddStoredExtensions } from './extensions';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@@ -53,13 +52,10 @@ export interface Chat {
|
||||
}>;
|
||||
}
|
||||
|
||||
type ScrollBehavior = 'auto' | 'smooth' | 'instant';
|
||||
|
||||
export function ChatContent({
|
||||
chats,
|
||||
setChats,
|
||||
selectedChatId,
|
||||
setSelectedChatId,
|
||||
initialQuery,
|
||||
setProgressMessage,
|
||||
setWorking,
|
||||
@@ -77,8 +73,8 @@ export function ChatContent({
|
||||
const [hasMessages, setHasMessages] = useState(false);
|
||||
const [lastInteractionTime, setLastInteractionTime] = useState<number>(Date.now());
|
||||
const [showGame, setShowGame] = useState(false);
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const [working, setWorkingLocal] = useState<Working>(Working.Idle);
|
||||
const scrollRef = useRef<ScrollAreaHandle>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setWorking(working);
|
||||
@@ -94,7 +90,6 @@ export function ChatContent({
|
||||
onToolCall: ({ toolCall }) => {
|
||||
updateWorking(Working.Working);
|
||||
setProgressMessage(`Executing tool: ${toolCall.toolName}`);
|
||||
requestAnimationFrame(() => scrollToBottom('instant'));
|
||||
},
|
||||
onResponse: (response) => {
|
||||
if (!response.ok) {
|
||||
@@ -115,8 +110,6 @@ export function ChatContent({
|
||||
const fetchResponses = await askAi(message.content);
|
||||
setMessageMetadata((prev) => ({ ...prev, [message.id]: fetchResponses }));
|
||||
|
||||
requestAnimationFrame(() => scrollToBottom('smooth'));
|
||||
|
||||
const timeSinceLastInteraction = Date.now() - lastInteractionTime;
|
||||
window.electron.logInfo('last interaction:' + lastInteractionTime);
|
||||
if (timeSinceLastInteraction > 60000) {
|
||||
@@ -150,23 +143,6 @@ export function ChatContent({
|
||||
}
|
||||
}, [messages]);
|
||||
|
||||
const scrollToBottom = (behavior: ScrollBehavior = 'smooth') => {
|
||||
if (messagesEndRef.current) {
|
||||
messagesEndRef.current.scrollIntoView({
|
||||
behavior,
|
||||
block: 'end',
|
||||
inline: 'nearest',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Single effect to handle all scrolling
|
||||
useEffect(() => {
|
||||
if (isLoading || messages.length > 0 || working === Working.Working) {
|
||||
scrollToBottom(isLoading || working === Working.Working ? 'instant' : 'smooth');
|
||||
}
|
||||
}, [messages, isLoading, working]);
|
||||
|
||||
// Handle submit
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
window.electron.startPowerSaveBlocker();
|
||||
@@ -178,7 +154,9 @@ export function ChatContent({
|
||||
role: 'user',
|
||||
content: content,
|
||||
});
|
||||
scrollToBottom('instant');
|
||||
if (scrollRef.current?.scrollToBottom) {
|
||||
scrollRef.current.scrollToBottom();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -241,7 +219,7 @@ export function ChatContent({
|
||||
{messages.length === 0 ? (
|
||||
<Splash append={append} />
|
||||
) : (
|
||||
<ScrollArea className="flex-1 px-4" id="chat-scroll-area">
|
||||
<ScrollArea ref={scrollRef} className="flex-1 px-4" autoScroll>
|
||||
{messages.map((message) => (
|
||||
<div key={message.id} className="mt-[16px]">
|
||||
{message.role === 'user' ? (
|
||||
@@ -288,7 +266,6 @@ export function ChatContent({
|
||||
</div>
|
||||
)}
|
||||
<div className="block h-16" />
|
||||
<div ref={messagesEndRef} style={{ height: '1px' }} />
|
||||
</ScrollArea>
|
||||
)}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import GooseLogo from './GooseLogo';
|
||||
const LoadingGoose = () => {
|
||||
return (
|
||||
<div className="w-full pb-[2px]">
|
||||
<div className="flex items-center text-xs text-textStandard mb-2 pl-4 animate-[appear_250ms_ease-in_forwards]">
|
||||
<div className="flex items-center text-xs text-textStandard mb-2 mt-2 pl-4 animate-[appear_250ms_ease-in_forwards]">
|
||||
<GooseLogo className="mr-2" size="small" hover={false} />
|
||||
goose is working on it..
|
||||
</div>
|
||||
|
||||
@@ -3,22 +3,86 @@ import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
|
||||
|
||||
import { cn } from '../../utils';
|
||||
|
||||
const ScrollArea = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn('relative overflow-hidden', className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
));
|
||||
export interface ScrollAreaHandle {
|
||||
scrollToBottom: () => void;
|
||||
}
|
||||
|
||||
interface ScrollAreaProps extends React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> {
|
||||
autoScroll?: boolean;
|
||||
}
|
||||
|
||||
const ScrollArea = React.forwardRef<ScrollAreaHandle, ScrollAreaProps>(
|
||||
({ className, children, autoScroll = false, ...props }, ref) => {
|
||||
const rootRef = React.useRef<React.ElementRef<typeof ScrollAreaPrimitive.Root>>(null);
|
||||
const viewportRef = React.useRef<HTMLDivElement>(null);
|
||||
const viewportEndRef = React.useRef<HTMLDivElement>(null);
|
||||
const [isFollowing, setIsFollowing] = React.useState(true);
|
||||
|
||||
const scrollToBottom = React.useCallback(() => {
|
||||
if (viewportEndRef.current) {
|
||||
viewportEndRef.current.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'end',
|
||||
inline: 'nearest',
|
||||
});
|
||||
setIsFollowing(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Expose the scrollToBottom method to parent components
|
||||
React.useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
scrollToBottom,
|
||||
}),
|
||||
[scrollToBottom]
|
||||
);
|
||||
|
||||
// Handle scroll events to update isFollowing state
|
||||
const handleScroll = React.useCallback(() => {
|
||||
if (!viewportRef.current) return;
|
||||
|
||||
const viewport = viewportRef.current;
|
||||
const { scrollHeight, scrollTop, clientHeight } = viewport;
|
||||
|
||||
const scrollBottom = scrollTop + clientHeight;
|
||||
const newIsFollowing = scrollHeight === scrollBottom;
|
||||
|
||||
// react will internally optimize this to not re-store the same values
|
||||
setIsFollowing(newIsFollowing);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!autoScroll || !isFollowing) return;
|
||||
|
||||
scrollToBottom();
|
||||
}, [children, autoScroll, isFollowing, scrollToBottom]);
|
||||
|
||||
// Add scroll event listener
|
||||
React.useEffect(() => {
|
||||
const viewport = viewportRef.current;
|
||||
if (!viewport) return;
|
||||
|
||||
viewport.addEventListener('scroll', handleScroll);
|
||||
return () => viewport.removeEventListener('scroll', handleScroll);
|
||||
}, [handleScroll]);
|
||||
|
||||
return (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={rootRef}
|
||||
className={cn('relative overflow-hidden', className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport ref={viewportRef} className="h-full w-full rounded-[inherit]">
|
||||
{children}
|
||||
{autoScroll && <div ref={viewportEndRef} style={{ height: '1px' }} />}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
);
|
||||
}
|
||||
);
|
||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
|
||||
|
||||
const ScrollBar = React.forwardRef<
|
||||
|
||||
Reference in New Issue
Block a user