feat: Redesign CLI welcome screen and improve visual consistency

This commit is contained in:
tanzhenxin 2026-01-16 11:48:31 +08:00
parent ff5ea3c6d7
commit b804b1f48a
81 changed files with 1474 additions and 1342 deletions

View file

@ -7,64 +7,121 @@
import type React from 'react';
import { Box, Text } from 'ink';
import Gradient from 'ink-gradient';
import { AuthType, shortenPath, tildeifyPath } from '@qwen-code/qwen-code-core';
import { theme } from '../semantic-colors.js';
import { shortAsciiLogo, longAsciiLogo, tinyAsciiLogo } from './AsciiArt.js';
import { shortAsciiLogo } from './AsciiArt.js';
import { getAsciiArtWidth } from '../utils/textUtils.js';
import { useTerminalSize } from '../hooks/useTerminalSize.js';
interface HeaderProps {
customAsciiArt?: string; // For user-defined ASCII art
version: string;
nightly: boolean;
authType?: AuthType;
model: string;
workingDirectory: string;
}
// Format auth type for display
function formatAuthType(authType?: AuthType): string {
switch (authType) {
case AuthType.QWEN_OAUTH:
return 'Qwen OAuth';
case AuthType.USE_OPENAI:
return 'OpenAI';
default:
return 'Unknown';
}
}
export const Header: React.FC<HeaderProps> = ({
customAsciiArt,
version,
nightly,
authType,
model,
workingDirectory,
}) => {
const { columns: terminalWidth } = useTerminalSize();
let displayTitle;
const widthOfLongLogo = getAsciiArtWidth(longAsciiLogo);
const widthOfShortLogo = getAsciiArtWidth(shortAsciiLogo);
if (customAsciiArt) {
displayTitle = customAsciiArt;
} else if (terminalWidth >= widthOfLongLogo) {
displayTitle = longAsciiLogo;
} else if (terminalWidth >= widthOfShortLogo) {
displayTitle = shortAsciiLogo;
} else {
displayTitle = tinyAsciiLogo;
}
const displayLogo = customAsciiArt ?? shortAsciiLogo;
const logoWidth = getAsciiArtWidth(displayLogo);
const formattedAuthType = formatAuthType(authType);
const artWidth = getAsciiArtWidth(displayTitle);
// Calculate available space properly:
// First determine if logo can be shown, then use remaining space for path
const logoGap = 4; // Gap between logo and info panel
const infoPanelPadding = 4; // paddingX={1} on each side + borders = ~4 chars
const minPathLength = 40; // Minimum readable path length
const minInfoPanelWidth = minPathLength + infoPanelPadding;
// Check if we have enough space for logo + gap + minimum info panel
const showLogo = terminalWidth >= logoWidth + logoGap + minInfoPanelWidth;
// Calculate available width for info panel (use all remaining space)
const availableInfoPanelWidth = showLogo
? terminalWidth - logoWidth - logoGap
: terminalWidth;
// Calculate max path length (subtract padding/borders from available space)
const maxPathLength = Math.max(
minPathLength,
availableInfoPanelWidth - infoPanelPadding,
);
// Now shorten the path to fit the available space
const displayPath = shortenPath(
tildeifyPath(workingDirectory),
maxPathLength,
);
// Use theme gradient colors if available, otherwise use text colors (excluding primary)
const gradientColors = theme.ui.gradient || [
theme.text.secondary,
theme.text.link,
theme.text.accent,
];
return (
<Box
alignItems="flex-start"
width={artWidth}
flexShrink={0}
flexDirection="column"
>
{theme.ui.gradient ? (
<Gradient colors={theme.ui.gradient}>
<Text>{displayTitle}</Text>
</Gradient>
) : (
<Text>{displayTitle}</Text>
)}
{nightly && (
<Box width="100%" flexDirection="row" justifyContent="flex-end">
{theme.ui.gradient ? (
<Gradient colors={theme.ui.gradient}>
<Text>v{version}</Text>
<Box flexDirection="row" alignItems="center" marginLeft={2} marginRight={2}>
{/* Left side: ASCII logo (only if enough space) */}
{showLogo && (
<>
<Box flexShrink={0}>
<Gradient colors={gradientColors}>
<Text>{displayLogo}</Text>
</Gradient>
) : (
<Text>v{version}</Text>
)}
</Box>
</Box>
{/* Fixed gap between logo and info panel */}
<Box width={4} />
</>
)}
{/* Right side: Info panel (flexible width) */}
<Box
flexDirection="column"
borderStyle="round"
borderColor={theme.border.default}
paddingX={1}
flexGrow={1}
>
{/* Title line: >_ Qwen Code (v{version}) */}
<Text>
<Text bold color={theme.text.accent}>
&gt;_ Qwen Code
</Text>
<Text color={theme.text.secondary}> (v{version})</Text>
</Text>
{/* Empty line for spacing */}
<Text> </Text>
{/* Auth and Model line */}
<Text>
<Text color={theme.text.secondary}>
{formattedAuthType} | {model}
</Text>
<Text color={theme.text.secondary}> (/auth to change)</Text>
</Text>
{/* Directory line */}
<Text color={theme.text.secondary}>{displayPath}</Text>
</Box>
</Box>
);
};