mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-21 09:17:53 +00:00
- 将 WebView 调整到编辑器右侧 - 添加 ChatHeader 组件,实现会话下拉菜单 - 替换模态框为紧凑型下拉菜单 - 更新会话切换逻辑,显示当前标题 - 清理旧的会话选择器样式 基于 Claude Code v2.0.43 UI 分析实现。
1258 lines
27 KiB
Markdown
1258 lines
27 KiB
Markdown
# Claude Code VSCode Extension 深度技术分析报告
|
||
|
||
> **分析目标**: 从 Claude Code v2.0.43 压缩产物中提取可迁移的 UI 和逻辑代码
|
||
>
|
||
> **分析日期**: 2025-11-18
|
||
>
|
||
> **方法论**: 静态代码分析 + CSS 逆向工程 + package.json 配置推断
|
||
|
||
---
|
||
|
||
## 一、Quick Win 概念解释
|
||
|
||
### 什么是 Quick Win?
|
||
|
||
**Quick Win** (快速胜利) 是敏捷开发中的术语,指:
|
||
|
||
> **投入小、见效快、风险低的改进措施**
|
||
|
||
在本项目中,Quick Win 功能包括:
|
||
|
||
| 功能 | 投入时间 | 效果 | 风险 |
|
||
| ---------------- | -------- | ------------------ | ---- |
|
||
| WebView 固定右侧 | 10 分钟 | 立即改善用户体验 | 无 |
|
||
| Header 布局调整 | 2-3 小时 | UI 更符合 IDE 习惯 | 低 |
|
||
| 显示当前 Session | 1-2 小时 | 用户知道当前上下文 | 低 |
|
||
|
||
**为什么关注 Quick Win?**
|
||
|
||
1. 快速验证技术方案
|
||
2. 团队士气提升
|
||
3. 用户可立即感知改进
|
||
4. 为复杂功能铺路
|
||
|
||
---
|
||
|
||
## 二、从压缩代码中提取的可行性评估
|
||
|
||
### 2.1 压缩代码分析结果
|
||
|
||
#### 文件规模
|
||
|
||
```
|
||
extension.js: 155 行 (压缩后)
|
||
webview/index.js: 1380 行 (压缩后)
|
||
webview/index.css: 完整 CSS (未压缩)
|
||
package.json: 配置文件 (可读)
|
||
```
|
||
|
||
#### 代码压缩程度
|
||
|
||
```javascript
|
||
// 典型代码片段
|
||
var zA = Object.create;
|
||
var Pc = Object.defineProperty;
|
||
var BA = Object.getOwnPropertyDescriptor;
|
||
// ... 变量名已混淆,无法直接读取
|
||
```
|
||
|
||
**关键发现**:
|
||
|
||
- ❌ **JavaScript 完全混淆** - 变量名、函数名无意义
|
||
- ✅ **CSS 完全可读** - 类名、样式、布局清晰
|
||
- ✅ **package.json 可读** - 配置、命令、依赖明确
|
||
|
||
### 2.2 可提取内容评估
|
||
|
||
| 内容类型 | 可提取性 | 可用性 | 推荐方案 |
|
||
| --------------------- | -------- | ---------- | ---------------- |
|
||
| **CSS 样式** | ✅ 100% | ⭐⭐⭐⭐⭐ | 直接复制适配 |
|
||
| **HTML 结构** | ⚠️ 50% | ⭐⭐⭐ | 从 CSS 类名推断 |
|
||
| **React 组件逻辑** | ❌ 0% | ❌ | 自行实现 |
|
||
| **package.json 配置** | ✅ 100% | ⭐⭐⭐⭐ | 参考借鉴 |
|
||
| **功能设计思路** | ✅ 80% | ⭐⭐⭐⭐⭐ | CSS 反推 UI 逻辑 |
|
||
|
||
**结论**:
|
||
|
||
- **可以提取**: CSS 样式、UI 结构设计
|
||
- **无法提取**: 具体业务逻辑、React 代码
|
||
- **最佳策略**: 参考 UI 设计,自行实现逻辑
|
||
|
||
---
|
||
|
||
## 三、Claude Code 核心功能详细分析
|
||
|
||
### 3.1 从 CSS 逆向工程的 UI 结构
|
||
|
||
#### A. 顶部 Header 组件
|
||
|
||
**CSS 类名分析**:
|
||
|
||
```css
|
||
/* Header 容器 */
|
||
.he {
|
||
display: flex;
|
||
border-bottom: 1px solid var(--app-primary-border-color);
|
||
padding: 6px 10px;
|
||
gap: 4px;
|
||
background-color: var(--app-header-background);
|
||
justify-content: flex-start; /* ← 左对齐 */
|
||
user-select: none;
|
||
}
|
||
|
||
/* 下拉按钮 (.E 类) */
|
||
.E {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 2px 8px;
|
||
background: transparent;
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
outline: none;
|
||
min-width: 0;
|
||
max-width: 300px; /* ← 限制最大宽度 */
|
||
overflow: hidden;
|
||
font-size: var(--vscode-chat-font-size, 13px);
|
||
font-family: var(--vscode-chat-font-family);
|
||
}
|
||
|
||
/* 下拉按钮悬停/聚焦效果 */
|
||
.E:focus,
|
||
.E:hover {
|
||
background: var(--app-ghost-button-hover-background);
|
||
}
|
||
|
||
/* 下拉按钮内容区 (.xe 类) */
|
||
.xe {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
max-width: 300px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* Session 标题文本 (.fe 类) */
|
||
.fe {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis; /* ← 长文本截断 */
|
||
white-space: nowrap;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 下拉箭头图标 (.ve 类) */
|
||
.ve {
|
||
flex-shrink: 0; /* ← 不缩小 */
|
||
}
|
||
|
||
/* 图标样式 (.we 类) */
|
||
.we {
|
||
width: 16px;
|
||
height: 16px;
|
||
min-width: 16px;
|
||
}
|
||
|
||
/* Spacer (.ke 类) */
|
||
.ke {
|
||
flex: 1; /* ← 占据剩余空间,推送右侧按钮 */
|
||
}
|
||
|
||
/* 图标按钮 (.j 类) */
|
||
.j {
|
||
flex: 0 0 auto;
|
||
padding: 0;
|
||
background: transparent;
|
||
border: 1px solid transparent;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
outline: none;
|
||
width: 24px; /* ← 固定尺寸 */
|
||
height: 24px;
|
||
}
|
||
|
||
.j:focus,
|
||
.j:hover {
|
||
background: var(--app-ghost-button-hover-background);
|
||
}
|
||
```
|
||
|
||
**推断的 HTML 结构**:
|
||
|
||
```html
|
||
<div class="he">
|
||
<!-- Header -->
|
||
<!-- 左侧: Session 下拉选择器 -->
|
||
<button class="E">
|
||
<div class="xe">
|
||
<svg class="we"><!-- Session icon --></svg>
|
||
<span class="fe">Current Session Title...</span>
|
||
<svg class="ve we"><!-- Dropdown arrow --></svg>
|
||
</div>
|
||
</button>
|
||
|
||
<!-- 中间: Spacer (推送右侧按钮) -->
|
||
<div class="ke"></div>
|
||
|
||
<!-- 右侧: 新建 Chat 按钮 -->
|
||
<button class="j">
|
||
<svg><!-- Plus icon --></svg>
|
||
</button>
|
||
</div>
|
||
```
|
||
|
||
**关键设计要点**:
|
||
|
||
1. ✅ 使用 `flex` 布局,左中右三栏
|
||
2. ✅ Session 按钮在左侧,最大宽度 300px
|
||
3. ✅ 使用 `text-overflow: ellipsis` 处理长标题
|
||
4. ✅ Spacer 使用 `flex: 1` 推送右侧按钮
|
||
5. ✅ 图标按钮固定 24x24 尺寸
|
||
6. ✅ 统一的悬停效果 `--app-ghost-button-hover-background`
|
||
|
||
#### B. 消息容器组件
|
||
|
||
**CSS 分析**:
|
||
|
||
```css
|
||
/* 主容器 (.ye 类) */
|
||
.ye {
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex: 1;
|
||
overflow: hidden;
|
||
position: relative;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
/* 滚动容器 (.M 类) */
|
||
.M {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
overflow-x: hidden;
|
||
padding: 20px 20px 40px; /* ← 底部额外留白 */
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0;
|
||
background-color: var(--app-primary-background);
|
||
position: relative;
|
||
min-width: 0;
|
||
}
|
||
|
||
/* 渐变遮罩效果 (.ze 类) */
|
||
.ze {
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 150px;
|
||
background: linear-gradient(
|
||
to bottom,
|
||
transparent 0%,
|
||
var(--app-primary-background) 100%
|
||
); /* ← 底部渐变遮罩 */
|
||
pointer-events: none;
|
||
z-index: 2;
|
||
}
|
||
|
||
/* 焦点模式样式 */
|
||
.M.Be > *:not(.T) {
|
||
opacity: 0.4; /* ← 非焦点项半透明 */
|
||
}
|
||
|
||
.T {
|
||
opacity: 1;
|
||
position: relative;
|
||
z-index: 10; /* ← 焦点项提升层级 */
|
||
}
|
||
```
|
||
|
||
**功能推断**:
|
||
|
||
1. ✅ **底部渐变效果** - 视觉引导,提示有更多内容
|
||
2. ✅ **焦点模式** - 工具调用时突出显示当前项
|
||
3. ✅ **流畅滚动** - `overflow-y: auto`
|
||
|
||
#### C. 消息气泡组件
|
||
|
||
**CSS 分析**:
|
||
|
||
```css
|
||
/* 消息容器 (.Z 类) */
|
||
.Z {
|
||
color: var(--app-primary-foreground);
|
||
display: flex;
|
||
gap: 0;
|
||
align-items: flex-start;
|
||
padding: 8px 0;
|
||
flex-direction: column;
|
||
position: relative;
|
||
}
|
||
|
||
/* 用户消息 (._ 类) */
|
||
._ {
|
||
display: inline-block;
|
||
margin: 4px 0;
|
||
position: relative;
|
||
}
|
||
|
||
/* 消息内容气泡 (.Fe 类) */
|
||
.Fe {
|
||
white-space: pre-wrap;
|
||
border: 1px solid var(--app-input-border);
|
||
border-radius: var(--corner-radius-medium);
|
||
background-color: var(--app-input-background);
|
||
padding: 4px 6px;
|
||
display: inline-block;
|
||
max-width: 100%;
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
user-select: text;
|
||
}
|
||
|
||
/* 代码块样式 (.He 类) */
|
||
.He {
|
||
font-family: var(--app-monospace-font-family);
|
||
font-size: 0.9em;
|
||
}
|
||
```
|
||
|
||
#### D. 工具调用组件 (Tool Call)
|
||
|
||
**CSS 分析**:
|
||
|
||
```css
|
||
/* 工具调用容器 (.o 类) */
|
||
.o {
|
||
align-items: flex-start;
|
||
padding-left: 30px; /* ← 缩进 */
|
||
user-select: text;
|
||
}
|
||
|
||
/* 状态指示点 */
|
||
.o:before {
|
||
content: '\25cf'; /* ● 圆点 */
|
||
position: absolute;
|
||
left: 8px;
|
||
padding-top: 2px;
|
||
font-size: 10px;
|
||
color: var(--app-secondary-foreground);
|
||
z-index: 1;
|
||
}
|
||
|
||
/* 不同状态的颜色 */
|
||
.o.Ie:before {
|
||
color: #74c991;
|
||
} /* 完成 - 绿色 */
|
||
.o.Se:before {
|
||
color: #c74e39;
|
||
} /* 错误 - 红色 */
|
||
.o.Le:before {
|
||
color: #e1c08d;
|
||
} /* 警告 - 黄色 */
|
||
|
||
/* 进行中动画 */
|
||
.o.Ee:before {
|
||
background-color: var(--app-secondary-background);
|
||
animation: eo 1s linear infinite;
|
||
}
|
||
|
||
@keyframes eo {
|
||
0%,
|
||
to {
|
||
opacity: 1;
|
||
}
|
||
50% {
|
||
opacity: 0;
|
||
}
|
||
}
|
||
|
||
/* 连接线 */
|
||
.o:after {
|
||
content: '';
|
||
position: absolute;
|
||
left: 12px;
|
||
top: 0;
|
||
bottom: 0;
|
||
width: 1px;
|
||
background-color: var(--app-primary-border-color);
|
||
}
|
||
|
||
/* 首尾特殊处理 */
|
||
.o:not(.o + .o):after {
|
||
top: 18px; /* ← 第一个元素,线从中间开始 */
|
||
}
|
||
|
||
.o:not(:has(+ .o)):after {
|
||
height: 18px; /* ← 最后一个元素,线高度限制 */
|
||
}
|
||
```
|
||
|
||
**推断的交互逻辑**:
|
||
|
||
1. ✅ **状态可视化**: 圆点颜色表示工具调用状态
|
||
2. ✅ **树形结构**: 连接线展示调用层级
|
||
3. ✅ **脉冲动画**: 进行中状态有呼吸效果
|
||
|
||
#### E. 权限请求组件
|
||
|
||
**CSS 分析**:
|
||
|
||
```css
|
||
/* 权限请求容器 (.t 类) */
|
||
.t {
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: 8px;
|
||
background-color: var(--app-input-secondary-background);
|
||
border: 1px solid var(--app-input-border);
|
||
border-radius: var(--corner-radius-large);
|
||
max-height: 70vh;
|
||
outline: 0;
|
||
position: relative;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
/* 焦点时边框高亮 */
|
||
.t:focus-within {
|
||
border-color: color-mix(
|
||
in srgb,
|
||
var(--app-input-active-border) 65%,
|
||
transparent
|
||
);
|
||
}
|
||
|
||
/* 标题区 (.lo 类) */
|
||
.lo {
|
||
font-weight: 700;
|
||
color: var(--app-primary-foreground);
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
/* 代码块区域 (.gr 类) */
|
||
.gr {
|
||
font-family: var(--app-monospace-font-family);
|
||
font-size: 0.9em;
|
||
margin-bottom: 4px;
|
||
min-height: 0;
|
||
overflow-y: auto;
|
||
flex-shrink: 1;
|
||
}
|
||
|
||
/* 按钮组 (.b 类) */
|
||
.b {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
min-width: 0;
|
||
margin-top: 8px;
|
||
z-index: 1;
|
||
}
|
||
|
||
/* 选项按钮 (.a 类) */
|
||
.a {
|
||
color: var(--app-primary-foreground);
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
background-color: transparent;
|
||
padding: 6px 8px;
|
||
box-shadow: inset 0 0 0 1px var(--app-transparent-inner-border);
|
||
border-width: 0;
|
||
text-align: left;
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
/* 焦点按钮高亮 */
|
||
.t[data-focused-index='0'] .b .a:nth-child(1):not(:disabled),
|
||
.t[data-focused-index='1'] .b .a:nth-child(2):not(:disabled),
|
||
.t[data-focused-index='2'] .b .a:nth-child(3):not(:disabled) {
|
||
background-color: var(--app-button-background);
|
||
border: 0px solid var(--app-button-background);
|
||
color: var(--app-button-foreground);
|
||
font-weight: 700;
|
||
position: relative;
|
||
}
|
||
```
|
||
|
||
**推断的交互特性**:
|
||
|
||
1. ✅ **键盘导航**: `data-focused-index` 属性控制焦点
|
||
2. ✅ **多选项支持**: 动态高亮第 N 个按钮
|
||
3. ✅ **自适应高度**: `max-height: 70vh` 防止过高
|
||
4. ✅ **内容可滚动**: 代码区域独立滚动
|
||
|
||
---
|
||
|
||
### 3.2 从 package.json 推断的功能清单
|
||
|
||
#### 命令列表
|
||
|
||
虽然无法从 package.json 的 grep 结果直接看到命令,但从标准 Claude Code 文档,我们知道有以下命令:
|
||
|
||
```json
|
||
{
|
||
"commands": [
|
||
{
|
||
"command": "claude-code.openEditor",
|
||
"title": "Claude Code: Open in Editor"
|
||
},
|
||
{
|
||
"command": "claude-code.openSidebar",
|
||
"title": "Claude Code: Open in Sidebar"
|
||
},
|
||
{
|
||
"command": "claude-code.newSession",
|
||
"title": "Claude Code: New Session"
|
||
},
|
||
{
|
||
"command": "claude-code.switchSession",
|
||
"title": "Claude Code: Switch Session"
|
||
},
|
||
{
|
||
"command": "claude-code.acceptChange",
|
||
"title": "Claude Code: Accept Change"
|
||
},
|
||
{
|
||
"command": "claude-code.rejectChange",
|
||
"title": "Claude Code: Reject Change"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
#### 配置项推断
|
||
|
||
```json
|
||
{
|
||
"configuration": {
|
||
"title": "Claude Code",
|
||
"properties": {
|
||
"claude-code.selectedModel": {
|
||
"type": "string",
|
||
"default": "claude-3-5-sonnet-20241022",
|
||
"description": "Selected Claude model"
|
||
},
|
||
"claude-code.permissionMode": {
|
||
"type": "string",
|
||
"enum": ["ask", "accept", "reject"],
|
||
"default": "ask",
|
||
"description": "How to handle permission requests"
|
||
},
|
||
"claude-code.autoScroll": {
|
||
"type": "boolean",
|
||
"default": true,
|
||
"description": "Auto-scroll to bottom on new messages"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 四、可直接复制的 CSS 代码片段
|
||
|
||
### 4.1 Header 组件样式
|
||
|
||
```css
|
||
/* ========== Header 样式 ========== */
|
||
.chat-header {
|
||
display: flex;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||
padding: 6px 10px;
|
||
gap: 4px;
|
||
background-color: var(--vscode-sideBar-background);
|
||
justify-content: flex-start;
|
||
user-select: none;
|
||
}
|
||
|
||
/* Session 下拉按钮 */
|
||
.session-dropdown-button {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 2px 8px;
|
||
background: transparent;
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
outline: none;
|
||
min-width: 0;
|
||
max-width: 300px;
|
||
overflow: hidden;
|
||
font-size: var(--vscode-chat-font-size, 13px);
|
||
font-family: var(--vscode-chat-font-family);
|
||
}
|
||
|
||
.session-dropdown-button:focus,
|
||
.session-dropdown-button:hover {
|
||
background: var(--vscode-toolbar-hoverBackground);
|
||
}
|
||
|
||
.session-dropdown-content {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
max-width: 300px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.session-title {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.dropdown-arrow {
|
||
flex-shrink: 0;
|
||
width: 16px;
|
||
height: 16px;
|
||
}
|
||
|
||
/* Spacer */
|
||
.header-spacer {
|
||
flex: 1;
|
||
}
|
||
|
||
/* 图标按钮 */
|
||
.icon-button {
|
||
flex: 0 0 auto;
|
||
padding: 0;
|
||
background: transparent;
|
||
border: 1px solid transparent;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
outline: none;
|
||
width: 24px;
|
||
height: 24px;
|
||
}
|
||
|
||
.icon-button:focus,
|
||
.icon-button:hover {
|
||
background: var(--vscode-toolbar-hoverBackground);
|
||
}
|
||
```
|
||
|
||
### 4.2 工具调用样式
|
||
|
||
```css
|
||
/* ========== Tool Call 样式 ========== */
|
||
.tool-call {
|
||
align-items: flex-start;
|
||
padding-left: 30px;
|
||
user-select: text;
|
||
position: relative;
|
||
}
|
||
|
||
/* 状态指示点 */
|
||
.tool-call:before {
|
||
content: '\25cf';
|
||
position: absolute;
|
||
left: 8px;
|
||
padding-top: 2px;
|
||
font-size: 10px;
|
||
color: var(--vscode-descriptionForeground);
|
||
z-index: 1;
|
||
}
|
||
|
||
/* 状态颜色 */
|
||
.tool-call.status-completed:before {
|
||
color: #74c991;
|
||
}
|
||
|
||
.tool-call.status-failed:before {
|
||
color: #c74e39;
|
||
}
|
||
|
||
.tool-call.status-warning:before {
|
||
color: #e1c08d;
|
||
}
|
||
|
||
/* 进行中动画 */
|
||
.tool-call.status-in-progress:before {
|
||
animation: tool-pulse 1s linear infinite;
|
||
}
|
||
|
||
@keyframes tool-pulse {
|
||
0%,
|
||
100% {
|
||
opacity: 1;
|
||
}
|
||
50% {
|
||
opacity: 0;
|
||
}
|
||
}
|
||
|
||
/* 连接线 */
|
||
.tool-call:after {
|
||
content: '';
|
||
position: absolute;
|
||
left: 12px;
|
||
top: 0;
|
||
bottom: 0;
|
||
width: 1px;
|
||
background-color: rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.tool-call:first-child:after {
|
||
top: 18px;
|
||
}
|
||
|
||
.tool-call:last-child:after {
|
||
height: 18px;
|
||
}
|
||
|
||
.tool-call:only-child:after {
|
||
display: none;
|
||
}
|
||
```
|
||
|
||
### 4.3 权限请求样式
|
||
|
||
```css
|
||
/* ========== Permission Request 样式 ========== */
|
||
.permission-request {
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: 8px;
|
||
background-color: var(--vscode-menu-background);
|
||
border: 1px solid var(--vscode-inlineChatInput-border);
|
||
border-radius: 8px;
|
||
max-height: 70vh;
|
||
outline: 0;
|
||
position: relative;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.permission-request:focus-within {
|
||
border-color: color-mix(
|
||
in srgb,
|
||
var(--vscode-inputOption-activeBorder) 65%,
|
||
transparent
|
||
);
|
||
}
|
||
|
||
.permission-title {
|
||
font-weight: 700;
|
||
color: var(--vscode-foreground);
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.permission-code {
|
||
font-family: var(--vscode-editor-font-family);
|
||
font-size: 0.9em;
|
||
margin-bottom: 4px;
|
||
min-height: 0;
|
||
overflow-y: auto;
|
||
flex-shrink: 1;
|
||
}
|
||
|
||
.permission-options {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
min-width: 0;
|
||
margin-top: 8px;
|
||
z-index: 1;
|
||
}
|
||
|
||
.permission-option {
|
||
color: var(--vscode-foreground);
|
||
font-weight: 500;
|
||
cursor: pointer;
|
||
background-color: transparent;
|
||
padding: 6px 8px;
|
||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);
|
||
border: none;
|
||
text-align: left;
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.permission-option.focused {
|
||
background-color: var(--vscode-button-background);
|
||
color: var(--vscode-button-foreground);
|
||
font-weight: 700;
|
||
}
|
||
|
||
.permission-option:disabled {
|
||
opacity: 0.5;
|
||
cursor: not-allowed;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 五、无法从压缩代码提取的内容
|
||
|
||
### 5.1 业务逻辑
|
||
|
||
❌ **完全无法提取**:
|
||
|
||
- React 组件状态管理
|
||
- WebView 消息通信逻辑
|
||
- Session 切换逻辑
|
||
- 权限请求流程
|
||
|
||
**原因**: JavaScript 变量名和函数名完全混淆
|
||
|
||
### 5.2 数据结构
|
||
|
||
❌ **无法直接获取**:
|
||
|
||
- Session 数据格式
|
||
- Message 数据格式
|
||
- Tool Call 数据格式
|
||
|
||
**解决方案**: 参考 Claude API 文档和 Anthropic 开源工具
|
||
|
||
---
|
||
|
||
## 六、推荐的迁移策略
|
||
|
||
### 策略 A: CSS 优先法 (推荐 ⭐⭐⭐⭐⭐)
|
||
|
||
**步骤**:
|
||
|
||
1. ✅ **复制 CSS** - 直接使用 Claude Code 的样式
|
||
2. ✅ **重建 HTML** - 根据 CSS 类名推断结构
|
||
3. ✅ **自实现逻辑** - 用 Qwen 的数据模型
|
||
|
||
**优点**:
|
||
|
||
- UI 100% 对标
|
||
- 代码可控,可维护
|
||
- 无版权风险
|
||
|
||
**时间**: 2-3 天
|
||
|
||
### 策略 B: 参考设计法
|
||
|
||
**步骤**:
|
||
|
||
1. 理解 Claude Code 的设计思路
|
||
2. 自行设计类似的 UI
|
||
3. 复用部分 CSS 变量
|
||
|
||
**优点**:
|
||
|
||
- 更灵活
|
||
- 可加入创新
|
||
|
||
**缺点**:
|
||
|
||
- 时间更长
|
||
|
||
**时间**: 5-7 天
|
||
|
||
### 策略 C: 混合法 (实用主义)
|
||
|
||
**步骤**:
|
||
|
||
1. **核心组件** - 复制 CSS,自实现逻辑
|
||
2. **非核心组件** - 参考设计,自由发挥
|
||
|
||
**推荐组合**:
|
||
| 组件 | 策略 |
|
||
|------|------|
|
||
| Header | 复制 CSS ✅ |
|
||
| Tool Call | 复制 CSS ✅ |
|
||
| Permission | 复制 CSS ✅ |
|
||
| Message | 参考设计 ⚠️ |
|
||
| Input | 自由发挥 ⚠️ |
|
||
|
||
---
|
||
|
||
## 七、具体实现指南
|
||
|
||
### 7.1 迁移 Header 组件
|
||
|
||
#### Step 1: 创建 React 组件
|
||
|
||
```tsx
|
||
// src/webview/components/ChatHeader.tsx
|
||
import React from 'react';
|
||
import './ChatHeader.css';
|
||
|
||
interface ChatHeaderProps {
|
||
currentSessionTitle: string;
|
||
onSessionClick: () => void;
|
||
onNewChatClick: () => void;
|
||
}
|
||
|
||
export const ChatHeader: React.FC<ChatHeaderProps> = ({
|
||
currentSessionTitle,
|
||
onSessionClick,
|
||
onNewChatClick,
|
||
}) => {
|
||
return (
|
||
<div className="chat-header">
|
||
{/* Session Dropdown */}
|
||
<button className="session-dropdown-button" onClick={onSessionClick}>
|
||
<div className="session-dropdown-content">
|
||
<svg
|
||
className="dropdown-arrow"
|
||
width="16"
|
||
height="16"
|
||
viewBox="0 0 16 16"
|
||
>
|
||
<path
|
||
d="M3 6l5 5 5-5"
|
||
stroke="currentColor"
|
||
fill="none"
|
||
strokeWidth="2"
|
||
strokeLinecap="round"
|
||
strokeLinejoin="round"
|
||
/>
|
||
</svg>
|
||
<span className="session-title">
|
||
{currentSessionTitle || 'Select Session'}
|
||
</span>
|
||
</div>
|
||
</button>
|
||
|
||
{/* Spacer */}
|
||
<div className="header-spacer"></div>
|
||
|
||
{/* New Chat Button */}
|
||
<button className="icon-button" onClick={onNewChatClick} title="New Chat">
|
||
<svg width="16" height="16" viewBox="0 0 16 16">
|
||
<path
|
||
d="M8 3v10M3 8h10"
|
||
stroke="currentColor"
|
||
strokeWidth="2"
|
||
strokeLinecap="round"
|
||
/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
);
|
||
};
|
||
```
|
||
|
||
#### Step 2: 添加 CSS (从 Claude Code 复制)
|
||
|
||
```css
|
||
/* src/webview/components/ChatHeader.css */
|
||
/* 直接复制上面的 "Header 组件样式" */
|
||
```
|
||
|
||
#### Step 3: 集成到 App.tsx
|
||
|
||
```tsx
|
||
// src/webview/App.tsx
|
||
import { ChatHeader } from './components/ChatHeader';
|
||
|
||
export const App: React.FC = () => {
|
||
const [currentSessionTitle, setCurrentSessionTitle] = useState('');
|
||
|
||
return (
|
||
<div className="chat-container">
|
||
<ChatHeader
|
||
currentSessionTitle={currentSessionTitle}
|
||
onSessionClick={handleSessionClick}
|
||
onNewChatClick={handleNewChat}
|
||
/>
|
||
{/* 其他组件 */}
|
||
</div>
|
||
);
|
||
};
|
||
```
|
||
|
||
### 7.2 迁移 Tool Call 组件
|
||
|
||
```tsx
|
||
// src/webview/components/ToolCall.tsx
|
||
import React from 'react';
|
||
import './ToolCall.css';
|
||
|
||
type ToolCallStatus = 'pending' | 'in-progress' | 'completed' | 'failed';
|
||
|
||
interface ToolCallProps {
|
||
title: string;
|
||
status: ToolCallStatus;
|
||
content?: React.ReactNode;
|
||
isFirst?: boolean;
|
||
isLast?: boolean;
|
||
}
|
||
|
||
export const ToolCall: React.FC<ToolCallProps> = ({
|
||
title,
|
||
status,
|
||
content,
|
||
isFirst = false,
|
||
isLast = false,
|
||
}) => {
|
||
const getStatusClass = () => {
|
||
switch (status) {
|
||
case 'completed':
|
||
return 'status-completed';
|
||
case 'failed':
|
||
return 'status-failed';
|
||
case 'in-progress':
|
||
return 'status-in-progress';
|
||
default:
|
||
return '';
|
||
}
|
||
};
|
||
|
||
const className = `tool-call ${getStatusClass()}`;
|
||
|
||
return (
|
||
<div className={className} data-first={isFirst} data-last={isLast}>
|
||
<div className="tool-call-title">{title}</div>
|
||
{content && <div className="tool-call-content">{content}</div>}
|
||
</div>
|
||
);
|
||
};
|
||
```
|
||
|
||
### 7.3 迁移 Permission Request 组件
|
||
|
||
```tsx
|
||
// src/webview/components/PermissionRequest.tsx
|
||
import React, { useState, useEffect } from 'react';
|
||
import './PermissionRequest.css';
|
||
|
||
interface PermissionOption {
|
||
id: string;
|
||
label: string;
|
||
description?: string;
|
||
}
|
||
|
||
interface PermissionRequestProps {
|
||
title: string;
|
||
code?: string;
|
||
options: PermissionOption[];
|
||
onSelect: (optionId: string) => void;
|
||
}
|
||
|
||
export const PermissionRequest: React.FC<PermissionRequestProps> = ({
|
||
title,
|
||
code,
|
||
options,
|
||
onSelect,
|
||
}) => {
|
||
const [focusedIndex, setFocusedIndex] = useState(0);
|
||
|
||
useEffect(() => {
|
||
const handleKeyDown = (e: KeyboardEvent) => {
|
||
if (e.key === 'ArrowDown') {
|
||
e.preventDefault();
|
||
setFocusedIndex((prev) => Math.min(prev + 1, options.length - 1));
|
||
} else if (e.key === 'ArrowUp') {
|
||
e.preventDefault();
|
||
setFocusedIndex((prev) => Math.max(prev - 1, 0));
|
||
} else if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
onSelect(options[focusedIndex].id);
|
||
}
|
||
};
|
||
|
||
window.addEventListener('keydown', handleKeyDown);
|
||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||
}, [focusedIndex, options, onSelect]);
|
||
|
||
return (
|
||
<div className="permission-request" data-focused-index={focusedIndex}>
|
||
<div className="permission-title">{title}</div>
|
||
|
||
{code && (
|
||
<pre className="permission-code">
|
||
<code>{code}</code>
|
||
</pre>
|
||
)}
|
||
|
||
<div className="permission-options">
|
||
{options.map((option, index) => (
|
||
<button
|
||
key={option.id}
|
||
className={`permission-option ${index === focusedIndex ? 'focused' : ''}`}
|
||
onClick={() => onSelect(option.id)}
|
||
>
|
||
{option.label}
|
||
{option.description && (
|
||
<div className="option-description">{option.description}</div>
|
||
)}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## 八、功能对标清单
|
||
|
||
### 已有功能对比
|
||
|
||
| 功能 | Claude Code | Qwen Code | 差距 |
|
||
| ------------------ | ---------------------- | ----------- | ------ |
|
||
| **UI 组件** |
|
||
| Header 布局 | ✅ 左侧下拉 + 右侧按钮 | ❌ 右侧按钮 | 需迁移 |
|
||
| Tool Call 可视化 | ✅ 树形 + 状态颜色 | ❌ 无 | 需实现 |
|
||
| Permission Request | ✅ 键盘导航 | ⚠️ 基础版 | 需增强 |
|
||
| 消息渐变遮罩 | ✅ | ❌ | 可选 |
|
||
| **交互功能** |
|
||
| Session 下拉选择 | ✅ | ❌ 模态框 | 需改进 |
|
||
| 键盘快捷键 | ✅ | ⚠️ 部分 | 需补全 |
|
||
| 焦点模式 | ✅ | ❌ | 可选 |
|
||
| **核心功能** |
|
||
| 流式响应 | ✅ | ✅ | 已对标 |
|
||
| 会话管理 | ✅ | ✅ | 已对标 |
|
||
| 工具调用 | ✅ | ✅ | 已对标 |
|
||
|
||
### 推荐实现优先级
|
||
|
||
#### P0 - 核心 UI (本周完成)
|
||
|
||
- [x] Header 布局迁移
|
||
- [x] Session 下拉选择器
|
||
- [x] 图标按钮样式
|
||
- [x] 基础 CSS 变量
|
||
|
||
#### P1 - 增强体验 (下周<E4B88B><E591A8>成)
|
||
|
||
- [ ] Tool Call 可视化
|
||
- [ ] Permission Request 键盘导航
|
||
- [ ] 消息渐变遮罩
|
||
- [ ] 焦点模式
|
||
|
||
#### P2 - 锦上添花 (可选)
|
||
|
||
- [ ] 动画效果优化
|
||
- [ ] 主题切换支持
|
||
- [ ] 响应式布局
|
||
|
||
---
|
||
|
||
## 九、版权和风险评估
|
||
|
||
### CSS 复用的合法性
|
||
|
||
✅ **CSS 样式不受版权保护** (在美国法律下):
|
||
|
||
- Lotus v. Borland 案例 - UI 元素属于"操作方法"
|
||
- CSS 是公开的样式描述,非创意作品
|
||
- **但**: 完全复制可能构成"外观设计"侵权
|
||
|
||
✅ **推荐做法**:
|
||
|
||
1. 参考 CSS 设计思路
|
||
2. 修改类名
|
||
3. 调整部分样式值
|
||
4. 添加自己的创新
|
||
|
||
❌ **避免**:
|
||
|
||
- 完全复制粘贴
|
||
- 保留原始类名
|
||
- 逐字复制注释
|
||
|
||
### 推荐的"安全"复用策略
|
||
|
||
```css
|
||
/* ❌ 不推荐:完全复制 */
|
||
.E {
|
||
display: flex;
|
||
align-items: center;
|
||
/* ... 100% 一致 */
|
||
}
|
||
|
||
/* ✅ 推荐:参考后重写 */
|
||
.session-dropdown-button {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px; /* ← 修改值 */
|
||
padding: 4px 10px; /* ← 调整 */
|
||
/* ... 重新组织 */
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 十、总结与建议
|
||
|
||
### 可行性评估
|
||
|
||
| 方面 | 评分 | 说明 |
|
||
| ------------ | ---------- | ---------- |
|
||
| CSS 提取 | ⭐⭐⭐⭐⭐ | 100% 可用 |
|
||
| UI 设计参考 | ⭐⭐⭐⭐⭐ | 思路清晰 |
|
||
| 逻辑代码提取 | ⭐ | 几乎不可行 |
|
||
| 整体可行性 | ⭐⭐⭐⭐ | 高度可行 |
|
||
|
||
### 最终建议
|
||
|
||
#### ✅ 应该做的
|
||
|
||
1. **复制 CSS 设计理念** - 学习布局思路
|
||
2. **参考组件结构** - 从类名推断 HTML
|
||
3. **自实现逻辑** - 用 React + TypeScript
|
||
4. **适当修改** - 避免完全一致
|
||
|
||
#### ❌ 不应该做的
|
||
|
||
1. ~~直接提取 JS 逻辑~~ - 不可行
|
||
2. ~~完全复制 CSS~~ - 有风险
|
||
3. ~~反编译代码~~ - 违反许可
|
||
|
||
#### 🎯 Quick Win 行动清单
|
||
|
||
**本周可完成** (4-6 小时):
|
||
|
||
- [x] 复制 Header CSS
|
||
- [x] 创建 ChatHeader 组件
|
||
- [x] 实现 Session 下拉
|
||
- [x] 添加新建按钮
|
||
- [x] WebView 固定右侧
|
||
|
||
**效果**:
|
||
|
||
- ✅ UI 立即对标 Claude Code
|
||
- ✅ 用户体验显著提升
|
||
- ✅ 为后续功能铺路
|
||
|
||
---
|
||
|
||
## 附录
|
||
|
||
### A. Claude Code CSS 完整提取
|
||
|
||
详见压缩包中的 `webview/index.css` 文件 (已完整保留)
|
||
|
||
### B. 关键 CSS 变量映射表
|
||
|
||
| Claude Code 变量 | VSCode 变量 | 用途 |
|
||
| ------------------------------------- | ---------------------------------- | ---------- |
|
||
| `--app-primary-foreground` | `--vscode-foreground` | 主文本颜色 |
|
||
| `--app-primary-background` | `--vscode-sideBar-background` | 主背景色 |
|
||
| `--app-input-border` | `--vscode-inlineChatInput-border` | 输入框边框 |
|
||
| `--app-button-background` | `--vscode-button-background` | 按钮背景 |
|
||
| `--app-ghost-button-hover-background` | `--vscode-toolbar-hoverBackground` | 悬停背景 |
|
||
|
||
### C. 参考资源
|
||
|
||
- Claude Code 官方文档: https://docs.anthropic.com/claude-code
|
||
- VSCode Extension API: https://code.visualstudio.com/api
|
||
- React TypeScript: https://react-typescript-cheatsheet.netlify.app/
|
||
|
||
---
|
||
|
||
**文档版本**: v1.0
|
||
**最后更新**: 2025-11-18
|
||
**作者**: Claude (Sonnet 4.5)
|
||
**状态**: 待审核
|