mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-04 08:54:28 -07:00
feat(ui): added microphone and updated placeholder for voice mode (#26270)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
@@ -4979,9 +4979,10 @@ describe('InputPrompt', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Initially not recording
|
// Initially not recording
|
||||||
expect(lastFrame()).not.toContain('🎙️ Listening...');
|
expect(lastFrame()).not.toContain('Listening...');
|
||||||
|
expect(lastFrame()).toContain('🎤 >');
|
||||||
expect(lastFrame()).toContain(
|
expect(lastFrame()).toContain(
|
||||||
'Voice mode: Space to start/stop recording',
|
'Type your message or space to talk (Esc to exit)',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Press space to start
|
// Press space to start
|
||||||
@@ -4991,7 +4992,7 @@ describe('InputPrompt', () => {
|
|||||||
|
|
||||||
// Now should show listening
|
// Now should show listening
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(lastFrame()).toContain('🎙️ Listening...');
|
expect(lastFrame()).toContain('Listening...');
|
||||||
});
|
});
|
||||||
|
|
||||||
unmount();
|
unmount();
|
||||||
@@ -5016,7 +5017,7 @@ describe('InputPrompt', () => {
|
|||||||
stdin.write(' ');
|
stdin.write(' ');
|
||||||
});
|
});
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(lastFrame()).toContain('🎙️ Listening...');
|
expect(lastFrame()).toContain('Listening...');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Stop recording
|
// Stop recording
|
||||||
@@ -5024,10 +5025,8 @@ describe('InputPrompt', () => {
|
|||||||
stdin.write(' ');
|
stdin.write(' ');
|
||||||
});
|
});
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(lastFrame()).not.toContain('🎙️ Listening...');
|
expect(lastFrame()).not.toContain('Listening...');
|
||||||
expect(lastFrame()).toContain(
|
expect(lastFrame()).toContain('🎤 >');
|
||||||
'Voice mode: Space to start/stop recording',
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
unmount();
|
unmount();
|
||||||
@@ -5047,10 +5046,8 @@ describe('InputPrompt', () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Should show voice mode hint even if buffer is not empty (new behavior)
|
// Should show voice mode prefix even if buffer is not empty
|
||||||
expect(lastFrame()).toContain(
|
expect(lastFrame()).toContain('🎤 >');
|
||||||
'Voice mode: Space to start/stop recording',
|
|
||||||
);
|
|
||||||
expect(lastFrame()).toContain('some existing text');
|
expect(lastFrame()).toContain('some existing text');
|
||||||
|
|
||||||
// Press space to start recording again
|
// Press space to start recording again
|
||||||
@@ -5059,7 +5056,7 @@ describe('InputPrompt', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(lastFrame()).toContain('🎙️ Listening...');
|
expect(lastFrame()).toContain('Listening...');
|
||||||
});
|
});
|
||||||
|
|
||||||
unmount();
|
unmount();
|
||||||
@@ -5085,7 +5082,7 @@ describe('InputPrompt', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Should NOT show listening, instead should call handleInput which handles space
|
// Should NOT show listening, instead should call handleInput which handles space
|
||||||
expect(lastFrame()).not.toContain('🎙️ Listening...');
|
expect(lastFrame()).not.toContain('Listening...');
|
||||||
expect(mockBuffer.handleInput).toHaveBeenCalled();
|
expect(mockBuffer.handleInput).toHaveBeenCalled();
|
||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
@@ -5202,7 +5199,10 @@ describe('InputPrompt', () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(lastFrame()).toContain('Voice mode: Hold Space to record');
|
expect(lastFrame()).toContain('🎤 >');
|
||||||
|
expect(lastFrame()).toContain(
|
||||||
|
'Type your message or hold space to talk (Esc to exit)',
|
||||||
|
);
|
||||||
|
|
||||||
// Press space once
|
// Press space once
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
@@ -5211,14 +5211,14 @@ describe('InputPrompt', () => {
|
|||||||
|
|
||||||
// Should insert space optimistically
|
// Should insert space optimistically
|
||||||
expect(mockBuffer.insert).toHaveBeenCalledWith(' ');
|
expect(mockBuffer.insert).toHaveBeenCalledWith(' ');
|
||||||
expect(lastFrame()).not.toContain('🎙️ Listening...');
|
expect(lastFrame()).not.toContain('Listening...');
|
||||||
|
|
||||||
// Advance timer past HOLD_DELAY_MS
|
// Advance timer past HOLD_DELAY_MS
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
vi.advanceTimersByTime(700);
|
vi.advanceTimersByTime(700);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(lastFrame()).not.toContain('🎙️ Listening...');
|
expect(lastFrame()).not.toContain('Listening...');
|
||||||
unmount();
|
unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -5248,7 +5248,7 @@ describe('InputPrompt', () => {
|
|||||||
// Should have backspaced the optimistic space
|
// Should have backspaced the optimistic space
|
||||||
expect(mockBuffer.backspace).toHaveBeenCalled();
|
expect(mockBuffer.backspace).toHaveBeenCalled();
|
||||||
// Should show listening
|
// Should show listening
|
||||||
expect(lastFrame()).toContain('🎙️ Listening...');
|
expect(lastFrame()).toContain('Listening...');
|
||||||
});
|
});
|
||||||
|
|
||||||
unmount();
|
unmount();
|
||||||
@@ -5274,7 +5274,7 @@ describe('InputPrompt', () => {
|
|||||||
// Use a short interval in waitFor to prevent advancing fake timers past the 300ms RELEASE_DELAY_MS
|
// Use a short interval in waitFor to prevent advancing fake timers past the 300ms RELEASE_DELAY_MS
|
||||||
await waitFor(
|
await waitFor(
|
||||||
() => {
|
() => {
|
||||||
expect(lastFrame()).toContain('🎙️ Listening...');
|
expect(lastFrame()).toContain('Listening...');
|
||||||
},
|
},
|
||||||
{ interval: 10 },
|
{ interval: 10 },
|
||||||
);
|
);
|
||||||
@@ -5284,7 +5284,8 @@ describe('InputPrompt', () => {
|
|||||||
stdin.write(' ');
|
stdin.write(' ');
|
||||||
vi.advanceTimersByTime(100);
|
vi.advanceTimersByTime(100);
|
||||||
});
|
});
|
||||||
expect(lastFrame()).toContain('🎙️ Listening...');
|
expect(lastFrame()).toContain('🎤 >');
|
||||||
|
expect(lastFrame()).toContain('Listening...');
|
||||||
|
|
||||||
// Stop heartbeat (release)
|
// Stop heartbeat (release)
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
@@ -5292,7 +5293,7 @@ describe('InputPrompt', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(lastFrame()).not.toContain('🎙️ Listening...');
|
expect(lastFrame()).not.toContain('Listening...');
|
||||||
});
|
});
|
||||||
|
|
||||||
unmount();
|
unmount();
|
||||||
|
|||||||
@@ -360,6 +360,20 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
isShellSuggestionsVisible,
|
isShellSuggestionsVisible,
|
||||||
} = completion;
|
} = completion;
|
||||||
|
|
||||||
|
const effectivePlaceholder = useMemo(() => {
|
||||||
|
if (!isVoiceModeEnabled) return placeholder;
|
||||||
|
const voiceAction =
|
||||||
|
(settings.experimental.voice?.activationMode ?? 'push-to-talk') ===
|
||||||
|
'push-to-talk'
|
||||||
|
? 'hold space to talk'
|
||||||
|
: 'space to talk';
|
||||||
|
return ` Type your message or ${voiceAction} (Esc to exit)`;
|
||||||
|
}, [
|
||||||
|
isVoiceModeEnabled,
|
||||||
|
placeholder,
|
||||||
|
settings.experimental.voice?.activationMode,
|
||||||
|
]);
|
||||||
|
|
||||||
const showCursor =
|
const showCursor =
|
||||||
focus && isShellFocused && !isEmbeddedShellFocused && !copyModeEnabled;
|
focus && isShellFocused && !isEmbeddedShellFocused && !copyModeEnabled;
|
||||||
|
|
||||||
@@ -1786,6 +1800,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
useBackgroundColor={useBackgroundColor}
|
useBackgroundColor={useBackgroundColor}
|
||||||
>
|
>
|
||||||
<Box flexGrow={1} flexDirection="row" paddingX={1}>
|
<Box flexGrow={1} flexDirection="row" paddingX={1}>
|
||||||
|
{isVoiceModeEnabled && <Text color={theme.text.accent}>🎤 </Text>}
|
||||||
<Text
|
<Text
|
||||||
color={statusColor ?? theme.text.accent}
|
color={statusColor ?? theme.text.accent}
|
||||||
aria-label={statusText || undefined}
|
aria-label={statusText || undefined}
|
||||||
@@ -1812,35 +1827,25 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
|||||||
<Box flexGrow={1} flexDirection="column" ref={innerBoxRef}>
|
<Box flexGrow={1} flexDirection="column" ref={innerBoxRef}>
|
||||||
{isRecording && (
|
{isRecording && (
|
||||||
<Box flexDirection="row" marginBottom={0}>
|
<Box flexDirection="row" marginBottom={0}>
|
||||||
<Text color={theme.status.success}>🎙️ Listening...</Text>
|
<Text color={theme.status.success}>Listening...</Text>
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
{isVoiceModeEnabled && !isRecording && (
|
|
||||||
<Box flexDirection="row" marginBottom={0}>
|
|
||||||
<Text color={theme.text.secondary}>
|
|
||||||
> Voice mode:{' '}
|
|
||||||
{(settings.experimental.voice?.activationMode ??
|
|
||||||
'push-to-talk') === 'push-to-talk'
|
|
||||||
? 'Hold Space to record'
|
|
||||||
: 'Space to start/stop recording'}{' '}
|
|
||||||
(Esc to exit)
|
|
||||||
</Text>
|
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{buffer.text.length === 0 && !isRecording ? (
|
{buffer.text.length === 0 && !isRecording ? (
|
||||||
!isVoiceModeEnabled && placeholder ? (
|
effectivePlaceholder ? (
|
||||||
showCursor ? (
|
showCursor ? (
|
||||||
<Text
|
<Text
|
||||||
terminalCursorFocus={showCursor}
|
terminalCursorFocus={showCursor}
|
||||||
terminalCursorPosition={0}
|
terminalCursorPosition={0}
|
||||||
>
|
>
|
||||||
{chalk.inverse(placeholder.slice(0, 1))}
|
{chalk.inverse(effectivePlaceholder.slice(0, 1))}
|
||||||
<Text color={theme.text.secondary}>
|
<Text color={theme.text.secondary}>
|
||||||
{placeholder.slice(1)}
|
{effectivePlaceholder.slice(1)}
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
) : (
|
||||||
<Text color={theme.text.secondary}>{placeholder}</Text>
|
<Text color={theme.text.secondary}>
|
||||||
|
{effectivePlaceholder}
|
||||||
|
</Text>
|
||||||
)
|
)
|
||||||
) : null
|
) : null
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
Reference in New Issue
Block a user