23 Commits

Author SHA1 Message Date
ea19dfcbe6 fix: 修复 waveform_trace 工具执行失败和类型错误
- 修复 waveform_trace 工具因 stderr 输出导致的误判失败
   - 修复 messageHandler onQuestion 回调的类型签名错误
2026-03-05 17:25:29 +08:00
fa55e32153 feat: 支持 AskUserQuestion 多问题和多选功能
- 新增 QuestionItem 类型支持单个问题配置(question/options/multiSelect)
   - AskUserEvent 改为 questions 数组支持多问题
   - AnswerRequest 新增 answers 字段支持多问题答案提交
   - 前端渲染支持单选按钮(radio)和多选复选框(checkbox)
   - 答案格式:{\"0\": [\"选项1\"], \"1\": [\"选项A\", \"选项B\"]}
   - 保持向后兼容旧的单问题格式
2026-03-05 16:58:59 +08:00
f6b1f5c45a 1.0.11 2026-03-05 10:39:30 +08:00
1f9a1822c9 fix: 修复打包后图片资源无法加载的问题
- 配置 webpack copy-webpack-plugin 将 src/assets 复制到 dist/assets
- 更新所有图片引用路径从 src/assets 改为 dist/assets
- 修改 localResourceRoots 配置以允许访问 dist/assets
2026-03-05 10:38:40 +08:00
63015c6bbc fix:排除 PUBLISH.md,避免敏感信息被打包 2026-03-05 09:34:44 +08:00
24b30df992 fix: 排除 docs 目录避免中文文件名打包冲突 2026-03-05 09:24:27 +08:00
67b1003831 style:将宁德时代欢迎弹窗换成全企业的 2026-03-04 22:19:14 +08:00
00d37bdaf0 1.0.9 2026-03-04 21:08:08 +08:00
c5fcb1427e Update:README.md 2026-03-04 21:07:50 +08:00
9118ebd662 feat:企业欢迎弹窗优化 2026-03-04 18:58:18 +08:00
19cdf47bed style: 将工具折叠图标颜色从蓝色改为灰色
- 修改 toolIcons.ts 中的 SVG 填充色为 #8a8a8a
   - 清理 messageArea.ts 中冗余的 CSS 样式规则
2026-03-04 16:46:40 +08:00
95bac94479 fix:修改代码变更继续对话查找不到之前的代码变更信息的bug 2026-03-04 16:17:56 +08:00
421a8934a7 chore: 优化打包配置,排除重复的 exe 文件
- 添加 .vscodeignore 排除 tools/waveform_trace/src/dist
   - 移除 package.json 的 files 字段
   - 减小 .vsix 打包体积
2026-03-04 15:11:46 +08:00
f7f45668d3 style: 统一使用蓝色主题色
- 压缩图标改为蓝色 #007ACC
- 问题选项按钮改为蓝色背景,悬停深蓝色
- 按钮、进度条等组件统一使用蓝色主题
- 添加 CSS 强制规则确保图标在所有主题下显示蓝色
2026-03-04 14:51:36 +08:00
c8e9a5b897 fix: 修复继续对话时消息覆盖问题并添加波形追踪工具
- 修复继续对话时 AI 消息被覆盖的问题
- 用户发送新消息时重置分段消息容器
- 继续对话时复用未完成的消息容器
- 添加 waveform_trace.exe 工具到仓库
- 更新 .gitignore 规则
2026-03-04 11:43:45 +08:00
a1bfa62796 feat:Update CHANGE.md 2026-03-03 20:21:37 +08:00
64e11cbc3c 1.0.8 2026-03-03 20:19:46 +08:00
15445aa13c fix: 修复继续对话时消息覆盖问题
- 对话完成时正确重置 currentSegmentedMessage
- 继续对话时创建新的消息容器
- 删除调试日志代码
2026-03-03 20:04:41 +08:00
52834047f2 feat: 每次登录都显示试用用户欢迎弹窗
- 移除 hasWelcomed 标记,不再记录是否已显示
- 试用用户每次打开聊天面板都会看到欢迎弹窗
2026-03-03 19:19:37 +08:00
76817675f1 fix: 修复试用用户欢迎弹窗不显示的问题
- 修复 userService 中 null 值未正确赋值的问题
- 优化欢迎弹窗判断逻辑:null=长期有效,undefined=无效
- 添加测试命令 resetWelcomeModal 用于清除弹窗标记
2026-03-03 19:06:02 +08:00
2cce8f94c9 fix: 修复试用用户无过期时间时欢迎弹窗不显示的问题
- 优化欢迎弹窗判断逻辑,支持无过期时间的长期试用用户
   - 只有在有过期时间且已过期时才不显示欢迎弹窗
   - 改进日志输出,更清晰地显示判断流
2026-03-03 18:48:30 +08:00
9b5f102d9f feat: 完善企业试用用户欢迎弹窗逻辑
- 添加试用到期时间检查

- 仅在试用未过期且有到期时间时显示欢迎弹窗

- 试用过期或无到期时间则显示邀请码弹窗

- 修复窗口重载后弹窗标记丢失问题
2026-03-03 18:26:27 +08:00
68de33165e fix:修复企业试用用户仍弹出邀请码的问题 2026-03-03 18:03:43 +08:00
26 changed files with 1538 additions and 234 deletions

3
.gitignore vendored
View File

@ -4,8 +4,7 @@ node_modules
.vscode-test/ .vscode-test/
*.vsix *.vsix
# waveform_trace 打包产物exe 太大,通过 Release 发布) # waveform_trace 打包产物
tools/waveform_trace/bin/
tools/waveform_trace/src/build/ tools/waveform_trace/src/build/
tools/waveform_trace/src/dist/ tools/waveform_trace/src/dist/
tools/waveform_trace/src/*.spec tools/waveform_trace/src/*.spec

28
.vscodeignore Normal file
View File

@ -0,0 +1,28 @@
# 开发文件
.vscode/**
.vscode-test/**
src/**
.gitignore
.yarnrc
vsc-extension-quickstart.md
**/tsconfig.json
**/.eslintrc.json
**/*.map
**/*.ts
# 测试文件
out/test/**
# 依赖
node_modules/**
# 文档(避免中文文件名打包问题)
docs/**
PUBLISH.md
# 只排除 waveform_trace 的 src/dist 目录
tools/waveform_trace/src/dist/**
# Git 相关
.git/**
.github/**

View File

@ -2,6 +2,34 @@
所有重要的项目变更都将记录在此文件中。 所有重要的项目变更都将记录在此文件中。
## [1.0.9] - 2026-03-04
### 优化
- 将工具折叠图标颜色从蓝色改为灰色
- 统一使用蓝色主题色
- 优化打包配置,排除重复的 exe 文件
### 修复
- 修复代码变更继续对话查找不到之前的代码变更信息的 bug
- 修复对话展示两遍的问题
## [1.0.8] - 2026-03-03
### 新增
- 删除文件确认功能
- 文件路径标签显示
- 企业试用用户欢迎弹窗优化
### 修复
- 修复继续对话时消息覆盖问题
- 修复试用用户欢迎弹窗显示逻辑
- 修复企业试用用户仍弹出邀请码的问题
- 修复登录过期点击重新登录失败的问题
## [1.0.7] - 2026-03-02 ## [1.0.7] - 2026-03-02
### 修复 ### 修复

View File

@ -0,0 +1,261 @@
# AskUserQuestion 多选支持 - API 设计文档
## 问题描述
当前 AI 询问用户问题时存在以下问题:
1. 后端返回的选项不准确
2. 多个问题只给几个选项
3. 不支持多选方式
## 需求
实现一个问题对应多个选项,支持多选的方式。
## 数据结构设计
### 后端返回格式
后端通过 SSE 的 `ask_user` 事件返回以下格式:
```json
{
"askId": "ask_1234567890",
"questions": [
{
"question": "请确认 SPI 控制器的配置需求:工作模式?",
"options": [
"Master/8位/模式0/固定分频/需要CS",
"Master/可配置位宽/可配置模式/需要CS",
"Slave模式"
],
"multiSelect": false
},
{
"question": "数据位宽?",
"options": [
"8位 还是其他?"
],
"multiSelect": false
},
{
"question": "时钟极性和相位?",
"options": [
"CPOL=0/CPHA=0 (模式0) 还是其他模式?"
],
"multiSelect": false
},
{
"question": "时钟分频?",
"options": [
"需要可配置的分频比吗?"
],
"multiSelect": false
},
{
"question": "是否需要芯片选信号 (CS) 控制?",
"options": [
"是",
"否"
],
"multiSelect": false
}
]
}
```
### 前端数据结构
#### 1. API 类型定义 (`src/types/api.ts`)
```typescript
/** ask_user 事件数据 */
export interface AskUserEvent {
askId: string;
questions: QuestionItem[];
}
/** 单个问题项 */
export interface QuestionItem {
question: string;
options: string[];
multiSelect?: boolean; // 是否支持多选,默认 false
}
```
#### 2. MessageSegment 类型 (`src/services/dialogService.ts`)
```typescript
export interface MessageSegment {
type: "text" | "tool" | "question" | "agent" | "plan" | "progress";
// ... 其他字段
askId?: string;
questions?: QuestionItem[]; // 改为问题数组
}
```
#### 3. 用户回答格式 (`src/types/api.ts`)
```typescript
export interface AnswerRequest {
taskId: string;
askId: string;
answers: {
[questionIndex: number]: string[]; // 每个问题的答案数组(支持多选)
};
}
```
## 前端实现要点
### 1. 显示多个问题
```typescript
// 遍历 questions 数组,为每个问题生成 UI
segment.questions?.forEach((q, index) => {
// 显示问题标题
// 显示选项(单选或多选)
// 收集答案
});
```
### 2. 多选支持
```typescript
if (q.multiSelect) {
// 渲染复选框
// 允许选择多个选项
} else {
// 渲染单选按钮
// 只允许选择一个选项
}
```
### 3. 提交答案
```typescript
const answers = {
0: ["Master/8位/模式0/固定分频/需要CS"], // 第1个问题的答案
1: ["8位 还是其他?"], // 第2个问题的答案
2: ["CPOL=0/CPHA=0 (模式0) 还是其他模式?"], // 第3个问题的答案
// ...
};
vscode.postMessage({
command: 'userAnswer',
askId: 'ask_1234567890',
answers: answers
});
```
## 后端需要做的修改
### 1. 修改 AskUserQuestion 工具的返回格式
从:
```json
{
"askId": "xxx",
"question": "单个问题",
"options": ["选项1", "选项2"]
}
```
改为:
```json
{
"askId": "xxx",
"questions": [
{
"question": "问题1",
"options": ["选项1", "选项2"],
"multiSelect": false
},
{
"question": "问题2",
"options": ["选项A", "选项B", "选项C"],
"multiSelect": true
}
]
}
```
### 2. 接收答案的格式
从:
```json
{
"taskId": "xxx",
"askId": "xxx",
"selected": ["选项1"],
"customInput": "自定义输入"
}
```
改为:
```json
{
"taskId": "xxx",
"askId": "xxx",
"answers": {
"0": ["选项1"], // 第1个问题的答案
"1": ["选项A", "选项B"] // 第2个问题的答案多选
}
}
```
## 示例场景
### 场景SPI 控制器配置
**后端发送:**
```json
{
"askId": "ask_spi_config",
"questions": [
{
"question": "工作模式?",
"options": [
"Master/8位/模式0/固定分频/需要CS",
"Master/可配置位宽/可配置模式/需要CS",
"Slave模式"
],
"multiSelect": false
},
{
"question": "需要哪些功能?",
"options": [
"可配置时钟分频",
"可配置数据位宽",
"支持多个CS",
"DMA支持"
],
"multiSelect": true
}
]
}
```
**用户选择:**
- 问题1选择 "Master/8位/模式0/固定分频/需要CS"
- 问题2选择 "可配置时钟分频" 和 "可配置数据位宽"
**前端提交:**
```json
{
"taskId": "task_xxx",
"askId": "ask_spi_config",
"answers": {
"0": ["Master/8位/模式0/固定分频/需要CS"],
"1": ["可配置时钟分频", "可配置数据位宽"]
}
}
```
## 总结
这个设计方案:
1. ✅ 支持多个问题
2. ✅ 每个问题有多个选项
3. ✅ 支持单选和多选
4. ✅ 数据结构清晰,易于扩展
5. ✅ 向后兼容(可以只有一个问题)

View File

@ -0,0 +1,804 @@
# VS Code Extension API 核心知识点
## 目录
- [1. Extension 生命周期](#1-extension-生命周期) ⭐⭐⭐
- [2. 激活事件 (Activation Events)](#2-激活事件-activation-events) ⭐⭐
- [3. 命令系统 (Commands)](#3-命令系统-commands) ⭐⭐
- [4. Webview API](#4-webview-api) ⭐⭐⭐⭐⭐ **面试重点**
- [5. TreeView 和自定义视图](#5-treeview-和自定义视图) ⭐⭐
- [6. 文件系统操作](#6-文件系统操作) ⭐⭐⭐
- [7. 配置和存储](#7-配置和存储) ⭐⭐⭐⭐ **面试重点**
- [8. 消息通知](#8-消息通知) ⭐
- [9. 语言特性支持](#9-语言特性支持) ⭐
- [10. 调试和诊断](#10-调试和诊断) ⭐
---
## 1. Extension 生命周期 ⭐⭐⭐
### 1.1 核心函数 🔥必考
```typescript
// extension.ts
import * as vscode from 'vscode';
// 插件激活时调用(只调用一次)
export function activate(context: vscode.ExtensionContext) {
console.log('Extension is now active!');
// 注册命令、视图、事件监听等
// 使用 context.subscriptions 管理资源
}
// 插件停用时调用(清理资源)
export function deactivate() {
console.log('Extension is deactivated');
// 清理资源、关闭连接等
}
```
### 1.2 ExtensionContext 重要属性 🔥必考
```typescript
interface ExtensionContext {
// 插件订阅管理(自动清理)
subscriptions: { dispose(): any }[];
// 工作区存储路径
storageUri: vscode.Uri | undefined;
globalStorageUri: vscode.Uri;
// 插件路径
extensionUri: vscode.Uri;
extensionPath: string;
// 状态存储
workspaceState: Memento; // 工作区级别
globalState: Memento; // 全局级别
secrets: SecretStorage; // 敏感信息存储
// 环境变量
environmentVariableCollection: EnvironmentVariableCollection;
}
```
### 1.3 资源管理最佳实践 🔥必考
```typescript
export function activate(context: vscode.ExtensionContext) {
// ✅ 推荐:使用 context.subscriptions 自动管理
context.subscriptions.push(
vscode.commands.registerCommand('extension.command', () => {})
);
// ❌ 不推荐:手动管理容易忘记清理
const disposable = vscode.commands.registerCommand('extension.command', () => {});
// 需要在 deactivate 中手动调用 disposable.dispose()
}
```
---
## 2. 激活事件 (Activation Events) ⭐⭐
### 2.1 常用激活事件 📌重要
```json
// package.json
{
"activationEvents": [
// 启动时激活
"onStartupFinished",
// 执行命令时激活
"onCommand:extension.helloWorld",
// 打开特定语言文件时激活
"onLanguage:javascript",
"onLanguage:verilog",
// 打开特定文件类型时激活
"onFileSystem:sftp",
// 打开特定视图时激活
"onView:myCustomView",
// 调试时激活
"onDebug",
// 打开特定 URI 时激活
"onUri",
// Webview 恢复时激活
"onWebviewPanel:myWebview",
// 任务执行时激活
"onTaskType:npm"
]
}
```
### 2.2 延迟激活策略 🔥必考
```typescript
// ✅ 推荐:使用 onStartupFinished 延迟激活
"activationEvents": ["onStartupFinished"]
// ❌ 不推荐:使用 * 会拖慢启动速度
"activationEvents": ["*"]
```
---
## 3. 命令系统 (Commands)
### 3.1 注册命令
```typescript
// 注册简单命令
const disposable = vscode.commands.registerCommand(
'extension.helloWorld',
() => {
vscode.window.showInformationMessage('Hello World!');
}
);
context.subscriptions.push(disposable);
// 注册带参数的命令
vscode.commands.registerCommand(
'extension.openFile',
(filePath: string) => {
vscode.workspace.openTextDocument(filePath).then(doc => {
vscode.window.showTextDocument(doc);
});
}
);
```
### 3.2 执行命令
```typescript
// 执行内置命令
await vscode.commands.executeCommand('workbench.action.files.save');
// 执行自定义命令
await vscode.commands.executeCommand('extension.openFile', '/path/to/file');
// 获取所有可用命令
const commands = await vscode.commands.getCommands();
```
### 3.3 常用内置命令
```typescript
// 文件操作
'workbench.action.files.save'
'workbench.action.files.saveAll'
'workbench.action.closeActiveEditor'
// 编辑器操作
'editor.action.formatDocument'
'editor.action.commentLine'
'editor.action.selectAll'
// 窗口操作
'workbench.action.toggleSidebarVisibility'
'workbench.action.terminal.new'
'workbench.action.quickOpen'
// Git 操作
'git.commit'
'git.push'
'git.pull'
```
---
## 4. Webview API ⭐⭐⭐⭐⭐ **面试重点**
### 4.1 创建 Webview Panel 🔥必考
```typescript
const panel = vscode.window.createWebviewPanel(
'myWebview', // viewType唯一标识
'My Webview', // 标题
vscode.ViewColumn.One, // 显示位置
{
enableScripts: true, // 启用 JavaScript
retainContextWhenHidden: true, // 隐藏时保留状态
localResourceRoots: [ // 允许访问的本地资源路径
vscode.Uri.joinPath(context.extensionUri, 'media')
]
}
);
```
### 4.2 设置 Webview 内容
```typescript
panel.webview.html = getWebviewContent();
function getWebviewContent() {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Webview</title>
</head>
<body>
<h1>Hello from Webview!</h1>
<button onclick="sendMessage()">Send Message</button>
<script>
const vscode = acquireVsCodeApi();
function sendMessage() {
vscode.postMessage({
command: 'alert',
text: 'Hello from Webview!'
});
}
// 接收来自 Extension 的消息
window.addEventListener('message', event => {
const message = event.data;
console.log('Received:', message);
});
</script>
</body>
</html>`;
}
```
### 4.3 Webview 消息通信 🔥必考(项目核心)
```typescript
// Extension → Webview
panel.webview.postMessage({
command: 'update',
data: 'some data'
});
// Webview → Extension
panel.webview.onDidReceiveMessage(
message => {
switch (message.command) {
case 'alert':
vscode.window.showInformationMessage(message.text);
break;
case 'getData':
// 处理数据请求
panel.webview.postMessage({
command: 'dataResponse',
data: fetchData()
});
break;
}
},
undefined,
context.subscriptions
);
```
### 4.4 Webview 生命周期管理 📌重要
```typescript
// 监听 Webview 关闭事件
panel.onDidDispose(
() => {
// 清理资源
console.log('Webview disposed');
},
null,
context.subscriptions
);
// 监听 Webview 可见性变化
panel.onDidChangeViewState(
e => {
if (e.webviewPanel.visible) {
console.log('Webview is now visible');
}
},
null,
context.subscriptions
);
```
### 4.5 加载本地资源 📌重要
```typescript
// 获取本地资源 URI
const scriptUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, 'media', 'script.js')
);
const styleUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, 'media', 'style.css')
);
// 在 HTML 中使用
const html = `
<link href="${styleUri}" rel="stylesheet">
<script src="${scriptUri}"></script>
`;
```
### 4.6 Webview 状态持久化 📌重要
```typescript
// Webview 中保存状态
const vscode = acquireVsCodeApi();
const state = vscode.getState() || { count: 0 };
// 更新状态
state.count++;
vscode.setState(state);
// Extension 中序列化状态
panel.webview.options = {
enableScripts: true,
retainContextWhenHidden: true
};
// 恢复 Webview
vscode.window.registerWebviewPanelSerializer('myWebview', {
async deserializeWebviewPanel(webviewPanel, state) {
webviewPanel.webview.html = getWebviewContent();
// 恢复状态
webviewPanel.webview.postMessage({ command: 'restore', state });
}
});
```
---
## 5. TreeView 和自定义视图
### 5.1 创建 TreeView Provider
```typescript
class MyTreeDataProvider implements vscode.TreeDataProvider<TreeItem> {
private _onDidChangeTreeData = new vscode.EventEmitter<TreeItem | undefined>();
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
refresh(): void {
this._onDidChangeTreeData.fire(undefined);
}
getTreeItem(element: TreeItem): vscode.TreeItem {
return element;
}
getChildren(element?: TreeItem): Thenable<TreeItem[]> {
if (!element) {
// 返回根节点
return Promise.resolve([
new TreeItem('Item 1', vscode.TreeItemCollapsibleState.None),
new TreeItem('Item 2', vscode.TreeItemCollapsibleState.Collapsed)
]);
}
// 返回子节点
return Promise.resolve([]);
}
}
class TreeItem extends vscode.TreeItem {
constructor(
public readonly label: string,
public readonly collapsibleState: vscode.TreeItemCollapsibleState
) {
super(label, collapsibleState);
this.tooltip = `Tooltip for ${label}`;
this.command = {
command: 'extension.itemClicked',
title: 'Click Item',
arguments: [this]
};
}
}
```
### 5.2 注册 TreeView
```typescript
const treeDataProvider = new MyTreeDataProvider();
const treeView = vscode.window.createTreeView('myTreeView', {
treeDataProvider,
showCollapseAll: true
});
context.subscriptions.push(treeView);
// 刷新视图
treeDataProvider.refresh();
```
### 5.3 WebviewView Provider侧边栏 Webview
```typescript
class MyWebviewViewProvider implements vscode.WebviewViewProvider {
resolveWebviewView(
webviewView: vscode.WebviewView,
context: vscode.WebviewViewResolveContext,
token: vscode.CancellationToken
) {
webviewView.webview.options = {
enableScripts: true
};
webviewView.webview.html = getWebviewContent();
webviewView.webview.onDidReceiveMessage(message => {
// 处理消息
});
}
}
// 注册
vscode.window.registerWebviewViewProvider(
'myWebviewView',
new MyWebviewViewProvider()
);
```
---
## 6. 文件系统操作 ⭐⭐⭐
### 6.1 读取文件 📌重要
```typescript
// 读取文本文件
const uri = vscode.Uri.file('/path/to/file.txt');
const content = await vscode.workspace.fs.readFile(uri);
const text = Buffer.from(content).toString('utf8');
// 使用 TextDocument API
const document = await vscode.workspace.openTextDocument(uri);
const text = document.getText();
```
### 6.2 写入文件
```typescript
// 写入文件
const uri = vscode.Uri.file('/path/to/file.txt');
const content = Buffer.from('Hello World', 'utf8');
await vscode.workspace.fs.writeFile(uri, content);
// 使用 WorkspaceEdit
const edit = new vscode.WorkspaceEdit();
edit.createFile(uri, { overwrite: true });
edit.insert(uri, new vscode.Position(0, 0), 'Hello World');
await vscode.workspace.applyEdit(edit);
```
### 6.3 文件监听
```typescript
// 监听文件变化
const watcher = vscode.workspace.createFileSystemWatcher('**/*.js');
watcher.onDidCreate(uri => {
console.log('File created:', uri.fsPath);
});
watcher.onDidChange(uri => {
console.log('File changed:', uri.fsPath);
});
watcher.onDidDelete(uri => {
console.log('File deleted:', uri.fsPath);
});
context.subscriptions.push(watcher);
```
### 6.4 工作区操作
```typescript
// 获取工作区文件夹
const workspaceFolders = vscode.workspace.workspaceFolders;
if (workspaceFolders) {
const rootPath = workspaceFolders[0].uri.fsPath;
}
// 查找文件
const files = await vscode.workspace.findFiles(
'**/*.ts', // include pattern
'**/node_modules/**' // exclude pattern
);
// 打开文件
const document = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(document);
```
---
## 7. 配置和存储 ⭐⭐⭐⭐ **面试重点**
### 7.1 读取配置 📌重要
```typescript
// 读取配置
const config = vscode.workspace.getConfiguration('myExtension');
const value = config.get<string>('settingName', 'defaultValue');
// 监听配置变化
vscode.workspace.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('myExtension.settingName')) {
console.log('Configuration changed');
}
});
```
### 7.2 更新配置
```typescript
const config = vscode.workspace.getConfiguration('myExtension');
// 更新用户配置(全局)
await config.update('settingName', 'newValue', vscode.ConfigurationTarget.Global);
// 更新工作区配置
await config.update('settingName', 'newValue', vscode.ConfigurationTarget.Workspace);
```
### 7.3 状态存储 🔥必考
```typescript
// 工作区状态(仅当前工作区)
await context.workspaceState.update('key', 'value');
const value = context.workspaceState.get('key');
// 全局状态(跨工作区)
await context.globalState.update('key', 'value');
const value = context.globalState.get('key');
// 存储对象
await context.globalState.update('userData', { name: 'John', age: 30 });
```
### 7.4 敏感信息存储 🔥必考Token 管理)
```typescript
// 存储密码、Token 等敏感信息
await context.secrets.store('apiToken', 'secret-token-value');
// 读取
const token = await context.secrets.get('apiToken');
// 删除
await context.secrets.delete('apiToken');
// 监听变化
context.secrets.onDidChange(e => {
console.log('Secret changed:', e.key);
});
```
---
## 8. 消息通知
### 8.1 信息提示
```typescript
// 普通信息
vscode.window.showInformationMessage('Operation completed!');
// 警告
vscode.window.showWarningMessage('This action may cause issues');
// 错误
vscode.window.showErrorMessage('Operation failed!');
```
### 8.2 带按钮的提示
```typescript
const result = await vscode.window.showInformationMessage(
'Do you want to continue?',
'Yes',
'No',
'Cancel'
);
if (result === 'Yes') {
// 用户点击了 Yes
}
```
### 8.3 输入框
```typescript
// 简单输入
const input = await vscode.window.showInputBox({
prompt: 'Enter your name',
placeHolder: 'John Doe',
validateInput: (value) => {
return value.length < 3 ? 'Name too short' : null;
}
});
// 快速选择
const selected = await vscode.window.showQuickPick(
['Option 1', 'Option 2', 'Option 3'],
{
placeHolder: 'Select an option',
canPickMany: false
}
);
```
### 8.4 进度提示
```typescript
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: 'Processing...',
cancellable: true
},
async (progress, token) => {
token.onCancellationRequested(() => {
console.log('User canceled');
});
progress.report({ increment: 0, message: 'Starting...' });
await doWork1();
progress.report({ increment: 50, message: 'Half done...' });
await doWork2();
progress.report({ increment: 100, message: 'Complete!' });
}
);
```
---
## 9. 语言特性支持
### 9.1 代码补全
```typescript
const provider = vscode.languages.registerCompletionItemProvider(
'javascript',
{
provideCompletionItems(document, position) {
const item = new vscode.CompletionItem('myFunction');
item.kind = vscode.CompletionItemKind.Function;
item.detail = 'My custom function';
item.documentation = 'This is a custom function';
item.insertText = new vscode.SnippetString('myFunction($1)$0');
return [item];
}
},
'.' // 触发字符
);
context.subscriptions.push(provider);
```
### 9.2 悬停提示
```typescript
const provider = vscode.languages.registerHoverProvider('javascript', {
provideHover(document, position) {
const range = document.getWordRangeAtPosition(position);
const word = document.getText(range);
return new vscode.Hover([
`**${word}**`,
'This is a hover tooltip'
]);
}
});
```
### 9.3 诊断(错误提示)
```typescript
const diagnosticCollection = vscode.languages.createDiagnosticCollection('myExtension');
context.subscriptions.push(diagnosticCollection);
function updateDiagnostics(document: vscode.TextDocument) {
const diagnostics: vscode.Diagnostic[] = [];
const text = document.getText();
const regex = /TODO/g;
let match;
while ((match = regex.exec(text))) {
const range = new vscode.Range(
document.positionAt(match.index),
document.positionAt(match.index + match[0].length)
);
const diagnostic = new vscode.Diagnostic(
range,
'TODO found',
vscode.DiagnosticSeverity.Warning
);
diagnostics.push(diagnostic);
}
diagnosticCollection.set(document.uri, diagnostics);
}
```
---
## 10. 调试和诊断
### 10.1 输出通道
```typescript
const outputChannel = vscode.window.createOutputChannel('My Extension');
context.subscriptions.push(outputChannel);
outputChannel.appendLine('Extension activated');
outputChannel.show(); // 显示输出面板
```
### 10.2 日志记录
```typescript
// 使用 LogOutputChannel带时间戳
const logger = vscode.window.createOutputChannel('My Extension', { log: true });
logger.trace('Trace message');
logger.debug('Debug message');
logger.info('Info message');
logger.warn('Warning message');
logger.error('Error message');
```
### 10.3 错误处理
```typescript
try {
await riskyOperation();
} catch (error) {
if (error instanceof Error) {
vscode.window.showErrorMessage(`Error: ${error.message}`);
logger.error(error.stack || error.message);
}
}
```
---
## 最佳实践总结
### ✅ 推荐做法
1. **资源管理**:所有 disposable 对象都放入 `context.subscriptions`
2. **延迟激活**:使用 `onStartupFinished` 而不是 `*`
3. **异步操作**:使用 `async/await` 处理异步操作
4. **错误处理**:捕获异常并给用户友好提示
5. **类型安全**:充分利用 TypeScript 类型系统
6. **状态持久化**:使用 `globalState`/`workspaceState` 保存状态
7. **敏感信息**:使用 `secrets` API 存储 Token、密码等
### ❌ 避免做法
1. 不要在 `activate` 中执行耗时操作
2. 不要忘记清理资源监听器、Webview 等)
3. 不要在 Webview 中直接访问文件系统
4. 不要在配置中存储敏感信息
5. 不要阻塞主线程(使用 Worker 或异步操作)
---
## 参考资源
- [VS Code Extension API 官方文档](https://code.visualstudio.com/api)
- [Extension Samples](https://github.com/microsoft/vscode-extension-samples)
- [Extension Guidelines](https://code.visualstudio.com/api/references/extension-guidelines)

View File

@ -2,7 +2,7 @@
"name": "iccoder", "name": "iccoder",
"displayName": "IC Coder: Agentic Verilog Platform", "displayName": "IC Coder: Agentic Verilog Platform",
"description": "Agentic Verilog Coding Platform for Real-World FPGAs", "description": "Agentic Verilog Coding Platform for Real-World FPGAs",
"version": "1.0.7", "version": "1.0.11",
"publisher": "ICCoderAgenticVerilogPlatform", "publisher": "ICCoderAgenticVerilogPlatform",
"engines": { "engines": {
"vscode": "^1.80.0" "vscode": "^1.80.0"
@ -135,6 +135,7 @@
"@vscode/test-cli": "^0.0.12", "@vscode/test-cli": "^0.0.12",
"@vscode/test-electron": "^2.5.2", "@vscode/test-electron": "^2.5.2",
"@vscode/vsce": "^3.7.1", "@vscode/vsce": "^3.7.1",
"copy-webpack-plugin": "^14.0.0",
"eslint": "^9.39.1", "eslint": "^9.39.1",
"ts-loader": "^9.5.4", "ts-loader": "^9.5.4",
"typescript": "^5.9.3", "typescript": "^5.9.3",
@ -142,14 +143,6 @@
"webpack": "^5.103.0", "webpack": "^5.103.0",
"webpack-cli": "^6.0.1" "webpack-cli": "^6.0.1"
}, },
"files": [
"dist",
"media",
"tools",
"src/assets",
"LICENSE",
"CHANGELOG.md"
],
"dependencies": { "dependencies": {
"@wavedrom/doppler": "^1.14.0", "@wavedrom/doppler": "^1.14.0",
"eventsource-parser": "^3.0.6", "eventsource-parser": "^3.0.6",

24
pnpm-lock.yaml generated
View File

@ -57,6 +57,9 @@ importers:
'@vscode/vsce': '@vscode/vsce':
specifier: ^3.7.1 specifier: ^3.7.1
version: 3.7.1 version: 3.7.1
copy-webpack-plugin:
specifier: ^14.0.0
version: 14.0.0(webpack@5.103.0)
eslint: eslint:
specifier: ^9.39.1 specifier: ^9.39.1
version: 9.39.1 version: 9.39.1
@ -823,6 +826,12 @@ packages:
convert-source-map@2.0.0: convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
copy-webpack-plugin@14.0.0:
resolution: {integrity: sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==}
engines: {node: '>= 20.9.0'}
peerDependencies:
webpack: ^5.1.0
core-util-is@1.0.3: core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
@ -1928,6 +1937,10 @@ packages:
serialize-javascript@6.0.2: serialize-javascript@6.0.2:
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
serialize-javascript@7.0.4:
resolution: {integrity: sha512-DuGdB+Po43Q5Jxwpzt1lhyFSYKryqoNjQSA9M92tyw0lyHIOur+XCalOUe0KTJpyqzT8+fQ5A0Jf7vCx/NKmIg==}
engines: {node: '>=20.0.0'}
setimmediate@1.0.5: setimmediate@1.0.5:
resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
@ -3297,6 +3310,15 @@ snapshots:
convert-source-map@2.0.0: {} convert-source-map@2.0.0: {}
copy-webpack-plugin@14.0.0(webpack@5.103.0):
dependencies:
glob-parent: 6.0.2
normalize-path: 3.0.0
schema-utils: 4.3.3
serialize-javascript: 7.0.4
tinyglobby: 0.2.15
webpack: 5.103.0(webpack-cli@6.0.1)
core-util-is@1.0.3: {} core-util-is@1.0.3: {}
cross-spawn@7.0.6: cross-spawn@7.0.6:
@ -4431,6 +4453,8 @@ snapshots:
dependencies: dependencies:
randombytes: 2.1.0 randombytes: 2.1.0
serialize-javascript@7.0.4: {}
setimmediate@1.0.5: {} setimmediate@1.0.5: {}
shallow-clone@3.0.1: shallow-clone@3.0.1:

View File

@ -53,7 +53,7 @@ function getTierIconUri(
const iconUri = webview.asWebviewUri( const iconUri = webview.asWebviewUri(
vscode.Uri.joinPath( vscode.Uri.joinPath(
context.extensionUri, context.extensionUri,
"src", "dist",
"assets", "assets",
"titleIcon", "titleIcon",
iconFile, iconFile,
@ -138,7 +138,7 @@ export async function showICHelperPanel(
retainContextWhenHidden: true, retainContextWhenHidden: true,
localResourceRoots: [ localResourceRoots: [
vscode.Uri.joinPath(context.extensionUri, "media"), vscode.Uri.joinPath(context.extensionUri, "media"),
vscode.Uri.joinPath(context.extensionUri, "src", "assets"), vscode.Uri.joinPath(context.extensionUri, "dist", "assets"),
], ],
}, },
); );
@ -166,7 +166,7 @@ export async function showICHelperPanel(
const autoIconUri = panel.webview.asWebviewUri( const autoIconUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath( vscode.Uri.joinPath(
context.extensionUri, context.extensionUri,
"src", "dist",
"assets", "assets",
"model", "model",
"Auto.png", "Auto.png",
@ -175,7 +175,7 @@ export async function showICHelperPanel(
const liteIconUri = panel.webview.asWebviewUri( const liteIconUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath( vscode.Uri.joinPath(
context.extensionUri, context.extensionUri,
"src", "dist",
"assets", "assets",
"model", "model",
"lite.png", "lite.png",
@ -184,7 +184,7 @@ export async function showICHelperPanel(
const syIconUri = panel.webview.asWebviewUri( const syIconUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath( vscode.Uri.joinPath(
context.extensionUri, context.extensionUri,
"src", "dist",
"assets", "assets",
"model", "model",
"Sy.png", "Sy.png",
@ -193,7 +193,7 @@ export async function showICHelperPanel(
const maxIconUri = panel.webview.asWebviewUri( const maxIconUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath( vscode.Uri.joinPath(
context.extensionUri, context.extensionUri,
"src", "dist",
"assets", "assets",
"model", "model",
"Max.png", "Max.png",
@ -204,7 +204,7 @@ export async function showICHelperPanel(
const qrCodeUri = panel.webview.asWebviewUri( const qrCodeUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath( vscode.Uri.joinPath(
context.extensionUri, context.extensionUri,
"src", "dist",
"assets", "assets",
"QRCode", "QRCode",
"wx.png", "wx.png",
@ -436,6 +436,7 @@ export async function showICHelperPanel(
message.askId, message.askId,
message.selected, message.selected,
message.customInput, message.customInput,
message.answers
); );
break; break;
// 新增:中止对话 // 新增:中止对话
@ -539,25 +540,39 @@ export async function showICHelperPanel(
// 检查是否需要显示欢迎弹窗 // 检查是否需要显示欢迎弹窗
{ {
console.log("[ICHelperPanel] 收到 checkWelcomeModal 消息"); console.log("[ICHelperPanel] 收到 checkWelcomeModal 消息");
const showWelcome = context.globalState.get("showWelcomeModal"); const userInfo = getCachedUserInfo();
console.log(
"[ICHelperPanel] showWelcomeModal 标记值:",
showWelcome,
);
if (showWelcome) { console.log("[ICHelperPanel] 用户信息:", userInfo);
// 清除标记并显示欢迎弹窗 console.log("[ICHelperPanel] isPluginTrial:", userInfo?.isPluginTrial);
await context.globalState.update("showWelcomeModal", undefined); console.log("[ICHelperPanel] pluginTrialExpiresAt:", userInfo?.pluginTrialExpiresAt);
console.log(
"[ICHelperPanel] ✅ 发送 showWelcomeModal 命令到前端", if (userInfo?.isPluginTrial === true) {
); // undefined 表示无效,不显示
if (userInfo.pluginTrialExpiresAt === undefined) {
console.log("[ICHelperPanel] pluginTrialExpiresAt 未设置,不显示欢迎弹窗");
break;
}
// null 表示长期有效,显示弹窗
// 有值则检查是否过期
if (userInfo.pluginTrialExpiresAt !== null) {
const now = Date.now();
const isExpired = now >= userInfo.pluginTrialExpiresAt;
console.log("[ICHelperPanel] 是否过期:", isExpired);
if (isExpired) {
console.log("[ICHelperPanel] 试用已过期,不显示欢迎弹窗");
break;
}
}
// 未过期或长期有效(null),显示欢迎弹窗
console.log("[ICHelperPanel] ✅ 发送 showWelcomeModal 命令到前端");
panel.webview.postMessage({ panel.webview.postMessage({
command: "showWelcomeModal", command: "showWelcomeModal",
}); });
} else { } else {
console.log( console.log("[ICHelperPanel] 非试用用户");
"[ICHelperPanel] showWelcomeModal 标记为 false不显示弹窗",
);
} }
} }
break; break;

View File

@ -18,6 +18,12 @@ class ChangeTrackerService {
* 开始新的变更会话 * 开始新的变更会话
*/ */
startSession(sessionId: string): void { startSession(sessionId: string): void {
// 如果已有 session无论状态重用并重置为 active
if (this.currentSession) {
this.currentSession.status = 'active';
return;
}
this.currentSession = { this.currentSession = {
sessionId, sessionId,
startTime: Date.now(), startTime: Date.now(),
@ -71,7 +77,6 @@ class ChangeTrackerService {
this.notifyListeners(); this.notifyListeners();
return session; return session;
} }
this.currentSession = null;
return null; return null;
} }

View File

@ -43,8 +43,7 @@ export interface MessageSegment {
toolResult?: string; toolResult?: string;
toolDescription?: string; toolDescription?: string;
askId?: string; askId?: string;
question?: string; questions?: import("../types/api").QuestionItem[];
options?: string[];
// 智能体相关字段 // 智能体相关字段
agentId?: string; agentId?: string;
agentName?: string; agentName?: string;
@ -97,7 +96,7 @@ export interface DialogCallbacks {
summary: string summary: string
) => void; ) => void;
/** 显示问题ask_user */ /** 显示问题ask_user */
onQuestion?: (askId: string, question: string, options: string[]) => void; onQuestion?: (askId: string, questions: import("../types/api").QuestionItem[]) => void;
/** 实时更新段落(流式过程中) */ /** 实时更新段落(流式过程中) */
onSegmentUpdate?: (segments: MessageSegment[]) => void; onSegmentUpdate?: (segments: MessageSegment[]) => void;
/** 对话完成,返回所有段落 */ /** 对话完成,返回所有段落 */
@ -647,8 +646,11 @@ export class DialogSession {
this.segments.push({ this.segments.push({
type: "question", type: "question",
askId: askId, askId: askId,
question: question, questions: [{
options: ["确认执行", "取消"], question: question,
options: ["确认执行", "取消"],
multiSelect: false
}],
}); });
// 实时发送段落更新 // 实时发送段落更新
@ -666,8 +668,11 @@ export class DialogSession {
await userInteractionManager.handleAskUser( await userInteractionManager.handleAskUser(
{ {
askId: askId, askId: askId,
question: question, questions: [{
options: ["确认执行", "取消"], question: question,
options: ["确认执行", "取消"],
multiSelect: false
}]
} as AskUserEvent, } as AskUserEvent,
this.taskId this.taskId
); );
@ -714,8 +719,11 @@ export class DialogSession {
// 注册问题到前端(类似 askUser以便用户回答时能找到 // 注册问题到前端(类似 askUser以便用户回答时能找到
const planEvent = { const planEvent = {
askId: askId, askId: askId,
question: `请确认执行计划:${data.title}`, questions: [{
options: ["确认执行", "修改计划", "取消"], question: `请确认执行计划:${data.title}`,
options: ["确认执行", "修改计划", "取消"],
multiSelect: false
}]
}; };
try { try {
await userInteractionManager.handleAskUser( await userInteractionManager.handleAskUser(
@ -856,13 +864,12 @@ export class DialogSession {
this.segments.push({ this.segments.push({
type: "question", type: "question",
askId: data.askId, askId: data.askId,
question: data.question, questions: data.questions,
options: data.options,
}); });
// 实时发送段落更新(包含问题) // 实时发送段落更新(包含问题)
callbacks.onSegmentUpdate?.(this.segments); callbacks.onSegmentUpdate?.(this.segments);
// 同时调用 onQuestion 用于更新状态栏等 // 同时调用 onQuestion 用于更新状态栏等
callbacks.onQuestion?.(data.askId, data.question, data.options); callbacks.onQuestion?.(data.askId, data.questions);
try { try {
await userInteractionManager.handleAskUser(data, this.taskId); await userInteractionManager.handleAskUser(data, this.taskId);
} catch (error) { } catch (error) {
@ -1110,7 +1117,8 @@ export class DialogSession {
async submitAnswer( async submitAnswer(
askId: string, askId: string,
selected?: string[], selected?: string[],
customInput?: string customInput?: string,
answers?: { [questionIndex: string]: string[] }
): Promise<void> { ): Promise<void> {
// 直接调用 receiveAnswer传递 taskId 作为 fallbackTaskId // 直接调用 receiveAnswer传递 taskId 作为 fallbackTaskId
// 如果 pendingQuestions 中有问题,走正常流程 // 如果 pendingQuestions 中有问题,走正常流程
@ -1119,6 +1127,7 @@ export class DialogSession {
askId, askId,
selected, selected,
customInput, customInput,
answers,
this.taskId this.taskId
); );
} }

View File

@ -4,7 +4,7 @@
*/ */
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { submitAnswer, submitToolConfirm } from './apiClient'; import { submitAnswer, submitToolConfirm } from './apiClient';
import type { AskUserEvent, AnswerRequest } from '../types/api'; import type { AskUserEvent, AnswerRequest, QuestionItem } from '../types/api';
/** /**
* 待处理的用户问题 * 待处理的用户问题
@ -12,8 +12,7 @@ import type { AskUserEvent, AnswerRequest } from '../types/api';
interface PendingQuestion { interface PendingQuestion {
askId: string; askId: string;
taskId: string; taskId: string;
question: string; questions: QuestionItem[];
options: string[];
resolve: (answer: string) => void; resolve: (answer: string) => void;
reject: (error: Error) => void; reject: (error: Error) => void;
} }
@ -45,9 +44,9 @@ export class UserInteractionManager {
* @param taskId 当前任务ID * @param taskId 当前任务ID
*/ */
async handleAskUser(event: AskUserEvent, taskId: string): Promise<void> { async handleAskUser(event: AskUserEvent, taskId: string): Promise<void> {
const { askId, question, options } = event; const { askId, questions } = event;
console.log(`[UserInteraction] 收到问题: askId=${askId}, question=${question}`); console.log(`[UserInteraction] 收到问题: askId=${askId}, count=${questions.length}`);
// 注意:问题显示已经通过 dialogService 的 onSegmentUpdate 统一处理 // 注意:问题显示已经通过 dialogService 的 onSegmentUpdate 统一处理
// 这里不再单独发送 showQuestion 命令,避免重复显示 // 这里不再单独发送 showQuestion 命令,避免重复显示
@ -57,8 +56,7 @@ export class UserInteractionManager {
this.pendingQuestions.set(askId, { this.pendingQuestions.set(askId, {
askId, askId,
taskId, taskId,
question, questions,
options,
resolve: (answer: string) => { resolve: (answer: string) => {
this.submitUserAnswer(askId, taskId, answer) this.submitUserAnswer(askId, taskId, answer)
.then(() => resolve()) .then(() => resolve())
@ -80,24 +78,38 @@ export class UserInteractionManager {
/** /**
* 处理用户提交的回答(从 WebView 调用) * 处理用户提交的回答(从 WebView 调用)
* @param askId 问题ID * @param askId 问题ID
* @param selected 选中的选项 * @param selected 选中的选项(旧格式)
* @param customInput 自定义输入 * @param customInput 自定义输入(旧格式)
* @param answers 新格式:按问题索引的答案
* @param fallbackTaskId 当问题不存在时使用的 taskId用于直接发送到后端 * @param fallbackTaskId 当问题不存在时使用的 taskId用于直接发送到后端
*/ */
async receiveAnswer( async receiveAnswer(
askId: string, askId: string,
selected?: string[], selected?: string[],
customInput?: string, customInput?: string,
answers?: { [questionIndex: string]: string[] },
fallbackTaskId?: string fallbackTaskId?: string
): Promise<void> { ): Promise<void> {
const pending = this.pendingQuestions.get(askId); const pending = this.pendingQuestions.get(askId);
const answer = customInput || selected?.join(', ') || '';
// 构建答案字符串
let answer = '';
if (answers && Object.keys(answers).length > 0) {
// 新格式:多问题答案
answer = Object.entries(answers)
.sort(([a], [b]) => parseInt(a) - parseInt(b))
.map(([_, vals]) => vals.join('; '))
.join(' | ');
} else {
// 旧格式:单问题答案
answer = customInput || selected?.join(', ') || '';
}
if (!pending) { if (!pending) {
// 问题不存在(可能是页面刷新或会话切换后),尝试直接发送到后端 // 问题不存在(可能是页面刷新或会话切换后),尝试直接发送到后端
if (fallbackTaskId) { if (fallbackTaskId) {
console.log(`[UserInteraction] 问题不在 pendingQuestions 中,直接发送到后端: askId=${askId}, taskId=${fallbackTaskId}`); console.log(`[UserInteraction] 问题不在 pendingQuestions 中,直接发送到后端: askId=${askId}, taskId=${fallbackTaskId}`);
await this.submitUserAnswer(askId, fallbackTaskId, answer); await this.submitUserAnswer(askId, fallbackTaskId, answer, answers);
} else { } else {
console.warn(`[UserInteraction] 问题不存在且无 fallbackTaskId: askId=${askId}`); console.warn(`[UserInteraction] 问题不存在且无 fallbackTaskId: askId=${askId}`);
} }
@ -119,7 +131,8 @@ export class UserInteractionManager {
private async submitUserAnswer( private async submitUserAnswer(
askId: string, askId: string,
taskId: string, taskId: string,
answer: string answer: string,
answers?: { [questionIndex: string]: string[] }
): Promise<void> { ): Promise<void> {
// 检查是否是工具确认类型的问题 // 检查是否是工具确认类型的问题
if (askId.startsWith('tool_confirm_')) { if (askId.startsWith('tool_confirm_')) {
@ -148,7 +161,8 @@ export class UserInteractionManager {
const request: AnswerRequest = { const request: AnswerRequest = {
askId, askId,
taskId, taskId,
customInput: answer answers: answers,
customInput: answers ? undefined : answer
}; };
try { try {

View File

@ -162,10 +162,14 @@ export async function getUserInfo(token: string): Promise<UserInfo | null> {
console.log('[UserService] 从 getInfo 接口获取到 isPluginTrial: true'); console.log('[UserService] 从 getInfo 接口获取到 isPluginTrial: true');
} }
// 获取试用到期时间 // 获取试用到期时间null 表示长期有效)
if (response.enterpriseTrialExpires) { if (response.enterpriseTrialExpires !== undefined) {
userInfo.pluginTrialExpiresAt = response.enterpriseTrialExpires; userInfo.pluginTrialExpiresAt = response.enterpriseTrialExpires;
console.log('[UserService] 试用到期时间:', new Date(response.enterpriseTrialExpires).toLocaleString()); if (response.enterpriseTrialExpires === null) {
console.log('[UserService] 试用长期有效');
} else {
console.log('[UserService] 试用到期时间:', new Date(response.enterpriseTrialExpires).toLocaleString());
}
} }
return userInfo; return userInfo;
@ -335,28 +339,38 @@ export async function onTokenReceived(token: string): Promise<UserInfo | null> {
console.log('[UserService] 检查用户类型isPluginTrial:', userInfo.isPluginTrial); console.log('[UserService] 检查用户类型isPluginTrial:', userInfo.isPluginTrial);
console.log('[UserService] extensionContext 是否存在:', !!extensionContext); console.log('[UserService] extensionContext 是否存在:', !!extensionContext);
if (userInfo.isPluginTrial === true) { if (userInfo.isPluginTrial === true && userInfo.pluginTrialExpiresAt !== null && userInfo.pluginTrialExpiresAt !== undefined) {
// 插件试用用户:标记需要显示欢迎弹窗 // 检查是否过期
const hasWelcomed = extensionContext?.globalState.get('pluginTrialWelcomed'); const now = Date.now();
console.log('[UserService] 是否已显示过欢迎弹窗:', hasWelcomed); const isExpired = now >= userInfo.pluginTrialExpiresAt;
console.log('[UserService] 试用到期时间:', new Date(userInfo.pluginTrialExpiresAt).toLocaleString());
console.log('[UserService] 当前时间:', new Date(now).toLocaleString());
console.log('[UserService] 是否过期:', isExpired);
if (!hasWelcomed && extensionContext) { if (isExpired) {
// 设置标记,让聊天面板显示欢迎弹窗 // 已过期:显示邀请码弹窗
await extensionContext.globalState.update('showWelcomeModal', true); console.log('[UserService] 试用已过期,将显示邀请码弹窗');
await extensionContext.globalState.update('pluginTrialWelcomed', true);
console.log('[UserService] ✅ 已设置欢迎弹窗标记 showWelcomeModal=true');
// 验证标记是否设置成功
const checkMark = extensionContext.globalState.get('showWelcomeModal');
console.log('[UserService] 验证标记:', checkMark);
} else if (!extensionContext) {
console.error('[UserService] ❌ extensionContext 为 null无法设置标记');
} else { } else {
console.log('[UserService] 已经显示欢迎弹窗,跳过'); // 未过期:显示欢迎弹窗
const hasWelcomed = extensionContext?.globalState.get('pluginTrialWelcomed');
console.log('[UserService] 是否已显示过欢迎弹窗:', hasWelcomed);
if (!hasWelcomed && extensionContext) {
await extensionContext.globalState.update('showWelcomeModal', true);
await extensionContext.globalState.update('pluginTrialWelcomed', true);
console.log('[UserService] ✅ 已设置欢迎弹窗标记 showWelcomeModal=true');
const checkMark = extensionContext.globalState.get('showWelcomeModal');
console.log('[UserService] 验证标记:', checkMark);
} else if (!extensionContext) {
console.error('[UserService] ❌ extensionContext 为 null无法设置标记');
} else {
console.log('[UserService] 已经显示过欢迎弹窗,跳过');
}
} }
} else { } else {
// 正式用户:显示邀请码弹窗(现有逻辑) // isPluginTrial=false 或 enterpriseTrialExpires 为 null:显示邀请码弹窗
console.log('[UserService] 正式用户登录,将在面板中检查邀请码'); console.log('[UserService] 非试用用户或无过期时间,将显示邀请码弹窗');
} }
return userInfo; return userInfo;

View File

@ -194,11 +194,17 @@ export interface PlanSummaryUpdateEvent {
timestamp: number; timestamp: number;
} }
/** 单个问题项 */
export interface QuestionItem {
question: string;
options: string[];
multiSelect?: boolean;
}
/** ask_user 事件数据 */ /** ask_user 事件数据 */
export interface AskUserEvent { export interface AskUserEvent {
askId: string; askId: string;
question: string; questions: QuestionItem[];
options: string[];
} }
/** complete 事件数据 */ /** complete 事件数据 */
@ -351,10 +357,12 @@ export interface AnswerRequest {
askId: string; askId: string;
/** 任务ID */ /** 任务ID */
taskId: string; taskId: string;
/** 选中的选项列表 */ /** 选中的选项列表(旧格式,兼容) */
selected?: string[]; selected?: string[];
/** 自定义输入内容 */ /** 自定义输入内容(旧格式,兼容) */
customInput?: string; customInput?: string;
/** 新格式:按问题索引的答案 */
answers?: { [questionIndex: string]: string[] };
} }
/** 用户回答响应 */ /** 用户回答响应 */

View File

@ -324,7 +324,7 @@ async function handleUserMessageWithBackend(
// 工具错误,不需要单独处理,通过 onSegmentUpdate 统一更新 // 工具错误,不需要单独处理,通过 onSegmentUpdate 统一更新
}, },
onQuestion: (askId, question, options) => { onQuestion: (askId: string, questions: import("../types/api").QuestionItem[]) => {
// 只更新状态栏,问题显示由 onSegmentUpdate 统一处理 // 只更新状态栏,问题显示由 onSegmentUpdate 统一处理
panel.webview.postMessage({ panel.webview.postMessage({
command: "updateStatus", command: "updateStatus",
@ -469,10 +469,11 @@ async function handleUserMessageWithBackend(
export async function handleUserAnswer( export async function handleUserAnswer(
askId: string, askId: string,
selected?: string[], selected?: string[],
customInput?: string customInput?: string,
answers?: { [questionIndex: string]: string[] }
): Promise<void> { ): Promise<void> {
if (currentSession) { if (currentSession) {
await currentSession.submitAnswer(askId, selected, customInput); await currentSession.submitAnswer(askId, selected, customInput, answers);
} }
} }

View File

@ -92,7 +92,8 @@ export async function executeWaveformTrace(
child.on('close', (code: number | null) => { child.on('close', (code: number | null) => {
if (code === 0) { if (code === 0) {
resolve(stdout); // 成功时返回 stdout忽略 stderr 中的进度信息
resolve(stdout || stderr);
} else { } else {
reject(new Error( reject(new Error(
`waveform_trace 执行失败 (code=${code}):\n${stderr || stdout}` `waveform_trace 执行失败 (code=${code}):\n${stderr || stdout}`

View File

@ -28,7 +28,7 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
retainContextWhenHidden: true, retainContextWhenHidden: true,
localResourceRoots: [ localResourceRoots: [
vscode.Uri.joinPath(context.extensionUri, "media"), vscode.Uri.joinPath(context.extensionUri, "media"),
vscode.Uri.joinPath(context.extensionUri, "src", "assets") vscode.Uri.joinPath(context.extensionUri, "dist", "assets")
], ],
} }
); );
@ -47,21 +47,21 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
// 获取模型图标URI // 获取模型图标URI
const autoIconUri = panel.webview.asWebviewUri( const autoIconUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "Auto.png") vscode.Uri.joinPath(context.extensionUri, "dist", "assets", "model", "Auto.png")
); );
const liteIconUri = panel.webview.asWebviewUri( const liteIconUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "lite.png") vscode.Uri.joinPath(context.extensionUri, "dist", "assets", "model", "lite.png")
); );
const syIconUri = panel.webview.asWebviewUri( const syIconUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "Sy.png") vscode.Uri.joinPath(context.extensionUri, "dist", "assets", "model", "Sy.png")
); );
const maxIconUri = panel.webview.asWebviewUri( const maxIconUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "model", "Max.png") vscode.Uri.joinPath(context.extensionUri, "dist", "assets", "model", "Max.png")
); );
// 获取二维码图片URI // 获取二维码图片URI
const qrCodeUri = panel.webview.asWebviewUri( const qrCodeUri = panel.webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, "src", "assets", "QRCode", "wx.png") vscode.Uri.joinPath(context.extensionUri, "dist", "assets", "QRCode", "wx.png")
); );
// 获取Logo URI // 获取Logo URI
@ -129,7 +129,8 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
handleUserAnswer( handleUserAnswer(
message.askId, message.askId,
message.selected, message.selected,
message.customInput message.customInput,
message.answers
); );
break; break;
// 新增:中止对话 // 新增:中止对话
@ -320,15 +321,15 @@ export class ICViewProvider implements vscode.WebviewViewProvider {
width: 200px; width: 200px;
padding: 8px 12px; padding: 8px 12px;
margin: 4px 0; margin: 4px 0;
background: var(--vscode-button-background); background: #007ACC;
color: var(--vscode-button-foreground); color: #ffffff;
border: none; border: none;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
text-align: center; text-align: center;
} }
.btn:hover { .btn:hover {
background: var(--vscode-button-hoverBackground); background: #005a9e;
} }
</style> </style>
</head> </head>

View File

@ -16,7 +16,7 @@ export function getContextButtonContent(): string {
<span class="add-context-label">添加上下文</span> <span class="add-context-label">添加上下文</span>
</button> </button>
<span class="tooltiptext">添加文件、文件夹、图片或文档作为上下文</span> <span class="tooltiptext">添加文件、文件夹作为上下文</span>
</div> </div>
<!-- 上拉菜单 --> <!-- 上拉菜单 -->

View File

@ -330,7 +330,7 @@ export function getConversationHistoryBarStyles(): string {
} }
.new-conversation-button:hover { .new-conversation-button:hover {
background: var(--vscode-toolbar-hoverBackground); background: #007ACC;
transform: scale(1.1); transform: scale(1.1);
} }

View File

@ -446,6 +446,9 @@ export function getInputAreaScript(): string {
addMessage(displayText, 'user'); addMessage(displayText, 'user');
// 重置分段消息容器,强制下次创建新容器
currentSegmentedMessage = null;
// 标记已有消息,切换布局到底部 // 标记已有消息,切换布局到底部
hasMessages = true; hasMessages = true;
updateInputAreaLayout(); updateInputAreaLayout();

View File

@ -248,19 +248,21 @@ export function getMessageAreaStyles(): string {
} }
.question-option { .question-option {
padding: 8px 16px; padding: 8px 16px;
background: var(--vscode-button-secondaryBackground); background: #007ACC;
color: var(--vscode-button-secondaryForeground); color: #ffffff;
border: 1px solid var(--vscode-button-border); border: 1px solid #007ACC;
border-radius: 6px; border-radius: 6px;
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s;
} }
.question-option:hover { .question-option:hover {
background: var(--vscode-button-secondaryHoverBackground); background: #005a9e;
border-color: #005a9e;
} }
.question-option.selected { .question-option.selected {
background: var(--vscode-button-background); background: #007ACC;
color: var(--vscode-button-foreground); color: #ffffff;
border-color: #007ACC;
} }
.question-message.answered .question-option:not(.selected) { .question-message.answered .question-option:not(.selected) {
opacity: 0.5; opacity: 0.5;
@ -420,6 +422,9 @@ export function getMessageAreaStyles(): string {
height: 100%; height: 100%;
display: block; display: block;
} }
.icon-expanded svg path {
fill: #007ACC !important;
}
.tool-segment-header.collapsed .tool-collapse-icon { .tool-segment-header.collapsed .tool-collapse-icon {
transform: rotate(-90deg); transform: rotate(-90deg);
} }
@ -546,7 +551,7 @@ export function getMessageAreaStyles(): string {
.tool-segment-description { .tool-segment-description {
margin: 6px 0 0 0px; margin: 6px 0 0 0px;
font-size: 0.9rem; font-size: 0.9rem;
color: #ccc; color: var(--vscode-descriptionForeground);
line-height: 1.4; line-height: 1.4;
} }
/* 低调显示的工具调用样式 */ /* 低调显示的工具调用样式 */
@ -585,20 +590,22 @@ export function getMessageAreaStyles(): string {
} }
.segment-question .question-option { .segment-question .question-option {
padding: 8px 16px; padding: 8px 16px;
background: var(--vscode-button-secondaryBackground); background: #007ACC;
color: var(--vscode-button-secondaryForeground); color: #ffffff;
border: 1px solid var(--vscode-button-border); border: 1px solid #007ACC;
border-radius: 6px; border-radius: 6px;
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s;
font-size: 13px; font-size: 13px;
} }
.segment-question .question-option:hover { .segment-question .question-option:hover {
background: var(--vscode-button-secondaryHoverBackground); background: #005a9e;
border-color: #005a9e;
} }
.segment-question .question-option.selected { .segment-question .question-option.selected {
background: var(--vscode-button-background); background: #007ACC;
color: var(--vscode-button-foreground); color: #ffffff;
border-color: #007ACC;
} }
.segment-question.answered .question-option:not(.selected) { .segment-question.answered .question-option:not(.selected) {
opacity: 0.5; opacity: 0.5;
@ -1007,34 +1014,42 @@ export function getMessageAreaScript(): string {
// 实时更新分段消息(按后端返回顺序) // 实时更新分段消息(按后端返回顺序)
function updateSegmentsRealtime(segments, isComplete) { function updateSegmentsRealtime(segments, isComplete) {
console.log('[WebView] updateSegmentsRealtime 被调用, segments:', segments, 'isComplete:', isComplete); // 如果对话完成且没有新段落,只重置容器
if (isComplete && (!segments || segments.length === 0)) {
currentSegmentedMessage = null;
return;
}
if (!segments || segments.length === 0) { if (!segments || segments.length === 0) {
console.log('[WebView] segments 为空,跳过渲染');
return; return;
} }
// 如果没有当前分段消息容器,创建一个 // 如果没有当前分段消息容器,创建一个
if (!currentSegmentedMessage) { if (!currentSegmentedMessage) {
console.log('[WebView] 创建新的分段消息容器');
// 移除流式消息(如果有) // 移除流式消息(如果有)
if (currentStreamingMessage) { if (currentStreamingMessage) {
console.log('[WebView] 移除流式消息');
currentStreamingMessage.remove(); currentStreamingMessage.remove();
currentStreamingMessage = null; currentStreamingMessage = null;
} }
// 移除所有工具状态消息(因为会在分段中显示) // 移除所有工具状态消息(因为会在分段中显示)
const toolStatuses = messagesEl.querySelectorAll('.tool-status'); const toolStatuses = messagesEl.querySelectorAll('.tool-status');
console.log('[WebView] 找到工具状态消息数量:', toolStatuses.length);
toolStatuses.forEach(el => { toolStatuses.forEach(el => {
console.log('[WebView] 移除工具状态消息:', el.className);
el.remove(); el.remove();
}); });
currentSegmentedMessage = document.createElement('div'); // 检查最后一个容器是否是未完成的对话(没有操作按钮)
currentSegmentedMessage.className = 'message bot-message segmented-message'; const lastSegmented = messagesEl.querySelector('.segmented-message:last-child');
messagesEl.appendChild(currentSegmentedMessage); if (lastSegmented && !lastSegmented.querySelector('.message-actions')) {
// 复用未完成的容器
currentSegmentedMessage = lastSegmented;
} else {
// 创建新容器
currentSegmentedMessage = document.createElement('div');
currentSegmentedMessage.className = 'message bot-message segmented-message';
messagesEl.appendChild(currentSegmentedMessage);
}
renderedSegmentCount = 0;
} }
// 保存当前所有工具的展开/折叠状态 // 保存当前所有工具的展开/折叠状态
@ -1173,64 +1188,60 @@ export function getMessageAreaScript(): string {
} else if (segment.type === 'question') { } else if (segment.type === 'question') {
segmentDiv.className += ' segment-question'; segmentDiv.className += ' segment-question';
// 兼容旧格式:如果有 segment.question转换为 questions 数组
const questions = segment.questions || (segment.question ? [{
question: segment.question,
options: segment.options || [],
multiSelect: false
}] : []);
// 检查是否已回答 // 检查是否已回答
const isAnswered = answeredQuestions.has(segment.askId); const isAnswered = answeredQuestions.has(segment.askId);
const selectedAnswer = answeredQuestions.get(segment.askId); const savedAnswers = answeredQuestions.get(segment.askId) || {};
if (isAnswered) { if (isAnswered) {
segmentDiv.classList.add('answered'); segmentDiv.classList.add('answered');
} }
// 检查是否有选项 // 渲染多个问题
const hasOptions = segment.options && segment.options.length > 0; const questionsHtml = questions.map((q, qIndex) => {
const inputType = q.multiSelect ? 'checkbox' : 'radio';
const inputName = \`q\${qIndex}\`;
const selectedAnswers = savedAnswers[qIndex] || [];
const optionsHtml = hasOptions const optionsHtml = q.options.map(opt => {
? (segment.options || []).map(opt => { const isSelected = selectedAnswers.includes(opt);
const isSelected = isAnswered && opt === selectedAnswer; return \`<label style="display:flex;align-items:center;gap:6px;cursor:pointer;padding:4px 0;">
return \`<button class="question-option\${isSelected ? ' selected' : ''}" data-option="\${opt}">\${opt}</button>\`; <input type="\${inputType}" name="\${inputName}" value="\${opt}" \${isSelected ? 'checked' : ''} \${isAnswered ? 'disabled' : ''}>
}).join('') <span>\${opt}</span>
: ''; </label>\`;
}).join('');
return \`
<div class="question-item" data-question-index="\${qIndex}" style="margin-bottom:12px;">
<div class="question-text" style="margin-bottom:8px;">\${formatText(q.question)}</div>
<div class="question-options">\${optionsHtml}</div>
</div>
\`;
}).join('');
segmentDiv.innerHTML = \` segmentDiv.innerHTML = \`
<div class="question-text">\${formatText(segment.question || '')}</div> \${questionsHtml}
\${hasOptions ? \`<div class="question-options" data-ask-id="\${segment.askId}">\${optionsHtml}</div>\` : ''} <button class="custom-submit" style="display:\${isAnswered ? 'none' : 'block'};margin-top:8px;padding:8px 16px;background:var(--vscode-button-background);color:var(--vscode-button-foreground);border:none;border-radius:6px;cursor:pointer;">提交答案</button>
<div class="custom-input-container" style="display: \${isAnswered ? 'none' : 'flex'};">
<input type="text" class="custom-input" placeholder="\${hasOptions ? '输入其他答案...' : '请输入您的答案...'}" />
<button class="custom-submit">提交</button>
</div>
\`; \`;
// 只在未回答时添加事件监听 // 只在未回答时添加事件监听
if (!isAnswered) { if (!isAnswered) {
setTimeout(() => { setTimeout(() => {
if (hasOptions) {
const optionButtons = segmentDiv.querySelectorAll('.question-option');
optionButtons.forEach(btn => {
btn.addEventListener('click', function() {
const option = this.getAttribute('data-option');
handleQuestionAnswerInSegment(segment.askId, option, segmentDiv);
});
});
}
const submitBtn = segmentDiv.querySelector('.custom-submit'); const submitBtn = segmentDiv.querySelector('.custom-submit');
const customInput = segmentDiv.querySelector('.custom-input'); if (submitBtn) {
if (submitBtn && customInput) {
submitBtn.addEventListener('click', function() { submitBtn.addEventListener('click', function() {
const customValue = customInput.value.trim(); const answers = {};
if (customValue) { questions.forEach((q, qIndex) => {
handleQuestionAnswerInSegment(segment.askId, customValue, segmentDiv); const inputs = segmentDiv.querySelectorAll(\`input[name="q\${qIndex}"]:checked\`);
} answers[qIndex] = Array.from(inputs).map(input => input.value);
}); });
handleMultiQuestionAnswer(segment.askId, answers, segmentDiv);
// 支持回车提交
customInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
const customValue = customInput.value.trim();
if (customValue) {
handleQuestionAnswerInSegment(segment.askId, customValue, segmentDiv);
}
}
}); });
} }
}, 0); }, 0);
@ -1246,7 +1257,7 @@ export function getMessageAreaScript(): string {
currentSegmentedMessage.appendChild(segmentDiv); currentSegmentedMessage.appendChild(segmentDiv);
}); });
// 如果对话完成,添加操作按钮 // 如果对话完成,添加操作按钮并重置容器
if (isComplete) { if (isComplete) {
console.log('[WebView] 对话完成,添加操作按钮'); console.log('[WebView] 对话完成,添加操作按钮');
const actionsDiv = document.createElement('div'); const actionsDiv = document.createElement('div');
@ -1281,7 +1292,7 @@ export function getMessageAreaScript(): string {
actionsDiv.appendChild(dislikeBtn); actionsDiv.appendChild(dislikeBtn);
currentSegmentedMessage.appendChild(actionsDiv); currentSegmentedMessage.appendChild(actionsDiv);
// 重置当前分段消息容器 // 重置当前分段消息容器(继续对话时创建新容器)
currentSegmentedMessage = null; currentSegmentedMessage = null;
} }
@ -1708,6 +1719,34 @@ export function getMessageAreaScript(): string {
}); });
} }
// 处理多问题答案提交
function handleMultiQuestionAnswer(askId, answers, segmentDiv) {
console.log('[WebView] 多问题答案提交:', askId, answers);
// 保存答案到 Map 中
answeredQuestions.set(askId, answers);
// 标记问题已回答
segmentDiv.classList.add('answered');
// 禁用所有输入
const inputs = segmentDiv.querySelectorAll('input');
inputs.forEach(input => input.disabled = true);
// 隐藏提交按钮
const submitBtn = segmentDiv.querySelector('.custom-submit');
if (submitBtn) {
submitBtn.style.display = 'none';
}
// 发送答案到后端
vscode.postMessage({
command: 'submitAnswer',
askId: askId,
answers: answers
});
}
${getWaveformPreviewScript()} ${getWaveformPreviewScript()}
${getCodeHighlightScript()} ${getCodeHighlightScript()}

View File

@ -18,7 +18,7 @@ export function getNdtWelcomeModalContent(logoUri?: string): string {
<div class="ndt-welcome-modal-header"> <div class="ndt-welcome-modal-header">
<div class="ndt-welcome-icon">🎉</div> <div class="ndt-welcome-icon">🎉</div>
<h2>欢迎宁德时代新能源科技股份有限公司<span style="white-space: nowrap;">的各位专家</span>使用 IC Coder</h2> <h2>欢迎企业<span style="white-space: nowrap;">的各位专家</span>使用 IC Coder</h2>
</div> </div>
<div class="ndt-welcome-modal-body"> <div class="ndt-welcome-modal-body">

View File

@ -195,11 +195,11 @@ export function getPlanCardStyles(): string {
background: var(--vscode-list-hoverBackground); background: var(--vscode-list-hoverBackground);
} }
.plan-btn-confirm { .plan-btn-confirm {
background: var(--vscode-button-background); background: #007ACC;
color: var(--vscode-button-foreground); color: #ffffff;
} }
.plan-btn-confirm:hover { .plan-btn-confirm:hover {
background: var(--vscode-button-hoverBackground); background: #005a9e;
} }
.plan-btn-cancel { .plan-btn-cancel {
background: transparent; background: transparent;

View File

@ -186,8 +186,8 @@ export function getProgressBarStyles(): string {
/* 已完成状态 */ /* 已完成状态 */
.progress-step.completed .step-circle { .progress-step.completed .step-circle {
background: var(--vscode-button-background); background: #007ACC;
border-color: var(--vscode-button-background); border-color: #007ACC;
} }
.progress-step.completed .step-number { .progress-step.completed .step-number {
@ -204,14 +204,14 @@ export function getProgressBarStyles(): string {
} }
.progress-step.completed + .progress-line { .progress-step.completed + .progress-line {
background: var(--vscode-button-background); background: #007ACC;
} }
/* 进行中状态 */ /* 进行中状态 */
.progress-step.active .step-circle { .progress-step.active .step-circle {
background: var(--vscode-button-background); background: #007ACC;
border-color: var(--vscode-button-background); border-color: #007ACC;
box-shadow: 0 0 0 2px var(--vscode-button-background)33; box-shadow: 0 0 0 2px #007ACC33;
animation: pulse 2s infinite; animation: pulse 2s infinite;
} }
@ -226,10 +226,10 @@ export function getProgressBarStyles(): string {
@keyframes pulse { @keyframes pulse {
0%, 100% { 0%, 100% {
box-shadow: 0 0 0 2px var(--vscode-button-background)33; box-shadow: 0 0 0 2px #007ACC33;
} }
50% { 50% {
box-shadow: 0 0 0 4px var(--vscode-button-background)1a; box-shadow: 0 0 0 4px #007ACC1a;
} }
} }
@ -351,7 +351,7 @@ export function getProgressBarScript(): string {
// 更新连接线 // 更新连接线
document.querySelectorAll('.progress-line').forEach((line, index) => { document.querySelectorAll('.progress-line').forEach((line, index) => {
if (index < currentIndex) { if (index < currentIndex) {
line.style.background = 'var(--vscode-button-background)'; line.style.background = '#007ACC';
} else { } else {
line.style.background = 'var(--vscode-input-border)'; line.style.background = 'var(--vscode-input-border)';
} }

View File

@ -19,40 +19,41 @@ export function getWelcomeModalContent(logoUri?: string): string {
<div class="welcome-modal-header"> <div class="welcome-modal-header">
<div class="welcome-icon">🎉</div> <div class="welcome-icon">🎉</div>
<h2>欢迎使用 IC Coder</h2> <h2>欢迎使用 IC Coder</h2>
<p class="welcome-modal-subtitle">您已成功激活 15 天试用期,让我们开始探索 IC Coder 的强大功能吧!</p>
</div> </div>
<div class="welcome-modal-body"> <div class="welcome-modal-body">
<div class="welcome-step"> <!-- 试用期提示 -->
<div class="welcome-step-icon">📝</div> <div class="trial-banner">
<div class="welcome-step-content"> <span>您已获得 <strong>5 天企业版试用期</strong>企业版试用期内Credits用量无限并可无限制使用所有功能</span>
<h3>步骤 1打开聊天面板</h3> </div>
<p>点击侧边栏的 IC Coder 图标,或使用命令面板搜索 "IC Coder: Open Chat"</p>
<!-- IC Coder 简介 -->
<div class="intro-section">
<h3 class="section-title">关于 IC Coder</h3>
<p class="intro-text">IC Coder是一款The Agentic AI Verilog Coding Platform自主式人工智能 Verilog 编码平台。我们采用全球顶尖的IC Coder自研芯片设计微调模型为代码生成提供强大的AI能力支撑。</p>
<div class="features">
<div class="feature-item">
<span class="feature-text">多智能体架构Multi-Agent System多个专业化AI智能体协同工作分别负责架构设计、代码生成、验证测试等不同环节</span>
</div>
<div class="feature-item">
<span class="feature-text">增强上下文引擎:智能理解和管理大规模设计上下文,确保生成代码的一致性和准确性</span>
</div>
<div class="feature-item">
<span class="feature-text">AI自主仿真IC Coder提供完全自动化的仿真验证流程无需手动编写测试代码</span>
</div>
</div> </div>
</div> </div>
<div class="welcome-step"> <!-- 按钮组 -->
<div class="welcome-step-icon">💬</div> <div class="button-group">
<div class="welcome-step-content"> <button id="welcomeStartBtn" class="welcome-btn welcome-btn-primary">
<h3>步骤 2输入您的需求</h3> <span>开始使用</span>
<p>描述您想要生成的 Verilog 代码或需要帮助的问题AI 将为您提供专业的解决方案</p> <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
</div> <path d="M6 3L11 8L6 13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div> </div>
<div class="welcome-step">
<div class="welcome-step-icon">🔬</div>
<div class="welcome-step-content">
<h3>步骤 3运行仿真</h3>
<p>使用 "生成 VCD" 命令运行 iverilog 仿真,并通过波形查看器查看仿真结果</p>
</div>
</div>
<button id="welcomeStartBtn" class="welcome-btn welcome-btn-primary">
<span>开始使用</span>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M6 3L11 8L6 13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div> </div>
</div> </div>
</div> </div>
@ -96,7 +97,7 @@ export function getWelcomeModalStyles(): string {
border: 1px solid var(--vscode-widget-border); border: 1px solid var(--vscode-widget-border);
border-radius: 12px; border-radius: 12px;
width: 100%; width: 100%;
max-width: 500px; max-width: 480px;
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5); box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
animation: modalSlideIn 0.3s cubic-bezier(0.2, 0.8, 0.2, 1); animation: modalSlideIn 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
overflow: hidden; overflow: hidden;
@ -124,9 +125,10 @@ export function getWelcomeModalStyles(): string {
.welcome-modal-header h2 { .welcome-modal-header h2 {
margin: 0 0 12px; margin: 0 0 12px;
font-size: 24px; font-size: 20px;
font-weight: 600; font-weight: 600;
color: var(--vscode-foreground); color: var(--vscode-foreground);
line-height: 1.4;
} }
.welcome-modal-subtitle { .welcome-modal-subtitle {
@ -140,37 +142,70 @@ export function getWelcomeModalStyles(): string {
padding: 0 32px 32px; padding: 0 32px 32px;
} }
.welcome-step { /* 试用期横幅 */
.trial-banner {
display: flex; display: flex;
gap: 16px; align-items: center;
margin: 20px 0; justify-content: center;
padding: 16px; padding: 12px 16px;
background: var(--vscode-editor-inactiveSelectionBackground); background: var(--vscode-editor-inactiveSelectionBackground);
border-radius: 8px; border-radius: 8px;
border-left: 4px solid var(--vscode-textLink-foreground); margin-bottom: 20px;
}
.welcome-step-icon {
font-size: 24px;
flex-shrink: 0;
}
.welcome-step-content h3 {
margin: 0 0 8px;
font-size: 15px;
font-weight: 600;
color: var(--vscode-textLink-foreground);
}
.welcome-step-content p {
margin: 0;
font-size: 13px; font-size: 13px;
color: var(--vscode-descriptionForeground); color: var(--vscode-descriptionForeground);
line-height: 1.5; border-left: 3px solid var(--vscode-textLink-foreground);
}
.trial-banner strong {
color: var(--vscode-textLink-foreground);
font-weight: 600;
}
/* IC Coder 简介区域 */
.intro-section {
margin-bottom: 24px;
}
.section-title {
margin: 0 0 12px;
font-size: 15px;
font-weight: 600;
color: var(--vscode-foreground);
}
.intro-text {
margin: 0 0 16px;
font-size: 13px;
color: var(--vscode-descriptionForeground);
line-height: 1.6;
}
.features {
display: flex;
flex-direction: column;
gap: 10px;
}
.feature-item {
padding: 10px 12px;
background: var(--vscode-editor-inactiveSelectionBackground);
border-radius: 6px;
font-size: 13px;
color: var(--vscode-foreground);
border-left: 2px solid var(--vscode-textLink-foreground);
}
.feature-text {
display: block;
}
/* 按钮组 */
.button-group {
display: flex;
} }
.welcome-btn { .welcome-btn {
width: 100%; flex: 1;
padding: 12px 16px; padding: 12px 16px;
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: 600;
@ -181,18 +216,32 @@ export function getWelcomeModalStyles(): string {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 8px; gap: 8px;
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
transition: all 0.2s; transition: all 0.2s;
margin-top: 24px;
} }
.welcome-btn:hover { .welcome-btn-primary {
background: var(--vscode-button-background);
color: var(--vscode-button-foreground);
}
.welcome-btn-primary:hover {
background: var(--vscode-button-hoverBackground); background: var(--vscode-button-hoverBackground);
transform: translateY(-1px); transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0,0,0,0.2); box-shadow: 0 2px 8px rgba(0,0,0,0.2);
} }
.welcome-btn-secondary {
background: var(--vscode-button-secondaryBackground);
color: var(--vscode-button-secondaryForeground);
border: 1px solid var(--vscode-button-border);
}
.welcome-btn-secondary:hover {
background: var(--vscode-button-secondaryHoverBackground);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
.welcome-btn:active { .welcome-btn:active {
transform: translateY(0); transform: translateY(0);
} }

Binary file not shown.

View File

@ -3,6 +3,7 @@
'use strict'; 'use strict';
const path = require('path'); const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
//@ts-check //@ts-check
/** @typedef {import('webpack').Configuration} WebpackConfig **/ /** @typedef {import('webpack').Configuration} WebpackConfig **/
@ -45,5 +46,12 @@ const extensionConfig = {
infrastructureLogging: { infrastructureLogging: {
level: "log", // enables logging required for problem matchers level: "log", // enables logging required for problem matchers
}, },
plugins: [
new CopyWebpackPlugin({
patterns: [
{ from: 'src/assets', to: 'assets' }
]
})
]
}; };
module.exports = [ extensionConfig ]; module.exports = [ extensionConfig ];