mirror of
https://github.com/google-gemini/gemini-cli
synced 2026-04-21 13:37:17 +00:00
Screen reader updates (#7307)
This commit is contained in:
parent
2fc857092e
commit
fe5bb6694e
13 changed files with 69 additions and 28 deletions
11
package-lock.json
generated
11
package-lock.json
generated
|
|
@ -8085,9 +8085,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/ink": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ink/-/ink-6.2.2.tgz",
|
||||
"integrity": "sha512-LN1f+/D8KKqMqRux08fIfA9wsEAJ9Bu9CiI3L6ih7bnqNSDUXT/JVJ0rUIc4NkjPiPaeI3BVNREcLYLz9ePSEg==",
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ink/-/ink-6.2.3.tgz",
|
||||
"integrity": "sha512-fQkfEJjKbLXIcVWEE3MvpYSnwtbbmRsmeNDNz1pIuOFlwE+UF2gsy228J36OXKZGWJWZJKUigphBSqCNMcARtg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@alcalzone/ansi-tokenize": "^0.2.0",
|
||||
|
|
@ -8104,7 +8104,6 @@
|
|||
"is-in-ci": "^2.0.0",
|
||||
"patch-console": "^2.0.0",
|
||||
"react-reconciler": "^0.32.0",
|
||||
"scheduler": "^0.26.0",
|
||||
"signal-exit": "^3.0.7",
|
||||
"slice-ansi": "^7.1.0",
|
||||
"stack-utils": "^2.0.6",
|
||||
|
|
@ -14519,7 +14518,7 @@
|
|||
"dotenv": "^17.1.0",
|
||||
"glob": "^10.4.1",
|
||||
"highlight.js": "^11.11.1",
|
||||
"ink": "^6.1.1",
|
||||
"ink": "^6.2.3",
|
||||
"ink-gradient": "^3.0.0",
|
||||
"ink-spinner": "^5.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
|
|
@ -14529,6 +14528,7 @@
|
|||
"react": "^19.1.0",
|
||||
"read-package-up": "^11.0.0",
|
||||
"shell-quote": "^1.8.3",
|
||||
"simple-git": "^3.28.0",
|
||||
"string-width": "^7.1.0",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"strip-json-comments": "^3.1.1",
|
||||
|
|
@ -14692,6 +14692,7 @@
|
|||
"version": "0.2.2",
|
||||
"dependencies": {
|
||||
"@google/genai": "1.13.0",
|
||||
"@lvce-editor/ripgrep": "^1.6.0",
|
||||
"@modelcontextprotocol/sdk": "^1.11.0",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@opentelemetry/exporter-logs-otlp-grpc": "^0.203.0",
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
"dotenv": "^17.1.0",
|
||||
"glob": "^10.4.1",
|
||||
"highlight.js": "^11.11.1",
|
||||
"ink": "^6.1.1",
|
||||
"ink": "^6.2.3",
|
||||
"ink-gradient": "^3.0.0",
|
||||
"ink-spinner": "^5.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
|
|
|
|||
|
|
@ -483,10 +483,10 @@ export async function loadCliConfig(
|
|||
}
|
||||
|
||||
const sandboxConfig = await loadSandboxConfig(settings, argv);
|
||||
|
||||
// The screen reader argument takes precedence over the accessibility setting.
|
||||
const screenReader =
|
||||
argv.screenReader ?? settings.ui?.accessibility?.screenReader ?? false;
|
||||
argv.screenReader !== undefined
|
||||
? argv.screenReader
|
||||
: (settings.ui?.accessibility?.screenReader ?? false);
|
||||
return new Config({
|
||||
sessionId,
|
||||
embeddingModel: DEFAULT_GEMINI_EMBEDDING_MODEL,
|
||||
|
|
|
|||
|
|
@ -5,11 +5,15 @@
|
|||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { Text } from 'ink';
|
||||
import { Text, useIsScreenReaderEnabled } from 'ink';
|
||||
import Spinner from 'ink-spinner';
|
||||
import type { SpinnerName } from 'cli-spinners';
|
||||
import { useStreamingContext } from '../contexts/StreamingContext.js';
|
||||
import { StreamingState } from '../types.js';
|
||||
import {
|
||||
SCREEN_READER_LOADING,
|
||||
SCREEN_READER_RESPONDING,
|
||||
} from '../textConstants.js';
|
||||
|
||||
interface GeminiRespondingSpinnerProps {
|
||||
/**
|
||||
|
|
@ -24,11 +28,19 @@ export const GeminiRespondingSpinner: React.FC<
|
|||
GeminiRespondingSpinnerProps
|
||||
> = ({ nonRespondingDisplay, spinnerType = 'dots' }) => {
|
||||
const streamingState = useStreamingContext();
|
||||
|
||||
const isScreenReaderEnabled = useIsScreenReaderEnabled();
|
||||
if (streamingState === StreamingState.Responding) {
|
||||
return <Spinner type={spinnerType} />;
|
||||
return isScreenReaderEnabled ? (
|
||||
<Text>{SCREEN_READER_RESPONDING}</Text>
|
||||
) : (
|
||||
<Spinner type={spinnerType} />
|
||||
);
|
||||
} else if (nonRespondingDisplay) {
|
||||
return <Text>{nonRespondingDisplay}</Text>;
|
||||
return isScreenReaderEnabled ? (
|
||||
<Text>{SCREEN_READER_LOADING}</Text>
|
||||
) : (
|
||||
<Text>{nonRespondingDisplay}</Text>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import {
|
|||
cleanupOldClipboardImages,
|
||||
} from '../utils/clipboardUtils.js';
|
||||
import * as path from 'node:path';
|
||||
import { SCREEN_READER_USER_PREFIX } from '../constants.js';
|
||||
import { SCREEN_READER_USER_PREFIX } from '../textConstants.js';
|
||||
|
||||
export interface InputPromptProps {
|
||||
buffer: TextBuffer;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { Box, Text } from 'ink';
|
|||
import type { CompressionProps } from '../../types.js';
|
||||
import Spinner from 'ink-spinner';
|
||||
import { Colors } from '../../colors.js';
|
||||
import { SCREEN_READER_MODEL_PREFIX } from '../../constants.js';
|
||||
import { SCREEN_READER_MODEL_PREFIX } from '../../textConstants.js';
|
||||
|
||||
export interface CompressionDisplayProps {
|
||||
compression: CompressionProps;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { Box, Text, useIsScreenReaderEnabled } from 'ink';
|
||||
import { Colors } from '../../colors.js';
|
||||
import crypto from 'node:crypto';
|
||||
import { colorizeCode, colorizeLine } from '../../utils/CodeColorizer.js';
|
||||
|
|
@ -107,6 +107,7 @@ export const DiffRenderer: React.FC<DiffRendererProps> = ({
|
|||
terminalWidth,
|
||||
theme,
|
||||
}) => {
|
||||
const screenReaderEnabled = useIsScreenReaderEnabled();
|
||||
if (!diffContent || typeof diffContent !== 'string') {
|
||||
return <Text color={Colors.AccentYellow}>No diff content.</Text>;
|
||||
}
|
||||
|
|
@ -120,6 +121,17 @@ export const DiffRenderer: React.FC<DiffRendererProps> = ({
|
|||
</Box>
|
||||
);
|
||||
}
|
||||
if (screenReaderEnabled) {
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
{parsedLines.map((line, index) => (
|
||||
<Text key={index}>
|
||||
{line.type}: {line.content}
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Check if the diff represents a new file (only additions and header lines)
|
||||
const isNewFile = parsedLines.every(
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import type React from 'react';
|
|||
import { Text, Box } from 'ink';
|
||||
import { MarkdownDisplay } from '../../utils/MarkdownDisplay.js';
|
||||
import { Colors } from '../../colors.js';
|
||||
import { SCREEN_READER_MODEL_PREFIX } from '../../constants.js';
|
||||
import { SCREEN_READER_MODEL_PREFIX } from '../../textConstants.js';
|
||||
|
||||
interface GeminiMessageProps {
|
||||
text: string;
|
||||
|
|
|
|||
|
|
@ -129,18 +129,22 @@ const ToolStatusIndicator: React.FC<ToolStatusIndicatorProps> = ({
|
|||
/>
|
||||
)}
|
||||
{status === ToolCallStatus.Success && (
|
||||
<Text color={Colors.AccentGreen}>{TOOL_STATUS.SUCCESS}</Text>
|
||||
<Text color={Colors.AccentGreen} aria-label={'Success:'}>
|
||||
{TOOL_STATUS.SUCCESS}
|
||||
</Text>
|
||||
)}
|
||||
{status === ToolCallStatus.Confirming && (
|
||||
<Text color={Colors.AccentYellow}>{TOOL_STATUS.CONFIRMING}</Text>
|
||||
<Text color={Colors.AccentYellow} aria-label={'Confirming:'}>
|
||||
{TOOL_STATUS.CONFIRMING}
|
||||
</Text>
|
||||
)}
|
||||
{status === ToolCallStatus.Canceled && (
|
||||
<Text color={Colors.AccentYellow} bold>
|
||||
<Text color={Colors.AccentYellow} aria-label={'Canceled:'} bold>
|
||||
{TOOL_STATUS.CANCELED}
|
||||
</Text>
|
||||
)}
|
||||
{status === ToolCallStatus.Error && (
|
||||
<Text color={Colors.AccentRed} bold>
|
||||
<Text color={Colors.AccentRed} aria-label={'Error:'} bold>
|
||||
{TOOL_STATUS.ERROR}
|
||||
</Text>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import type React from 'react';
|
||||
import { Text, Box } from 'ink';
|
||||
import { Colors } from '../../colors.js';
|
||||
import { SCREEN_READER_USER_PREFIX } from '../../constants.js';
|
||||
import { SCREEN_READER_USER_PREFIX } from '../../textConstants.js';
|
||||
import { isSlashCommand as checkIsSlashCommand } from '../../utils/commandUtils.js';
|
||||
|
||||
interface UserMessageProps {
|
||||
|
|
|
|||
|
|
@ -65,7 +65,6 @@ export function RadioButtonSelect<T>({
|
|||
const [scrollOffset, setScrollOffset] = useState(0);
|
||||
const [numberInput, setNumberInput] = useState('');
|
||||
const numberInputTimer = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const newScrollOffset = Math.max(
|
||||
0,
|
||||
|
|
@ -195,7 +194,10 @@ export function RadioButtonSelect<T>({
|
|||
return (
|
||||
<Box key={item.label} alignItems="center">
|
||||
<Box minWidth={2} flexShrink={0}>
|
||||
<Text color={isSelected ? Colors.AccentGreen : Colors.Foreground}>
|
||||
<Text
|
||||
color={isSelected ? Colors.AccentGreen : Colors.Foreground}
|
||||
aria-hidden
|
||||
>
|
||||
{isSelected ? '●' : ' '}
|
||||
</Text>
|
||||
</Box>
|
||||
|
|
@ -203,6 +205,7 @@ export function RadioButtonSelect<T>({
|
|||
marginRight={1}
|
||||
flexShrink={0}
|
||||
minWidth={itemNumberText.length}
|
||||
aria-state={{ checked: isSelected }}
|
||||
>
|
||||
<Text color={numberColor}>{itemNumberText}</Text>
|
||||
</Box>
|
||||
|
|
|
|||
|
|
@ -16,10 +16,6 @@ export const STREAM_DEBOUNCE_MS = 100;
|
|||
|
||||
export const SHELL_COMMAND_NAME = 'Shell Command';
|
||||
|
||||
export const SCREEN_READER_USER_PREFIX = 'User: ';
|
||||
|
||||
export const SCREEN_READER_MODEL_PREFIX = 'Model: ';
|
||||
|
||||
// Tool status symbols used in ToolMessage component
|
||||
export const TOOL_STATUS = {
|
||||
SUCCESS: '✓',
|
||||
|
|
|
|||
13
packages/cli/src/ui/textConstants.ts
Normal file
13
packages/cli/src/ui/textConstants.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
export const SCREEN_READER_USER_PREFIX = 'User: ';
|
||||
|
||||
export const SCREEN_READER_MODEL_PREFIX = 'Model: ';
|
||||
|
||||
export const SCREEN_READER_LOADING = 'loading';
|
||||
|
||||
export const SCREEN_READER_RESPONDING = 'responding';
|
||||
Loading…
Reference in a new issue