style(vscode-ide-companion): update UI components and styling

- Updated chat message container spacing and styling
- Adjusted AssistantMessage text color handling
- Enhanced QwenMessageTimeline CSS rules for better visual flow
- Moved InfoBanner to ui directory for better organization
- Updated InputForm styling
- Added new CSS classes and updated tailwind configuration
- Improved timeline connection lines and message item spacing

These changes enhance the visual appearance and user experience of the chat interface.
This commit is contained in:
yiliang114
2025-12-04 08:28:54 +08:00
parent 86cd06ef43
commit 3053e6c41f
10 changed files with 185 additions and 36 deletions

View File

@@ -32,7 +32,7 @@ import { EmptyState } from './components/ui/EmptyState.js';
import type { PlanEntry } from './components/PlanDisplay.js';
import { type CompletionItem } from './types/CompletionTypes.js';
import { useCompletionTrigger } from './hooks/useCompletionTrigger.js';
import { InfoBanner } from './components/InfoBanner.js';
import { InfoBanner } from './components/ui/InfoBanner.js';
import { ChatHeader } from './components/ui/layouts/ChatHeader.js';
import {
UserMessage,
@@ -487,7 +487,7 @@ export const App: React.FC = () => {
<div
ref={messagesContainerRef}
className="chat-messages flex-1 overflow-y-auto overflow-x-hidden pt-5 pr-5 pl-5 pb-[120px] flex flex-col relative min-w-0 focus:outline-none [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-white/20 [&::-webkit-scrollbar-thumb]:rounded-sm [&::-webkit-scrollbar-thumb:hover]:bg-white/30 [&>*]:flex [&>*]:gap-0 [&>*]:items-start [&>*]:text-left [&>*]:py-2 [&>*:not(:last-child)]:pb-[10px] [&>.message-item]:px-0 [&>.message-item]:py-0 [&>*]:flex-col [&>*]:relative [&>*]:animate-[fadeIn_0.2s_ease-in]"
className="chat-messages flex-1 overflow-y-auto overflow-x-hidden pt-5 pr-5 pl-5 pb-[120px] flex flex-col relative min-w-0 focus:outline-none [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-white/20 [&::-webkit-scrollbar-thumb]:rounded-sm [&::-webkit-scrollbar-thumb:hover]:bg-white/30 [&>*]:flex [&>*]:gap-0 [&>*]:items-start [&>*]:text-left [&>*]:py-2 [&>*:not(:last-child)]:pb-[8px] [&>*]:flex-col [&>*]:relative [&>*]:animate-[fadeIn_0.2s_ease-in]"
style={{ backgroundColor: 'var(--app-primary-background)' }}
>
{!hasContent ? (

View File

@@ -201,6 +201,9 @@ export const InputForm: React.FC<InputFormProps> = ({
</button>
)}
{/* Spacer */}
<div className="flex-1 min-w-0" />
{/* Thinking button */}
<button
type="button"

View File

@@ -66,11 +66,10 @@ export const AssistantMessage: React.FC<AssistantMessageProps> = ({
paddingBottom: '8px',
}}
>
<span style={{ color: 'var(--app-secondary-foreground)', width: '100%' }}>
<span style={{ width: '100%' }}>
<p
style={{
margin: 0,
color: 'var(--app-secondary-foreground)',
width: '100%',
wordWrap: 'break-word',
overflowWrap: 'break-word',

View File

@@ -8,7 +8,7 @@
* 实现原理:
* 1. 为所有AI消息项(qwen-message.message-item:not(.user-message-container))创建垂直连接线
* 2. 当用户消息(user-message-container)隔断AI消息序列时会自动重新开始一组新的时间线规则
* 3. 每组AI消息序列的开始元素(top设为15px),结束元素(bottom设为calc(100% - 15px))
* 3. 每组AI消息序列的开始元素(top设为15px),结束元素(底部预留15px)
*/
/* 默认的连接线样式 - 为所有AI消息项创建完整高度的连接线 */
@@ -25,13 +25,37 @@
/* 处理每组AI消息序列的开始 - 包括整个消息列表的第一个AI消息和被用户消息隔断后的新AI消息 */
.qwen-message.message-item:not(.user-message-container):first-child::after,
.user-message-container + .qwen-message.message-item:not(.user-message-container)::after {
.user-message-container + .qwen-message.message-item:not(.user-message-container)::after,
/* 如果前一个兄弟不是 .qwen-message.message-item例如等待提示、哨兵元素或卡片样式工具调用也作为一组新开始 */
.chat-messages > :not(.qwen-message.message-item)
+ .qwen-message.message-item:not(.user-message-container)::after {
top: 15px;
}
/* 处理每组AI消息序列的结尾 */
.qwen-message.message-item:not(.user-message-container):last-child::after,
.qwen-message.message-item:not(.user-message-container):has(+ .user-message-container)::after {
bottom: calc(100% - 15px);
/* 后一个兄弟是用户消息时 */
.qwen-message.message-item:not(.user-message-container):has(+ .user-message-container)::after,
/* 或者后一个兄弟不是 .qwen-message.message-item如等待提示、哨兵元素、卡片样式工具调用等时 */
.qwen-message.message-item:not(.user-message-container):has(+ :not(.qwen-message.message-item))::after,
/* 真正是父容器最后一个子元素时 */
.qwen-message.message-item:not(.user-message-container):last-child::after {
/* 注意:同时设置 top 和 bottom 时高度为 (容器高度 - top - bottom)。
* 这里期望“底部留 15px 间距”,因此 bottom 应为 15px而不是 calc(100% - 15px))。 */
top: 0;
}
bottom: calc(100% - 15px);
}
.user-message-container:first-child {
margin-top: 0;
}
.message-item {
padding: 8px 0;
width: 100%;
align-items: flex-start;
padding-left: 30px;
user-select: text;
position: relative;
padding-top: 8px;
padding-bottom: 8px;
}

View File

@@ -45,7 +45,7 @@ export const UserMessage: React.FC<UserMessageProps> = ({
return (
<div
className="qwen-message message-item user-message-container flex gap-0 items-start text-left flex-col relative animate-[fadeIn_0.2s_ease-in]"
className="qwen-message user-message-container flex gap-0 my-1 items-start text-left flex-col relative animate-[fadeIn_0.2s_ease-in]"
style={{ position: 'relative' }}
>
<div

View File

@@ -4,21 +4,19 @@
* SPDX-License-Identifier: Apache-2.0
*
* Simplified timeline styles for tool calls and messages
* Merged version of both SimpleTimeline.css files
* Only keeping actually used styles
*/
/* Basic timeline container */
.simple-toolcall-container,
.simple-timeline-container {
/* ToolCallContainer timeline styles (from LayoutComponents.css) */
.toolcall-container {
position: relative;
padding-left: 30px;
padding-top: 8px;
padding-bottom: 8px;
}
/* Timeline connector - simple version */
.simple-toolcall-container::after,
.simple-timeline-container::after {
/* ToolCallContainer timeline connector */
.toolcall-container::after {
content: '';
position: absolute;
left: 12px;
@@ -28,28 +26,45 @@
background-color: var(--app-primary-border-color);
}
/* First item connector starts lower */
.simple-toolcall-container:first-child::after,
.simple-timeline-container:first-child::after {
/* First item: connector starts from status point position */
.toolcall-container:first-child::after {
top: 24px;
}
/* Last item connector ends higher */
.simple-toolcall-container:last-child::after,
.simple-timeline-container:last-child::after {
/* Last item: connector shows only upper part */
.toolcall-container:last-child::after {
height: calc(100% - 24px);
top: 0;
bottom: auto;
}
/* Bullet point */
.simple-toolcall-container::before,
.simple-timeline-container::before {
content: '\25cf';
/* AssistantMessage timeline styles */
.assistant-message-container {
position: relative;
padding-left: 30px;
padding-top: 8px;
padding-bottom: 8px;
}
/* AssistantMessage timeline connector */
.assistant-message-container::after {
content: '';
position: absolute;
left: 8px;
padding-top: 2px;
font-size: 10px;
color: var(--app-secondary-foreground);
z-index: 2;
left: 12px;
top: 0;
bottom: 0;
width: 1px;
background-color: var(--app-primary-border-color);
}
/* First item: connector starts from status point position */
.assistant-message-container:first-child::after {
top: 24px;
}
/* Last item: connector shows only upper part */
.assistant-message-container:last-child::after {
height: calc(100% - 24px);
top: 0;
bottom: auto;
}

View File

@@ -0,0 +1,107 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import type React from 'react';
import { TerminalIcon, CloseIcon } from '../icons/index.js';
interface InfoBannerProps {
/**
* Whether the banner is visible
*/
visible: boolean;
/**
* Callback when the banner is dismissed
*/
onDismiss: () => void;
/**
* Optional: Custom message content (if not provided, uses default)
*/
message?: React.ReactNode;
/**
* Optional: Custom link text
*/
linkText?: string;
/**
* Optional: Callback when the link is clicked
*/
onLinkClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void;
}
export const InfoBanner: React.FC<InfoBannerProps> = ({
visible,
onDismiss,
message,
linkText = 'Switch back in Settings.',
onLinkClick,
}) => {
if (!visible) {
return null;
}
return (
<div
className="flex items-center justify-between"
style={{
gap: '12px',
padding: '8px 16px',
}}
>
<div className="flex items-center flex-1 min-w-0" style={{ gap: '8px' }}>
{/* Icon */}
<TerminalIcon className="flex-shrink-0 w-4 h-4" />
{/* Message */}
<label
className="m-0 leading-snug text-[11px]"
style={{ color: 'var(--app-primary-foreground)' }}
>
{message || (
<>
Prefer the Terminal experience?{' '}
{onLinkClick && (
<a
href="#"
className="no-underline hover:underline cursor-pointer outline-none"
style={{ color: 'var(--app-qwen-orange)' }}
onClick={onLinkClick}
>
{linkText}
</a>
)}
</>
)}
</label>
</div>
{/* Close button */}
<button
className="flex items-center justify-center cursor-pointer rounded"
style={{
background: 'none',
border: 'none',
padding: '6px',
color: 'var(--app-secondary-foreground)',
borderRadius: '4px',
}}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor =
'var(--app-ghost-button-hover-background)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = 'transparent';
}}
aria-label="Close banner"
onClick={onDismiss}
>
<CloseIcon className="w-[10px] h-[10px]" />
</button>
</div>
);
};

View File

@@ -10,8 +10,9 @@
/* Import component styles */
@import '../components/toolcalls/shared/DiffDisplay.css';
@import '../components/messages/AssistantMessage.css';
@import '../components/toolcalls/shared/MergedSimpleTimeline.css';
@import '../components/toolcalls/shared/SimpleTimeline.css';
@import '../components/messages/QwenMessageTimeline.css';
@import '../components/MarkdownRenderer.css';
/* ===========================

View File

@@ -95,7 +95,8 @@
color: var(--app-secondary-foreground);
}
.btn-text-compact--primary {
color: var(--app-primary-foreground);
color: var(--app-secondary-foreground);
/* color: var(--app-primary-foreground); */
}
.btn-text-compact:hover {
background-color: var(--app-ghost-button-hover-background);