From 05a49702d888bee912682f78c3993d98f7f9d628 Mon Sep 17 00:00:00 2001 From: Jacob Richman Date: Wed, 28 May 2025 18:17:19 +0000 Subject: Refactor: Add GeminiRespondingSpinner to make use of streamingState idiomatic (#583) --- .../ui/components/messages/ToolMessage.test.tsx | 40 ++++++++---- .../cli/src/ui/components/messages/ToolMessage.tsx | 73 ++++++++++------------ 2 files changed, 60 insertions(+), 53 deletions(-) (limited to 'packages/cli/src/ui/components/messages') diff --git a/packages/cli/src/ui/components/messages/ToolMessage.test.tsx b/packages/cli/src/ui/components/messages/ToolMessage.test.tsx index 10380ad4..a40ca31b 100644 --- a/packages/cli/src/ui/components/messages/ToolMessage.test.tsx +++ b/packages/cli/src/ui/components/messages/ToolMessage.test.tsx @@ -4,15 +4,29 @@ * SPDX-License-Identifier: Apache-2.0 */ +import React from 'react'; import { render } from 'ink-testing-library'; import { ToolMessage, ToolMessageProps } from './ToolMessage.js'; import { StreamingState, ToolCallStatus } from '../../types.js'; import { Text } from 'ink'; -import { StreamingContext } from '../../contexts/StreamingContext.js'; +import { + StreamingContext, + StreamingContextType, +} from '../../contexts/StreamingContext.js'; // Mock child components or utilities if they are complex or have side effects -vi.mock('ink-spinner', () => ({ - default: () => MockSpinner, +vi.mock('../GeminiRespondingSpinner.js', () => ({ + GeminiRespondingSpinner: ({ + nonRespondingDisplay, + }: { + nonRespondingDisplay?: string; + }) => { + const { streamingState } = React.useContext(StreamingContext)!; + if (streamingState === StreamingState.Responding) { + return MockRespondingSpinner; + } + return nonRespondingDisplay ? {nonRespondingDisplay} : null; + }, })); vi.mock('./DiffRenderer.js', () => ({ DiffRenderer: function MockDiffRenderer({ @@ -33,12 +47,14 @@ vi.mock('../../utils/MarkdownDisplay.js', () => ({ const renderWithContext = ( ui: React.ReactElement, streamingState: StreamingState, -) => - render( - +) => { + const contextValue: StreamingContextType = { streamingState }; + return render( + {ui} , ); +}; describe('', () => { const baseProps: ToolMessageProps = { @@ -110,8 +126,8 @@ describe('', () => { , StreamingState.Idle, ); - expect(lastFrame()).toContain('⠇'); - expect(lastFrame()).not.toContain('MockSpinner'); + expect(lastFrame()).toContain('⊷'); + expect(lastFrame()).not.toContain('MockRespondingSpinner'); expect(lastFrame()).not.toContain('✔'); }); @@ -120,17 +136,17 @@ describe('', () => { , StreamingState.WaitingForConfirmation, ); - expect(lastFrame()).toContain('⠇'); - expect(lastFrame()).not.toContain('MockSpinner'); + expect(lastFrame()).toContain('⊷'); + expect(lastFrame()).not.toContain('MockRespondingSpinner'); expect(lastFrame()).not.toContain('✔'); }); - it('shows MockSpinner for Executing status when streamingState is Responding', () => { + it('shows MockRespondingSpinner for Executing status when streamingState is Responding', () => { const { lastFrame } = renderWithContext( , StreamingState.Responding, // Simulate app still responding ); - expect(lastFrame()).toContain('MockSpinner'); + expect(lastFrame()).toContain('MockRespondingSpinner'); expect(lastFrame()).not.toContain('✔'); }); }); diff --git a/packages/cli/src/ui/components/messages/ToolMessage.tsx b/packages/cli/src/ui/components/messages/ToolMessage.tsx index c8b61297..922f59d0 100644 --- a/packages/cli/src/ui/components/messages/ToolMessage.tsx +++ b/packages/cli/src/ui/components/messages/ToolMessage.tsx @@ -6,16 +6,11 @@ import React from 'react'; import { Box, Text } from 'ink'; -import Spinner from 'ink-spinner'; -import { - IndividualToolCallDisplay, - StreamingState, - ToolCallStatus, -} from '../../types.js'; +import { IndividualToolCallDisplay, ToolCallStatus } from '../../types.js'; import { DiffRenderer } from './DiffRenderer.js'; import { Colors } from '../../colors.js'; import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js'; -import { useStreamingContext } from '../../contexts/StreamingContext.js'; +import { GeminiRespondingSpinner } from '../GeminiRespondingSpinner.js'; const STATIC_HEIGHT = 1; const RESERVED_LINE_COUNT = 5; // for tool name, status, padding etc. @@ -61,7 +56,6 @@ export const ToolMessage: React.FC = ({ return ( - {/* Status Indicator */} = ({ type ToolStatusIndicatorProps = { status: ToolCallStatus; }; + const ToolStatusIndicator: React.FC = ({ status, -}) => { - const { streamingState } = useStreamingContext(); - return ( - - {status === ToolCallStatus.Pending && ( - o - )} - {status === ToolCallStatus.Executing && - (streamingState === StreamingState.Responding ? ( - - ) : ( - // Paused spinner to avoid flicker. - - ))} - {status === ToolCallStatus.Success && ( - - )} - {status === ToolCallStatus.Confirming && ( - ? - )} - {status === ToolCallStatus.Canceled && ( - - - - - )} - {status === ToolCallStatus.Error && ( - - x - - )} - - ); -}; +}) => ( + + {status === ToolCallStatus.Pending && ( + o + )} + {status === ToolCallStatus.Executing && ( + + )} + {status === ToolCallStatus.Success && ( + + )} + {status === ToolCallStatus.Confirming && ( + ? + )} + {status === ToolCallStatus.Canceled && ( + + - + + )} + {status === ToolCallStatus.Error && ( + + x + + )} + +); type ToolInfo = { name: string; -- cgit v1.2.3