Add emoji thought bubble with fallback

This commit is contained in:
Dmitry Lyalin
2026-01-31 11:35:59 -05:00
parent 4620d5bb15
commit a55d278905
2 changed files with 31 additions and 10 deletions
@@ -9,7 +9,7 @@ import { render } from '../../../test-utils/render.js';
import { ThinkingMessage } from './ThinkingMessage.js'; import { ThinkingMessage } from './ThinkingMessage.js';
describe('ThinkingMessage', () => { describe('ThinkingMessage', () => {
it('renders thinking header', () => { it('renders thinking subject', () => {
const { lastFrame } = render( const { lastFrame } = render(
<ThinkingMessage <ThinkingMessage
thought={{ subject: 'Planning', description: 'test' }} thought={{ subject: 'Planning', description: 'test' }}
@@ -17,7 +17,7 @@ describe('ThinkingMessage', () => {
/>, />,
); );
expect(lastFrame()).toContain('Thinking'); expect(lastFrame()).toContain('Planning');
}); });
it('renders with thought subject', () => { it('renders with thought subject', () => {
@@ -54,6 +54,6 @@ describe('ThinkingMessage', () => {
/>, />,
); );
expect(lastFrame()).toContain('Thinking'); expect(lastFrame()).not.toContain('Planning');
}); });
}); });
@@ -6,6 +6,7 @@
import type React from 'react'; import type React from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import process from 'node:process';
import type { ThoughtSummary } from '@google/gemini-cli-core'; import type { ThoughtSummary } from '@google/gemini-cli-core';
import { MaxSizedBox, MINIMUM_MAX_HEIGHT } from '../shared/MaxSizedBox.js'; import { MaxSizedBox, MINIMUM_MAX_HEIGHT } from '../shared/MaxSizedBox.js';
@@ -22,10 +23,13 @@ export const ThinkingMessage: React.FC<ThinkingMessageProps> = ({
}) => { }) => {
const subject = thought.subject.trim(); const subject = thought.subject.trim();
const description = thought.description.trim(); const description = thought.description.trim();
const headerText = subject || description;
const bodyText = subject ? description : '';
const contentMaxHeight = const contentMaxHeight =
availableTerminalHeight !== undefined availableTerminalHeight !== undefined
? Math.max(availableTerminalHeight - 4, MINIMUM_MAX_HEIGHT) ? Math.max(availableTerminalHeight - 4, MINIMUM_MAX_HEIGHT)
: undefined; : undefined;
const bubbleIcon = shouldUseEmojiBubble() ? '💬' : '◆';
return ( return (
<Box <Box
@@ -41,17 +45,34 @@ export const ThinkingMessage: React.FC<ThinkingMessageProps> = ({
maxWidth={terminalWidth - 2} maxWidth={terminalWidth - 2}
overflowDirection="top" overflowDirection="top"
> >
{(subject || description) && ( {headerText && (
<Box flexDirection="column"> <Box flexDirection="column">
{subject && ( <Text bold color="magenta">
<Text bold color="magenta"> {bubbleIcon} {headerText}
{subject} </Text>
</Text> {bodyText && <Text>{bodyText}</Text>}
)}
{description && <Text>{description}</Text>}
</Box> </Box>
)} )}
</MaxSizedBox> </MaxSizedBox>
</Box> </Box>
); );
}; };
function shouldUseEmojiBubble(): 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;
}