mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-28 15:01:14 -07:00
feat(cli): implement customizable witty phrase positioning
- Add ui.wittyPhrasePosition setting (status, inline, ambient) - Refactor usePhraseCycler to return tips and wit separately - Implement 'inline' position: append witty phrases in gray after status - Update status length estimation to account for inline wit - Replace pause icon with up arrow (↑) for awaiting approval - Remove 'Tip:' prefix from loading phrases - Update unit tests and research report
This commit is contained in:
@@ -59,6 +59,7 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
const isAlternateBuffer = useAlternateBuffer();
|
||||
const { showApprovalModeIndicator } = uiState;
|
||||
const newLayoutSetting = settings.merged.ui.newFooterLayout;
|
||||
const wittyPosition = settings.merged.ui.wittyPhrasePosition;
|
||||
const isExperimentalLayout = newLayoutSetting !== 'legacy';
|
||||
const showUiDetails = uiState.cleanUiDetailsVisible;
|
||||
const suggestionsPosition = isAlternateBuffer ? 'above' : 'below';
|
||||
@@ -197,15 +198,8 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
|
||||
const ambientText = isInteractiveShellWaiting
|
||||
? undefined
|
||||
: uiState.currentLoadingPhrase;
|
||||
|
||||
// Wit often ends with an ellipsis or similar, tips usually don't.
|
||||
const isAmbientTip =
|
||||
ambientText &&
|
||||
!ambientText.includes('…') &&
|
||||
!ambientText.includes('...') &&
|
||||
!ambientText.includes('feeling lucky');
|
||||
const ambientPrefix = isAmbientTip ? 'Tip: ' : '';
|
||||
: uiState.currentTip ||
|
||||
(wittyPosition === 'ambient' ? uiState.currentWittyPhrase : undefined);
|
||||
|
||||
let estimatedStatusLength = 0;
|
||||
if (
|
||||
@@ -225,13 +219,16 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
estimatedStatusLength = hookLabel.length + hookNames.length + 10; // +10 for spinner and spacing
|
||||
} else if (showLoadingIndicator) {
|
||||
const thoughtText = uiState.thought?.subject || 'Waiting for model...';
|
||||
estimatedStatusLength = thoughtText.length + 25; // Spinner(3) + timer(15) + padding
|
||||
const inlineWittyLength =
|
||||
wittyPosition === 'inline' && uiState.currentWittyPhrase
|
||||
? uiState.currentWittyPhrase.length + 1
|
||||
: 0;
|
||||
estimatedStatusLength = thoughtText.length + 25 + inlineWittyLength; // Spinner(3) + timer(15) + padding + witty
|
||||
} else if (hasPendingActionRequired) {
|
||||
estimatedStatusLength = 35; // "⏸ Awaiting user approval..."
|
||||
estimatedStatusLength = 25; // "↑ Awaiting approval"
|
||||
}
|
||||
|
||||
const estimatedAmbientLength =
|
||||
ambientPrefix.length + (ambientText?.length || 0);
|
||||
const estimatedAmbientLength = ambientText?.length || 0;
|
||||
const willCollideAmbient =
|
||||
estimatedStatusLength + estimatedAmbientLength + 5 > terminalWidth;
|
||||
const willCollideShortcuts = estimatedStatusLength + 45 > terminalWidth; // Assume worst-case shortcut hint is 45 chars
|
||||
@@ -263,7 +260,6 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
return (
|
||||
<Box flexDirection="row" justifyContent="flex-end" marginLeft={1}>
|
||||
<Text color={theme.text.secondary} wrap="truncate-end">
|
||||
{ambientPrefix}
|
||||
{ambientText}
|
||||
</Text>
|
||||
</Box>
|
||||
@@ -322,13 +318,13 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
elapsedTime={uiState.elapsedTime}
|
||||
forceRealStatusOnly={isExperimentalLayout}
|
||||
showCancelAndTimer={!isExperimentalLayout}
|
||||
wittyPhrase={uiState.currentWittyPhrase}
|
||||
wittyPosition={wittyPosition}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (hasPendingActionRequired) {
|
||||
return (
|
||||
<Text color={theme.status.warning}>⏸ Awaiting user approval...</Text>
|
||||
);
|
||||
return <Text color={theme.status.warning}>↑ Awaiting approval</Text>;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -18,6 +18,8 @@ import { INTERACTIVE_SHELL_WAITING_PHRASE } from '../hooks/usePhraseCycler.js';
|
||||
|
||||
interface LoadingIndicatorProps {
|
||||
currentLoadingPhrase?: string;
|
||||
wittyPhrase?: string;
|
||||
wittyPosition?: 'status' | 'inline' | 'ambient';
|
||||
elapsedTime: number;
|
||||
inline?: boolean;
|
||||
rightContent?: React.ReactNode;
|
||||
@@ -29,6 +31,8 @@ interface LoadingIndicatorProps {
|
||||
|
||||
export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
|
||||
currentLoadingPhrase,
|
||||
wittyPhrase,
|
||||
wittyPosition = 'inline',
|
||||
elapsedTime,
|
||||
inline = false,
|
||||
rightContent,
|
||||
@@ -57,9 +61,11 @@ export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
|
||||
: thought?.subject
|
||||
? (thoughtLabel ?? thought.subject)
|
||||
: forceRealStatusOnly
|
||||
? streamingState === StreamingState.Responding
|
||||
? 'Waiting for model...'
|
||||
: undefined
|
||||
? wittyPosition === 'status' && wittyPhrase
|
||||
? wittyPhrase
|
||||
: streamingState === StreamingState.Responding
|
||||
? 'Waiting for model...'
|
||||
: undefined
|
||||
: currentLoadingPhrase;
|
||||
const thinkingIndicator = '';
|
||||
|
||||
@@ -69,6 +75,16 @@ export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
|
||||
? `esc to cancel, ${elapsedTime < 60 ? `${elapsedTime}s` : formatDuration(elapsedTime * 1000)}`
|
||||
: null;
|
||||
|
||||
const wittyPhraseNode =
|
||||
forceRealStatusOnly &&
|
||||
wittyPosition === 'inline' &&
|
||||
wittyPhrase &&
|
||||
primaryText ? (
|
||||
<Box marginLeft={1}>
|
||||
<Text color={theme.text.secondary}>{wittyPhrase}</Text>
|
||||
</Box>
|
||||
) : null;
|
||||
|
||||
if (inline) {
|
||||
return (
|
||||
<Box>
|
||||
@@ -91,6 +107,7 @@ export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
|
||||
{primaryText}
|
||||
</Text>
|
||||
)}
|
||||
{wittyPhraseNode}
|
||||
{cancelAndTimerContent && (
|
||||
<>
|
||||
<Box flexShrink={0} width={1} />
|
||||
@@ -129,6 +146,7 @@ export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
|
||||
{primaryText}
|
||||
</Text>
|
||||
)}
|
||||
{wittyPhraseNode}
|
||||
{!isNarrow && cancelAndTimerContent && (
|
||||
<>
|
||||
<Box flexShrink={0} width={1} />
|
||||
|
||||
Reference in New Issue
Block a user