diff --git a/packages/cli/src/ui/components/LoadingIndicator.test.tsx b/packages/cli/src/ui/components/LoadingIndicator.test.tsx
index 9df7dec8a0..cb4b65097a 100644
--- a/packages/cli/src/ui/components/LoadingIndicator.test.tsx
+++ b/packages/cli/src/ui/components/LoadingIndicator.test.tsx
@@ -12,6 +12,7 @@ import { StreamingContext } from '../contexts/StreamingContext.js';
import { StreamingState } from '../types.js';
import { vi } from 'vitest';
import * as useTerminalSize from '../hooks/useTerminalSize.js';
+import * as terminalUtils from '../utils/terminalUtils.js';
// Mock GeminiRespondingSpinner
vi.mock('./GeminiRespondingSpinner.js', () => ({
@@ -34,7 +35,12 @@ vi.mock('../hooks/useTerminalSize.js', () => ({
useTerminalSize: vi.fn(),
}));
+vi.mock('../utils/terminalUtils.js', () => ({
+ shouldUseEmoji: vi.fn(() => true),
+}));
+
const useTerminalSizeMock = vi.mocked(useTerminalSize.useTerminalSize);
+const shouldUseEmojiMock = vi.mocked(terminalUtils.shouldUseEmoji);
const renderWithContext = (
ui: React.ReactElement,
@@ -258,17 +264,15 @@ describe('', () => {
const output = lastFrame();
expect(output).toBeDefined();
if (output) {
- expect(output).toContain('Thinking: ');
+ expect(output).toContain('💬');
expect(output).toContain('Thinking about something...');
expect(output).not.toContain('and other stuff.');
}
unmount();
});
-<<<<<<< HEAD
- it('should prioritize thought.subject over currentLoadingPhrase', async () => {
-=======
- it('should use "Thinking: " as the thought indicator', () => {
+ it('should use ASCII fallback thought indicator when emoji is unavailable', async () => {
+ shouldUseEmojiMock.mockReturnValue(false);
const props = {
thought: {
subject: 'Thinking with fallback',
@@ -276,17 +280,19 @@ describe('', () => {
},
elapsedTime: 5,
};
- const { lastFrame, unmount } = renderWithContext(
+ const { lastFrame, unmount, waitUntilReady } = renderWithContext(
,
StreamingState.Responding,
);
+ await waitUntilReady();
const output = lastFrame();
- expect(output).toContain('Thinking: Thinking with fallback');
+ expect(output).toContain('o Thinking with fallback');
+ expect(output).not.toContain('💬');
+ shouldUseEmojiMock.mockReturnValue(true);
unmount();
});
- it('should prioritize thought.subject over currentLoadingPhrase', () => {
->>>>>>> 3e1e540d7 (feat(cli): overhaul inline thinking UI to match mock and update status bar indicator)
+ it('should prioritize thought.subject over currentLoadingPhrase', async () => {
const props = {
thought: {
subject: 'This should be displayed',
@@ -301,31 +307,22 @@ describe('', () => {
);
await waitUntilReady();
const output = lastFrame();
- expect(output).toContain('Thinking: ');
+ expect(output).toContain('💬');
expect(output).toContain('This should be displayed');
expect(output).not.toContain('This should not be displayed');
unmount();
});
-<<<<<<< HEAD
it('should not display thought icon for non-thought loading phrases', async () => {
const { lastFrame, unmount, waitUntilReady } = renderWithContext(
-=======
- it('should not display thought indicator for non-thought loading phrases', () => {
- const { lastFrame, unmount } = renderWithContext(
->>>>>>> 3e1e540d7 (feat(cli): overhaul inline thinking UI to match mock and update status bar indicator)
,
StreamingState.Responding,
);
-<<<<<<< HEAD
await waitUntilReady();
expect(lastFrame()).not.toContain('💬');
-=======
- expect(lastFrame()).not.toContain('Thinking: ');
->>>>>>> 3e1e540d7 (feat(cli): overhaul inline thinking UI to match mock and update status bar indicator)
unmount();
});
diff --git a/packages/cli/src/ui/components/LoadingIndicator.tsx b/packages/cli/src/ui/components/LoadingIndicator.tsx
index de4db32391..3d6a838370 100644
--- a/packages/cli/src/ui/components/LoadingIndicator.tsx
+++ b/packages/cli/src/ui/components/LoadingIndicator.tsx
@@ -15,6 +15,7 @@ import { formatDuration } from '../utils/formatters.js';
import { useTerminalSize } from '../hooks/useTerminalSize.js';
import { isNarrowWidth } from '../utils/isNarrowWidth.js';
import { INTERACTIVE_SHELL_WAITING_PHRASE } from '../hooks/usePhraseCycler.js';
+import { shouldUseEmoji } from '../utils/terminalUtils.js';
interface LoadingIndicatorProps {
currentLoadingPhrase?: string;
@@ -58,7 +59,9 @@ export const LoadingIndicator: React.FC = ({
const hasThoughtIndicator =
currentLoadingPhrase !== INTERACTIVE_SHELL_WAITING_PHRASE &&
Boolean(thought?.subject?.trim());
- const thinkingIndicator = hasThoughtIndicator ? 'Thinking: ' : '';
+ const thinkingIndicator = hasThoughtIndicator
+ ? `${shouldUseEmoji() ? '💬' : 'o'} `
+ : '';
const cancelAndTimerContent =
showCancelAndTimer &&
diff --git a/packages/cli/src/ui/utils/terminalUtils.ts b/packages/cli/src/ui/utils/terminalUtils.ts
index 18cd08f952..b0a3b93034 100644
--- a/packages/cli/src/ui/utils/terminalUtils.ts
+++ b/packages/cli/src/ui/utils/terminalUtils.ts
@@ -43,3 +43,25 @@ export function isITerm2(): boolean {
export function resetITerm2Cache(): void {
cachedIsITerm2 = undefined;
}
+
+/**
+ * Returns true if the terminal likely supports emoji.
+ */
+export function shouldUseEmoji(): boolean {
+ const locale = (
+ process.env['LC_ALL'] ||
+ process.env['LC_CTYPE'] ||
+ process.env['LANG'] ||
+ ''
+ ).toLowerCase();
+ const supportsUtf8 = locale.includes('utf-8') || locale.includes('utf8');
+ if (!supportsUtf8) {
+ return false;
+ }
+
+ if (process.env['TERM'] === 'linux') {
+ return false;
+ }
+
+ return true;
+}