Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions ui/desktop/src/components/AnimatedIcons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useState, useEffect } from 'react';
import {
CodeXml,
Cog,
Fuel,
GalleryHorizontalEnd,
Gavel,
GlassWater,
Grape,
Watch0,
Watch1,
Watch2,
Watch3,
Watch4,
Watch5,
Watch6,
} from './icons';

interface AnimatedIconsProps {
className?: string;
cycleInterval?: number; // milliseconds between icon changes
variant?: 'thinking' | 'waiting';
}

const thinkingIcons = [CodeXml, Cog, Fuel, GalleryHorizontalEnd, Gavel, GlassWater, Grape];
const waitingIcons = [Watch0, Watch1, Watch2, Watch3, Watch4, Watch5, Watch6];

export default function AnimatedIcons({
className = '',
cycleInterval = 500,
variant = 'thinking',
}: AnimatedIconsProps) {
const [currentIconIndex, setCurrentIconIndex] = useState(0);
const icons = variant === 'thinking' ? thinkingIcons : waitingIcons;

useEffect(() => {
const interval = setInterval(() => {
setCurrentIconIndex((prevIndex) => (prevIndex + 1) % icons.length);
}, cycleInterval);

return () => clearInterval(interval);
}, [cycleInterval, icons]);

const CurrentIcon = icons[currentIconIndex];

return (
<div className={`transition-opacity duration-200 w-4 h-4 ${className}`}>
<CurrentIcon className="w-full h-full" />
</div>
);
}
22 changes: 11 additions & 11 deletions ui/desktop/src/components/BaseChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import { useSessionContinuation } from '../hooks/useSessionContinuation';
import { useFileDrop } from '../hooks/useFileDrop';
import { useCostTracking } from '../hooks/useCostTracking';
import { Message } from '../types/message';
import { ChatState } from '../types/chatState';

// Context for sharing current model info
const CurrentModelContext = createContext<{ model: string; mode: string } | null>(null);
Expand Down Expand Up @@ -138,9 +139,7 @@ function BaseChatContent({
ancestorMessages,
setAncestorMessages,
append,
isLoading,
isWaiting,
isStreaming,
chatState,
error,
setMessages,
input: _input,
Expand Down Expand Up @@ -226,8 +225,10 @@ function BaseChatContent({

// Handle recipe auto-execution
useEffect(() => {
handleAutoExecution(append, isLoading);
}, [handleAutoExecution, append, isLoading]);
const isProcessingResponse =
chatState !== ChatState.Idle && chatState !== ChatState.WaitingForUserInput;
handleAutoExecution(append, isProcessingResponse);
}, [handleAutoExecution, append, chatState]);

// Use shared session continuation
const { createNewSessionIfNeeded } = useSessionContinuation({
Expand Down Expand Up @@ -406,7 +407,7 @@ function BaseChatContent({
}}
isUserMessage={isUserMessage}
onScrollToBottom={handleScrollToBottom}
isStreamingMessage={isLoading}
isStreamingMessage={chatState !== ChatState.Idle}
/>
) : (
// Render messages with SearchView wrapper when search is enabled
Expand All @@ -422,7 +423,7 @@ function BaseChatContent({
}}
isUserMessage={isUserMessage}
onScrollToBottom={handleScrollToBottom}
isStreamingMessage={isLoading}
isStreamingMessage={chatState !== ChatState.Idle}
/>
</SearchView>
)}
Expand Down Expand Up @@ -501,12 +502,11 @@ function BaseChatContent({
</ScrollArea>

{/* Fixed loading indicator at bottom left of chat container */}
{isLoading && (
{chatState !== ChatState.Idle && (
<div className="absolute bottom-1 left-4 z-20 pointer-events-none">
<LoadingGoose
message={isLoadingSummary ? 'summarizing conversation…' : undefined}
isWaiting={isWaiting}
isStreaming={isStreaming}
chatState={chatState}
/>
</div>
)}
Expand All @@ -517,7 +517,7 @@ function BaseChatContent({
>
<ChatInput
handleSubmit={handleSubmit}
isLoading={isLoading}
chatState={chatState}
onStop={onStopGoose}
commandHistory={commandHistory}
initialValue={_input || (messages.length === 0 ? initialPrompt : '')}
Expand Down
8 changes: 6 additions & 2 deletions ui/desktop/src/components/ChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Button } from './ui/button';
import type { View } from '../App';
import Stop from './ui/Stop';
import { Attach, Send, Close, Microphone } from './icons';
import { ChatState } from '../types/chatState';
import { debounce } from 'lodash';
import { LocalMessageStorage } from '../utils/localMessageStorage';
import { Message } from '../types/message';
Expand Down Expand Up @@ -52,7 +53,7 @@ interface ModelLimit {

interface ChatInputProps {
handleSubmit: (e: React.FormEvent) => void;
isLoading?: boolean;
chatState: ChatState;
onStop?: () => void;
commandHistory?: string[]; // Current chat's message history
initialValue?: string;
Expand All @@ -78,7 +79,7 @@ interface ChatInputProps {

export default function ChatInput({
handleSubmit,
isLoading = false,
chatState = ChatState.Idle,
onStop,
commandHistory = [],
initialValue = '',
Expand All @@ -99,6 +100,9 @@ export default function ChatInput({
const [displayValue, setDisplayValue] = useState(initialValue); // For immediate visual feedback
const [isFocused, setIsFocused] = useState(false);
const [pastedImages, setPastedImages] = useState<PastedImage[]>([]);

// Derived state - chatState != Idle means we're in some form of loading state
const isLoading = chatState !== ChatState.Idle;
const { alerts, addAlert, clearAlerts } = useAlerts();
const dropdownRef = useRef<HTMLDivElement>(null);
const toolCount = useToolCount();
Expand Down
29 changes: 14 additions & 15 deletions ui/desktop/src/components/LoadingGoose.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
import GooseLogo from './GooseLogo';
import ThinkingIcons from './ThinkingIcons';
import AnimatedIcons from './AnimatedIcons';
import FlyingBird from './FlyingBird';
import { ChatState } from '../types/chatState';

interface LoadingGooseProps {
message?: string;
isWaiting?: boolean;
isStreaming?: boolean;
chatState?: ChatState;
}

const LoadingGoose = ({
message,
isWaiting = false,
isStreaming = false
}: LoadingGooseProps) => {
const LoadingGoose = ({ message, chatState = ChatState.Idle }: LoadingGooseProps) => {
// Determine the appropriate message based on state
const getLoadingMessage = () => {
if (message) return message; // Custom message takes priority

if (isWaiting) return 'goose is thinking…';
if (isStreaming) return 'goose is working on it…';


if (chatState === ChatState.Thinking) return 'goose is thinking…';
if (chatState === ChatState.Streaming) return 'goose is working on it…';
if (chatState === ChatState.WaitingForUserInput) return 'goose is waiting…';

// Default fallback
return 'goose is working on it…';
};
Expand All @@ -30,10 +27,12 @@ const LoadingGoose = ({
data-testid="loading-indicator"
className="flex items-center gap-2 text-xs text-textStandard py-2"
>
{isWaiting ? (
<ThinkingIcons className="flex-shrink-0" cycleInterval={600} />
) : isStreaming ? (
{chatState === ChatState.Thinking ? (
<AnimatedIcons className="flex-shrink-0" cycleInterval={600} />
) : chatState === ChatState.Streaming ? (
<FlyingBird className="flex-shrink-0" cycleInterval={150} />
) : chatState === ChatState.WaitingForUserInput ? (
<AnimatedIcons className="flex-shrink-0" cycleInterval={600} variant="waiting" />
) : (
<GooseLogo size="small" hover={false} />
)}
Expand Down
42 changes: 0 additions & 42 deletions ui/desktop/src/components/ThinkingIcons.tsx

This file was deleted.

3 changes: 2 additions & 1 deletion ui/desktop/src/components/hub.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { type View, ViewOptions } from '../App';
import { SessionInsights } from './sessions/SessionsInsights';
import ChatInput from './ChatInput';
import { generateSessionId } from '../sessions';
import { ChatState } from '../types/chatState';
import { ChatContextManagerProvider } from './context_management/ChatContextManager';
import 'react-toastify/dist/ReactToastify.css';

Expand Down Expand Up @@ -87,7 +88,7 @@ export default function Hub({

<ChatInput
handleSubmit={handleSubmit}
isLoading={false}
chatState={ChatState.Idle}
onStop={() => {}}
commandHistory={[]}
initialValue=""
Expand Down
20 changes: 20 additions & 0 deletions ui/desktop/src/components/icons/Watch0.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export function Watch0({ className = '' }: { className?: string }) {
return (
<svg
className={className}
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.33"
d="M8 6.667V8l.667.667m2.086-3.56-.54-2.7A1.333 1.333 0 0 0 8.88 1.333H7.093A1.333 1.333 0 0 0 5.76 2.407l-.52 2.7m.013 5.8.534 2.666a1.333 1.333 0 0 0 1.333 1.074h1.813a1.333 1.333 0 0 0 1.334-1.074l.54-2.7M12 8a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"
/>
</svg>
);
}
20 changes: 20 additions & 0 deletions ui/desktop/src/components/icons/Watch1.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export function Watch1({ className = '' }: { className?: string }) {
return (
<svg
className={className}
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.33"
d="M9 7 8 8l.667.667m2.086-3.56-.54-2.7A1.333 1.333 0 0 0 8.88 1.333H7.093A1.333 1.333 0 0 0 5.76 2.407l-.52 2.7m.013 5.8.534 2.666a1.333 1.333 0 0 0 1.333 1.074h1.813a1.333 1.333 0 0 0 1.334-1.074l.54-2.7M12 8a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"
/>
</svg>
);
}
20 changes: 20 additions & 0 deletions ui/desktop/src/components/icons/Watch2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export function Watch2({ className = '' }: { className?: string }) {
return (
<svg
className={className}
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.33"
d="M9.742 8H8l.667.667m2.086-3.56-.54-2.7A1.333 1.333 0 0 0 8.88 1.333H7.093A1.333 1.333 0 0 0 5.76 2.407l-.52 2.7m.013 5.8.534 2.666a1.333 1.333 0 0 0 1.333 1.074h1.813a1.333 1.333 0 0 0 1.334-1.074l.54-2.7M12 8a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"
/>
</svg>
);
}
20 changes: 20 additions & 0 deletions ui/desktop/src/components/icons/Watch3.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export function Watch3({ className = '' }: { className?: string }) {
return (
<svg
className={className}
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.33"
d="M8.667 9.816 8 8l.667.667m2.086-3.56-.54-2.7A1.333 1.333 0 0 0 8.88 1.333H7.093A1.333 1.333 0 0 0 5.76 2.407l-.52 2.7m.013 5.8.534 2.666a1.333 1.333 0 0 0 1.333 1.074h1.813a1.333 1.333 0 0 0 1.334-1.074l.54-2.7M12 8a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"
/>
</svg>
);
}
20 changes: 20 additions & 0 deletions ui/desktop/src/components/icons/Watch4.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export function Watch4({ className = '' }: { className?: string }) {
return (
<svg
className={className}
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.33"
d="M7 9.5 8 8l.667.667m2.086-3.56-.54-2.7A1.333 1.333 0 0 0 8.88 1.333H7.093A1.333 1.333 0 0 0 5.76 2.407l-.52 2.7m.013 5.8.534 2.666a1.333 1.333 0 0 0 1.333 1.074h1.813a1.333 1.333 0 0 0 1.334-1.074l.54-2.7M12 8a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"
/>
</svg>
);
}
20 changes: 20 additions & 0 deletions ui/desktop/src/components/icons/Watch5.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export function Watch5({ className = '' }: { className?: string }) {
return (
<svg
className={className}
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="1.33"
d="M6.232 8.288 8 8l.667.667m2.086-3.56-.54-2.7A1.333 1.333 0 0 0 8.88 1.333H7.093A1.333 1.333 0 0 0 5.76 2.407l-.52 2.7m.013 5.8.534 2.666a1.333 1.333 0 0 0 1.333 1.074h1.813a1.333 1.333 0 0 0 1.334-1.074l.54-2.7M12 8a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"
/>
</svg>
);
}
Loading
Loading