mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
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:
@@ -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 ? (
|
||||
|
||||
@@ -201,6 +201,9 @@ export const InputForm: React.FC<InputFormProps> = ({
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Spacer */}
|
||||
<div className="flex-1 min-w-0" />
|
||||
|
||||
{/* Thinking button */}
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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';
|
||||
|
||||
|
||||
/* ===========================
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user