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 { PlanEntry } from './components/PlanDisplay.js';
|
||||||
import { type CompletionItem } from './types/CompletionTypes.js';
|
import { type CompletionItem } from './types/CompletionTypes.js';
|
||||||
import { useCompletionTrigger } from './hooks/useCompletionTrigger.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 { ChatHeader } from './components/ui/layouts/ChatHeader.js';
|
||||||
import {
|
import {
|
||||||
UserMessage,
|
UserMessage,
|
||||||
@@ -487,7 +487,7 @@ export const App: React.FC = () => {
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
ref={messagesContainerRef}
|
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)' }}
|
style={{ backgroundColor: 'var(--app-primary-background)' }}
|
||||||
>
|
>
|
||||||
{!hasContent ? (
|
{!hasContent ? (
|
||||||
|
|||||||
@@ -201,6 +201,9 @@ export const InputForm: React.FC<InputFormProps> = ({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Spacer */}
|
||||||
|
<div className="flex-1 min-w-0" />
|
||||||
|
|
||||||
{/* Thinking button */}
|
{/* Thinking button */}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -66,11 +66,10 @@ export const AssistantMessage: React.FC<AssistantMessageProps> = ({
|
|||||||
paddingBottom: '8px',
|
paddingBottom: '8px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span style={{ color: 'var(--app-secondary-foreground)', width: '100%' }}>
|
<span style={{ width: '100%' }}>
|
||||||
<p
|
<p
|
||||||
style={{
|
style={{
|
||||||
margin: 0,
|
margin: 0,
|
||||||
color: 'var(--app-secondary-foreground)',
|
|
||||||
width: '100%',
|
width: '100%',
|
||||||
wordWrap: 'break-word',
|
wordWrap: 'break-word',
|
||||||
overflowWrap: 'break-word',
|
overflowWrap: 'break-word',
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
* 实现原理:
|
* 实现原理:
|
||||||
* 1. 为所有AI消息项(qwen-message.message-item:not(.user-message-container))创建垂直连接线
|
* 1. 为所有AI消息项(qwen-message.message-item:not(.user-message-container))创建垂直连接线
|
||||||
* 2. 当用户消息(user-message-container)隔断AI消息序列时,会自动重新开始一组新的时间线规则
|
* 2. 当用户消息(user-message-container)隔断AI消息序列时,会自动重新开始一组新的时间线规则
|
||||||
* 3. 每组AI消息序列的开始元素(top设为15px),结束元素(bottom设为calc(100% - 15px))
|
* 3. 每组AI消息序列的开始元素(top设为15px),结束元素(底部预留15px)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* 默认的连接线样式 - 为所有AI消息项创建完整高度的连接线 */
|
/* 默认的连接线样式 - 为所有AI消息项创建完整高度的连接线 */
|
||||||
@@ -25,13 +25,37 @@
|
|||||||
|
|
||||||
/* 处理每组AI消息序列的开始 - 包括整个消息列表的第一个AI消息和被用户消息隔断后的新AI消息 */
|
/* 处理每组AI消息序列的开始 - 包括整个消息列表的第一个AI消息和被用户消息隔断后的新AI消息 */
|
||||||
.qwen-message.message-item:not(.user-message-container):first-child::after,
|
.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;
|
top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 处理每组AI消息序列的结尾 */
|
/* 处理每组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 {
|
.qwen-message.message-item:not(.user-message-container):has(+ .user-message-container)::after,
|
||||||
bottom: calc(100% - 15px);
|
/* 或者后一个兄弟不是 .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;
|
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 (
|
return (
|
||||||
<div
|
<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' }}
|
style={{ position: 'relative' }}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -4,21 +4,19 @@
|
|||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*
|
*
|
||||||
* Simplified timeline styles for tool calls and messages
|
* Simplified timeline styles for tool calls and messages
|
||||||
* Merged version of both SimpleTimeline.css files
|
* Only keeping actually used styles
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Basic timeline container */
|
/* ToolCallContainer timeline styles (from LayoutComponents.css) */
|
||||||
.simple-toolcall-container,
|
.toolcall-container {
|
||||||
.simple-timeline-container {
|
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 30px;
|
padding-left: 30px;
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Timeline connector - simple version */
|
/* ToolCallContainer timeline connector */
|
||||||
.simple-toolcall-container::after,
|
.toolcall-container::after {
|
||||||
.simple-timeline-container::after {
|
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 12px;
|
left: 12px;
|
||||||
@@ -28,28 +26,45 @@
|
|||||||
background-color: var(--app-primary-border-color);
|
background-color: var(--app-primary-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* First item connector starts lower */
|
/* First item: connector starts from status point position */
|
||||||
.simple-toolcall-container:first-child::after,
|
.toolcall-container:first-child::after {
|
||||||
.simple-timeline-container:first-child::after {
|
|
||||||
top: 24px;
|
top: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Last item connector ends higher */
|
/* Last item: connector shows only upper part */
|
||||||
.simple-toolcall-container:last-child::after,
|
.toolcall-container:last-child::after {
|
||||||
.simple-timeline-container:last-child::after {
|
|
||||||
height: calc(100% - 24px);
|
height: calc(100% - 24px);
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: auto;
|
bottom: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bullet point */
|
/* AssistantMessage timeline styles */
|
||||||
.simple-toolcall-container::before,
|
.assistant-message-container {
|
||||||
.simple-timeline-container::before {
|
position: relative;
|
||||||
content: '\25cf';
|
padding-left: 30px;
|
||||||
|
padding-top: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* AssistantMessage timeline connector */
|
||||||
|
.assistant-message-container::after {
|
||||||
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 8px;
|
left: 12px;
|
||||||
padding-top: 2px;
|
top: 0;
|
||||||
font-size: 10px;
|
bottom: 0;
|
||||||
color: var(--app-secondary-foreground);
|
width: 1px;
|
||||||
z-index: 2;
|
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 component styles */
|
||||||
@import '../components/toolcalls/shared/DiffDisplay.css';
|
@import '../components/toolcalls/shared/DiffDisplay.css';
|
||||||
@import '../components/messages/AssistantMessage.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/messages/QwenMessageTimeline.css';
|
||||||
|
@import '../components/MarkdownRenderer.css';
|
||||||
|
|
||||||
|
|
||||||
/* ===========================
|
/* ===========================
|
||||||
|
|||||||
@@ -95,7 +95,8 @@
|
|||||||
color: var(--app-secondary-foreground);
|
color: var(--app-secondary-foreground);
|
||||||
}
|
}
|
||||||
.btn-text-compact--primary {
|
.btn-text-compact--primary {
|
||||||
color: var(--app-primary-foreground);
|
color: var(--app-secondary-foreground);
|
||||||
|
/* color: var(--app-primary-foreground); */
|
||||||
}
|
}
|
||||||
.btn-text-compact:hover {
|
.btn-text-compact:hover {
|
||||||
background-color: var(--app-ghost-button-hover-background);
|
background-color: var(--app-ghost-button-hover-background);
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ export default {
|
|||||||
'./src/webview/components/toolcalls/**/*.{js,jsx,ts,tsx}',
|
'./src/webview/components/toolcalls/**/*.{js,jsx,ts,tsx}',
|
||||||
'./src/webview/components/InProgressToolCall.tsx',
|
'./src/webview/components/InProgressToolCall.tsx',
|
||||||
'./src/webview/components/MessageContent.tsx',
|
'./src/webview/components/MessageContent.tsx',
|
||||||
'./src/webview/components/InfoBanner.tsx',
|
|
||||||
'./src/webview/components/InputForm.tsx',
|
'./src/webview/components/InputForm.tsx',
|
||||||
'./src/webview/components/PermissionDrawer.tsx',
|
'./src/webview/components/PermissionDrawer.tsx',
|
||||||
'./src/webview/components/PlanDisplay.tsx',
|
'./src/webview/components/PlanDisplay.tsx',
|
||||||
|
|||||||
Reference in New Issue
Block a user