wip(vscode-ide-companion): 实现 quick win 功能

- 将 WebView 调整到编辑器右侧
- 添加 ChatHeader 组件,实现会话下拉菜单
- 替换模态框为紧凑型下拉菜单
- 更新会话切换逻辑,显示当前标题
- 清理旧的会话选择器样式
基于 Claude Code v2.0.43 UI 分析实现。
This commit is contained in:
yiliang114
2025-11-19 00:16:45 +08:00
parent 729a3d0ab3
commit 732220e651
52 changed files with 16502 additions and 1420 deletions

View File

@@ -0,0 +1,119 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
padding: 40px 20px;
}
.empty-state-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 32px;
max-width: 600px;
width: 100%;
}
.empty-state-logo {
display: flex;
flex-direction: column;
align-items: center;
gap: 24px;
}
.empty-state-logo-image {
width: 120px;
height: 120px;
object-fit: contain;
}
.empty-state-text {
text-align: center;
}
.empty-state-title {
font-size: 15px;
color: var(--app-primary-foreground);
line-height: 1.5;
font-weight: 400;
max-width: 400px;
}
/* Banner Styles */
.empty-state-banner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 12px 16px;
background-color: var(--app-input-secondary-background);
border: 1px solid var(--app-primary-border-color);
border-radius: var(--corner-radius-medium);
width: 100%;
max-width: 500px;
}
.banner-content {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
min-width: 0;
}
.banner-icon {
flex-shrink: 0;
width: 16px;
height: 16px;
fill: var(--app-primary-foreground);
}
.banner-content label {
font-size: 13px;
color: var(--app-primary-foreground);
margin: 0;
line-height: 1.4;
}
.banner-link {
color: var(--app-claude-orange);
text-decoration: none;
cursor: pointer;
}
.banner-link:hover {
text-decoration: underline;
}
.banner-close {
flex-shrink: 0;
width: 20px;
height: 20px;
padding: 0;
background: transparent;
border: none;
border-radius: var(--corner-radius-small);
color: var(--app-primary-foreground);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.2s;
}
.banner-close:hover {
background-color: var(--app-ghost-button-hover-background);
}
.banner-close svg {
width: 10px;
height: 10px;
}

View File

@@ -0,0 +1,86 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import type React from 'react';
import './EmptyState.css';
// Extend Window interface to include ICON_URI
declare global {
interface Window {
ICON_URI?: string;
}
}
export const EmptyState: React.FC = () => {
// Get icon URI from window, fallback to empty string if not available
const iconUri = window.ICON_URI || '';
return (
<div className="empty-state">
<div className="empty-state-content">
{/* Qwen Logo */}
<div className="empty-state-logo">
{iconUri && (
<img
src={iconUri}
alt="Qwen Logo"
className="empty-state-logo-image"
/>
)}
<div className="empty-state-text">
<div className="empty-state-title">
What to do first? Ask about this codebase or we can start writing
code.
</div>
</div>
</div>
{/* Info Banner */}
<div className="empty-state-banner">
<div className="banner-content">
<svg
className="banner-icon"
width="16"
height="16"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.14648 7.14648C5.34175 6.95122 5.65825 6.95122 5.85352 7.14648L8.35352 9.64648C8.44728 9.74025 8.5 9.86739 8.5 10C8.5 10.0994 8.47037 10.1958 8.41602 10.2773L8.35352 10.3535L5.85352 12.8535C5.65825 13.0488 5.34175 13.0488 5.14648 12.8535C4.95122 12.6583 4.95122 12.3417 5.14648 12.1465L7.29297 10L5.14648 7.85352C4.95122 7.65825 4.95122 7.34175 5.14648 7.14648Z"></path>
<path d="M14.5 12C14.7761 12 15 12.2239 15 12.5C15 12.7761 14.7761 13 14.5 13H9.5C9.22386 13 9 12.7761 9 12.5C9 12.2239 9.22386 12 9.5 12H14.5Z"></path>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M16.5 4C17.3284 4 18 4.67157 18 5.5V14.5C18 15.3284 17.3284 16 16.5 16H3.5C2.67157 16 2 15.3284 2 14.5V5.5C2 4.67157 2.67157 4 3.5 4H16.5ZM3.5 5C3.22386 5 3 5.22386 3 5.5V14.5C3 14.7761 3.22386 15 3.5 15H16.5C16.7761 15 17 14.7761 17 14.5V5.5C17 5.22386 16.7761 5 16.5 5H3.5Z"
></path>
</svg>
<label>
Prefer the Terminal experience?{' '}
<a href="#" className="banner-link">
Switch back in Settings.
</a>
</label>
</div>
<button className="banner-close" aria-label="Close banner">
<svg
width="10"
height="10"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1 1L13 13M1 13L13 1"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
></path>
</svg>
</button>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,115 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
/**
* PlanDisplay.css - Styles for the task plan component
*/
.plan-display {
background-color: rgba(100, 150, 255, 0.05);
border: 1px solid rgba(100, 150, 255, 0.3);
border-radius: 8px;
padding: 16px;
margin: 8px 0;
animation: fadeIn 0.3s ease-in;
}
.plan-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.plan-icon {
font-size: 18px;
}
.plan-title {
font-size: 14px;
font-weight: 600;
color: rgba(150, 180, 255, 1);
}
.plan-entries {
display: flex;
flex-direction: column;
gap: 8px;
}
.plan-entry {
display: flex;
gap: 8px;
padding: 8px;
background-color: var(--vscode-input-background);
border-radius: 4px;
border-left: 3px solid transparent;
transition: all 0.2s ease;
}
.plan-entry[data-priority="high"] {
border-left-color: #ff6b6b;
}
.plan-entry[data-priority="medium"] {
border-left-color: #ffd93d;
}
.plan-entry[data-priority="low"] {
border-left-color: #6bcf7f;
}
.plan-entry.completed {
opacity: 0.6;
}
.plan-entry.completed .plan-entry-content {
text-decoration: line-through;
}
.plan-entry.in_progress {
background-color: rgba(100, 150, 255, 0.1);
border-left-width: 4px;
}
.plan-entry-header {
display: flex;
align-items: center;
gap: 6px;
flex-shrink: 0;
}
.plan-entry-status,
.plan-entry-priority {
font-size: 14px;
}
.plan-entry-index {
font-size: 12px;
font-weight: 600;
color: var(--vscode-descriptionForeground);
min-width: 20px;
}
.plan-entry-content {
flex: 1;
font-size: 13px;
line-height: 1.5;
color: var(--vscode-foreground);
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

View File

@@ -0,0 +1,78 @@
/**
* @license
* Copyright 2025 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import type React from 'react';
import './PlanDisplay.css';
export interface PlanEntry {
content: string;
priority: 'high' | 'medium' | 'low';
status: 'pending' | 'in_progress' | 'completed';
}
interface PlanDisplayProps {
entries: PlanEntry[];
}
/**
* PlanDisplay component - displays AI's task plan/todo list
*/
export const PlanDisplay: React.FC<PlanDisplayProps> = ({ entries }) => {
const getPriorityIcon = (priority: string) => {
switch (priority) {
case 'high':
return '🔴';
case 'medium':
return '🟡';
case 'low':
return '🟢';
default:
return '⚪';
}
};
const getStatusIcon = (status: string) => {
switch (status) {
case 'pending':
return '⏱️';
case 'in_progress':
return '⚙️';
case 'completed':
return '✅';
default:
return '❓';
}
};
return (
<div className="plan-display">
<div className="plan-header">
<span className="plan-icon">📋</span>
<span className="plan-title">Task Plan</span>
</div>
<div className="plan-entries">
{entries.map((entry, index) => (
<div
key={index}
className={`plan-entry ${entry.status}`}
data-priority={entry.priority}
>
<div className="plan-entry-header">
<span className="plan-entry-status">
{getStatusIcon(entry.status)}
</span>
<span className="plan-entry-priority">
{getPriorityIcon(entry.priority)}
</span>
<span className="plan-entry-index">{index + 1}.</span>
</div>
<div className="plan-entry-content">{entry.content}</div>
</div>
))}
</div>
</div>
);
};