34 Commits

Author SHA1 Message Date
47f95afabb Merge branch 'feat/codeToChat' into feat/ningDeShiDai 2026-03-10 20:53:31 +08:00
7fe87e515b feat:新增对话结束后添加结束语句 2026-03-10 20:53:13 +08:00
081ddec55c Merge branch 'feat/ningDeShiDai' of https://git.pengyejiatu.com/pengyejiatu/IC-Coder-Plugin into feat/ningDeShiDai 2026-03-10 19:18:06 +08:00
12c2f634bd Merge branch 'feat/codeToChat' into feat/ningDeShiDai 2026-03-10 19:05:45 +08:00
790110ba7e feat: 添加示例刷新按钮
- 在示例标题旁添加刷新按钮
   - 点击从13个示例中随机选择2个替换当前显示
   - 添加500ms节流防止频繁点击
   - 优化按钮交互动画效果
2026-03-10 19:04:45 +08:00
b9dc631bf7 Merge branch 'feat/ningDeShiDai' of https://git.pengyejiatu.com/pengyejiatu/Ic-coder-plugin into feat/ningDeShiDai 2026-03-10 19:01:38 +08:00
6425496d2e feat: 离线部署模式改进和 SystemVerilog 支持
- 添加离线模式仿真模拟:识别代码生成完成消息后自动模拟仿真流程
- 启用 iverilog SystemVerilog 2012 标准支持(-g2012)
- 优化进度条显示逻辑

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 18:56:53 +08:00
fd5a01c67f Merge branch 'feat/codeToChat' into feat/ningDeShiDai 2026-03-10 18:40:13 +08:00
29e80ce296 feat: 优化消息处理和界面显示
- 增强 messageHandler 消息处理逻辑
   - 优化 messageArea 显示效果
   - 改进 webviewContent 界面交互
2026-03-10 18:39:50 +08:00
c244a308d7 style:将spec改为specification 2026-03-10 17:08:36 +08:00
a25d68f527 feat: 添加用户手册只读预览功能
- 新增 UserManualPanel 组件实现只读预览
   - 支持 Markdown 完整渲染(表格、代码块、图片、分隔线)
   - 优化排版和字体大小
   - 用户无法编辑手册内容
2026-03-10 17:01:16 +08:00
77b54aebf0 feat: 添加用户手册功能
- 新增用户手册 Markdown 文档及配套截图
   - 新增打开用户手册命令
   - 在侧边栏和主面板中集成用户手册入口
   - 优化用户手册打开方式,支持 Markdown 预览
2026-03-10 16:29:37 +08:00
840436eb36 refactor: 优化设置面板和模型提示文案
- 移除设置面板中的规则配置标签页
   - 更新模型选择器提示文案为"FPGA专属微调模型"
2026-03-10 14:23:37 +08:00
f5dd7534f0 feat:将模型选中固定为max模型
- 并且鼠标移动上去显示“IC Coder自研顶尖微调模型“
2026-03-10 14:13:20 +08:00
ebb9de5294 tyle:修改了聊天面板的样式及删除了用户反馈和web端的跳转 2026-03-10 14:02:44 +08:00
531d140b99 refactor: 优化错误提示信息
- 将"当前访问人数过多"改为更通用的"处理用户消息失败"
   - 移除错误消息中的  emoji,保持简洁
2026-03-09 15:55:51 +08:00
97b8e8aa7d refactor: 更新后端服务地址占位符 2026-03-09 15:34:41 +08:00
4ed998e937 feat: 添加后端服务地址自定义配置功能
- 在设置面板添加后端服务地址配置项
   - 支持保存、加载和重置自定义配置
   - 配置持久化存储,重启后保留
   - 添加 SSE 请求日志用于验证配置
2026-03-09 15:29:56 +08:00
ad0f0336d5 feat: 移除用户信息、余额检查和登录过期提示
- 隐藏用户信息显示和退出登录按钮
   - 删除发送消息前的余额检查逻辑
   - 删除对话完成后的余额更新逻辑
   - 注释掉所有登录过期弹窗提示
   - 移除用户服务和余额服务的初始化调用
2026-03-09 14:28:41 +08:00
7cde4fa138 refactor: 优化代码格式和用户提示
- 统一代码格式化(Prettier)
- 将 iverilog 相关错误提示改为 'IC Coder编译器'
- 优化后端服务错误提示为 '当前访问人数过多,请稍后重试'
- 修复代码风格一致性问题
2026-03-09 11:10:56 +08:00
1b7259d1c1 feat:排除打包项目中的waveform_trace文件中的无关文档 2026-03-09 10:41:17 +08:00
09ff812562 feat:修复 Windows vvp 解析问题
- 修复 iverilog 生成的 .vvp 文件 shebang 导致 Windows 解析失败
2026-03-07 18:41:42 +08:00
e7c631d532 feat: 优化文档结构
- 将文档移至 docs/ 目录统一管理
   - 更新 .vscodeignore 排除规则
2026-03-07 18:41:14 +08:00
06573e37d7 feat: 优化 webpack 打包配置
- 添加自动模式切换(开发/生产)
   - 启用 Tree Shaking 移除未使用代码
   - 加快编译速度(transpileOnly)
   - 添加打包体积监控
   - 自动清理旧文件
   - 添加打包优化文档
2026-03-06 18:27:56 +08:00
d740f4da44 feat: 支持文件路径标签带行号点击跳转
- 前端解析 file.v:1-2 格式,提取文件名和行号
   - 新增 openFilePathTag 命令,支持智能文件查找
   - 修复模板字符串中正则表达式转义问题
   - 不影响现有 openFile 和 diff 功能
2026-03-06 16:24:21 +08:00
f24bd38ec7 feat: 优化上下文项显示和识别逻辑
- 支持显示所有类型的上下文项(文件和代码片段)
   - 增强路径识别,支持代码片段格式(文件名:行号-行号)
2026-03-06 15:40:59 +08:00
45934baf0a feat: 添加上下文项点击功能
- 文件类型可点击打开文件
   - 代码片段可点击打开文件并选中对应代码
   - 文件夹类型不可点击
2026-03-06 10:13:27 +08:00
4384ee53c5 fix: 修复关闭面板后快捷键无法自动打开面板的问题
- 通过 try-catch 检测 webview 是否真正可用
   - 修复 panel._isDisposed 检测不准确的问题
   - 增加异常捕获防止发送消息时崩溃
   - 延长消息发送延迟至 500ms 确保面板加载完成
2026-03-06 10:05:52 +08:00
d89c326be5 Merge branch 'feat/DeleteConfirmation' into feat/codeToChat 2026-03-06 09:16:12 +08:00
2dccb4f871 update:changelog.md 2026-03-06 09:11:33 +08:00
a9ddf3074e 1.0.12 2026-03-06 09:10:51 +08:00
db087bb184 update:更新changelog.md 2026-03-06 09:10:09 +08:00
5e9083041f fix: 修复多选问题提交后选中项不显示高亮的问题 2026-03-06 09:08:38 +08:00
be0555d6bc feat:codeToChat 2026-03-06 08:59:02 +08:00
46 changed files with 1802 additions and 1252 deletions

View File

@ -18,10 +18,15 @@ node_modules/**
# 文档(避免中文文件名打包问题) # 文档(避免中文文件名打包问题)
docs/** docs/**
PUBLISH.md CLAUDE.md
# 只排除 waveform_trace 的 src/dist 目录 # 只排除 waveform_trace 的 src/dist 目录
tools/waveform_trace/src/dist/** tools/waveform_trace/src/**
tools/iverilog/examples/**
tools/iverilog/INSTALL.md
tools/iverilog/README.md
tools/iverilog/DOWNLOAD_INSTRUCTIONS.md
# Git 相关 # Git 相关
.git/** .git/**

View File

@ -2,6 +2,12 @@
所有重要的项目变更都将记录在此文件中。 所有重要的项目变更都将记录在此文件中。
## [1.0.12] - 2026-03-06
### 新增
- 支持 AskUserQuestion 多问题和多选功能
## [1.0.9] - 2026-03-04 ## [1.0.9] - 2026-03-04
### 优化 ### 优化

View File

@ -0,0 +1,42 @@
# 代码快速添加到对话功能
## 功能说明
选中代码后,通过右键菜单/小灯泡/快捷键Ctrl+Shift+I将代码作为上下文添加到聊天面板输入框上方。
## 实现方式
### 1. Code Action Provider
`src/providers/codeActionProvider.ts` - 提供小灯泡菜单选项
### 2. 命令注册
`src/extension.ts` - 注册 `ic-coder.addCodeToChat` 命令,发送消息到 webview
### 3. 全局引用
`src/panels/ICHelperPanel.ts` - 保存 panel 到 `(global as any).currentICHelperPanel`
### 4. 上下文显示
`src/views/contextDisplay.ts` - 添加 `code` 类型支持和 `addCodeContext` 消息处理
### 5. 配置
`package.json` - 配置命令、右键菜单、快捷键
## 用户体验
1. 选中代码
2. 右键/小灯泡/Ctrl+Shift+I
3. 代码显示为上下文项:`文件名.v:10-25` 📄
4. 输入问题发送(代码自动作为上下文)
## 数据结构
代码上下文存储为 JSON
```json
{
"fileName": "路径",
"startLine": 10,
"endLine": 25,
"code": "代码内容",
"languageId": "verilog"
}
```

View File

@ -0,0 +1,55 @@
# Webpack 打包优化说明
## 优化内容
### 1. 自动模式切换
- 开发模式:保持源码可读性
- 生产模式:自动压缩代码
### 2. 性能优化
- **Tree Shaking**:移除未使用的代码
- **transpileOnly**:跳过类型检查,加快编译速度
- **自动清理**:每次打包自动删除旧文件
### 3. 体积监控
- 单文件超过 2MB 会发出警告
- 帮助及时发现打包体积问题
## 使用方法
### 开发模式
```bash
# 编译(不压缩)
pnpm run compile
# 监听模式(自动重新编译)
pnpm run watch
```
### 生产模式
```bash
# Windows
set NODE_ENV=production && pnpm run package
# macOS/Linux
NODE_ENV=production pnpm run package
```
## 打包结果
- **输出目录**`dist/`
- **入口文件**`dist/extension.js`
- **静态资源**`dist/assets/`
## 性能对比
| 模式 | 体积 | 编译速度 | Source Map |
|------|------|----------|------------|
| 开发 | 较大 | 快 | 完整 |
| 生产 | 小 | 较慢 | 隐藏 |
## 注意事项
1. 开发时使用 `pnpm run watch`,修改代码自动重新编译
2. 发布前必须使用生产模式打包
3. 如果打包体积超过 2MB检查是否引入了不必要的依赖

251
media/USER_MANUAL.md Normal file
View File

@ -0,0 +1,251 @@
# IC Coder 插件端用户手册
欢迎**宁德时代新能源科技股份有限公司**的各位专家使用 IC Coder 企业版!
本手册旨在为贵公司提供插件的安装、配置及使用指导,帮助您快速上手并充分发挥工具的功能。
在使用过程中,如遇任何功能异常、性能问题或操作疑问,欢迎随时联系我们,我们将在第一时间为您提供支持。
若贵司有特定的业务场景或个性化功能需求,也可直接与我们沟通,我们会为贵司进行定制化开发。
| 功能 | 说明 |
| ----------------------- | ---------------------------------------------------- |
| 自研微调模型 | IC Coder 自研顶尖 Max 微调模型,专为 FPGA 开发 |
| 高质量 Verilog 代码生成 | 根据自然语言描述生成符合规范的 Verilog 代码 |
| 自动语法检查 | 自动检测代码语法错误并自动修复 |
| 自动仿真 | 内置编译器,自动编译和仿真 |
| 波形检查工具 | 内置 VCD 波形查看器,自动波形解析 |
| 自主检查迭代 | AI 自动检查代码问题并迭代优化 |
| Diff 功能 | 快速比较代码差异,追踪 AI 修改,可一键确认和撤销修改 |
| Code Action 快捷操作 | Ctrl+L 快捷键或右键菜单快速添加代码到对话 |
| 支持上下文连续对话 | 多轮对话AI 记住之前的交互内容 |
| 会话历史管理 | 自动保存对话记录,随时恢复历史会话 |
## IC Coder 快速入门指南
### 系统要求
- **Visual Studio Code**: 版本 >= 1.60.0
各位专家也可以通过这个链接下载VSCode [下载链接](https://code.visualstudio.com/)
---
### 安装步骤
#### 步骤 1通过 VSIX 文件安装(推荐)
1. **获取安装包**
- 确保您已经获得 `iccoder-Trial-1.0.vsix` 文件
2. **打开 VS Code**
- 启动 Visual Studio Code
3. **安装插件**
有以下三种安装方式:
**方式 A通过命令面板**
-`Ctrl+Shift+P` (Windows/Linux) 或 `Cmd+Shift+P` (macOS) 打开命令面板
- 输入 `Extensions: Install from VSIX...`
- 选择 `iccoder-Trial-1.0.vsix` 文件
- 等待安装完成
![安装方式1.png](./manual/安装方式1.png)
**方式 B通过扩展视图**
- 点击左侧活动栏的扩展图标(或按 `Ctrl+Shift+X`
- 点击扩展视图右上角的 `...` (更多操作)
- 选择 `从 VSIX 安装...`
- 选择 `iccoder-Trial-1.0.vsix` 文件
![安装方式2.png](./manual/安装方式2.png)
**方式 C通过命令行**
```bash
code --install-extension iccoder-Trial-1.0.vsix
```
4. **重启 VS Code**
- 安装完成后,建议重启 VS Code 以确保插件正常加载
#### 步骤 2打开 IC Coder 界面
**登录后会自动打开**,手动打开也有以下几种方式:
**方式 1通过侧边栏**
- 点击左侧活动栏的 IC Coder 图标
- 侧边栏会显示 IC Coder 聊天界面
![侧边栏打开.png](./manual/侧边栏打开.png)
**方式 2通过命令面板**
- 按 `Ctrl+Shift+P` (Windows/Linux) 或 `Cmd+Shift+P` (macOS)
- 输入以下命令之一:
- `IC Coder: 打开聊天` - 打开聊天界面
- `打开 IC Coder 助手` - 打开助手面板
![命令面板打开.png](./manual/命令面板打开.png)
---
#### 步骤 3开始使用
插件已预配置好后端服务,安装后即可直接使用,无需手动配置。
![聊天界面.png](./manual/聊天界面.png)
### 故障排除
#### 问题 :插件无法安装
**症状**:安装 VSIX 文件时报错
#### 解决方案:
- 确认 VS Code 版本 >= 1.60.0
- 检查 VSIX 文件是否完整(未损坏)
- 尝试以管理员权限运行 VS Code
- 清除 VS Code 缓存后重试
### 完整使用流程示例
下面通过一个完整的案例,展示如何使用 IC Coder 从需求到代码生成的全过程。
#### 步骤 1输入设计需求
在对话框中输入设计需求,例如:
```
我需要设计一个 8 位加法器,要求有进位输入和进位输出
```
点击**发送**按钮。
![输入需求.png](./manual/输入需求.png)
#### 步骤 2AI 询问补充信息
AI 会根据需求,询问一些关键的设计细节。例如:
- 是否需要溢出检测?
- 时钟频率要求是多少?
- 是否需要流水线设计?
您只需要根据实际需求**选择相应的选项或者直接输入需求**即可AI 会根据您的选择生成最合适的设计方案。
#### 步骤 3确认 AI 生成的任务列表
AI 会根据您的需求和补充信息生成一个详细的任务列表Todo List
![确认任务.png](./manual/确认任务.png)
仔细查看任务列表,确认无误后点击**确认**按钮AI 将开始执行。
#### 步骤 4观察 AI 执行过程
AI 开始工作后,您可以在对话框中实时看到所有执行步骤:
![观察执行过程.png](./manual/观察执行过程.png)
每个步骤完成后,任务列表中对应的项目会被标记为完成状态。
#### 步骤 5仿真运行与结果查看
当 AI 完成代码生成后,会自动运行仿真验证:
![仿真运行结果.png](./manual/仿真运行结果.png)
#### 步骤 6查看生成的文件
所有生成的文件会自动保存到您的工作目录中:
```
project/
├── src/
│ └── tb_adder_8bit.v # RTL 设计文件
├── sim/
│ └── tb_adder_8bit # 测试平台文件
└── tb_adder_8bit.vcd # 波形文件
```
您可以在 VS Code 的文件资源管理器中直接打开这些文件进行查看或修改。
#### 步骤8继续对话
如果您对给出的结果不太满意您可以告诉IC Coder您想具体修改的地方或者文件
#### 使用流程总结
整个使用流程可以概括为:
- **输入需求** → 在对话框中描述您的设计需求
- **回答问题** → 根据 AI 的询问选择合适的选项
- **确认任务** → 查看并确认 AI 生成的任务列表
- **观察执行** → 实时查看 AI 的所有执行步骤
- **查看结果** → 仿真成功后查看生成的文件
#### 使用提示
**如何描述需求更准确?**
- **明确功能**:清楚说明模块要实现什么功能
- **指定参数**:说明位宽、时钟频率等关键参数
- **特殊要求**:如果有特殊的时序要求或接口规范,请明确说明
**示例:**
```
好的描述:设计一个 16 位的 FIFO深度为 256支持异步读写
不够清晰:帮我写一个 FIFO
```
#### 常见问题
**Q: 仿真失败了怎么办?**
A: AI 会根据错误自动修复代码并重新仿真。
**Q: 可以修改生成的代码吗?**
A: 可以,可以直接编辑文件,然后告诉 AI 重新运行仿真。
**Q: 可以导入已有的代码吗?**
A: 可以,在工作区中打开对应的代码文件夹,然后直接在对话中告诉 AI 您要修改或优化哪个文件AI 会读取并进行修改。
**Q: 如何查看 AI 的思考过程?**
A: 在执行过程中AI 会实时显示每一步的操作和决策依据。
**Q: 如何保存对话历史?**
A: 对话历史会自动保存在本地,可以点击历史对话查看历史会话记录。
---
### 卸载插件
如需卸载插件:
1. 打开扩展视图
2. 找到 "IC Coder" 插件
3. 点击卸载按钮
4. 重启 VS Code
---
### 注意事项
1. **需提前打开一个文件夹作为工作区**,否则会准确的为您服务
![打开文件夹.png](./manual/打开文件夹.png)
2. **开箱即用**
- 插件已预配置后端服务,无需手动设置
- 安装后即可直接使用所有功能
---
**祝您使用愉快!如有问题欢迎反馈。**

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

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.11", "version": "1.0.12",
"publisher": "ICCoderAgenticVerilogPlatform", "publisher": "ICCoderAgenticVerilogPlatform",
"engines": { "engines": {
"vscode": "^1.80.0" "vscode": "^1.80.0"
@ -54,6 +54,28 @@
"command": "ic-coder.testNotification", "command": "ic-coder.testNotification",
"title": "测试系统通知", "title": "测试系统通知",
"category": "IC Coder" "category": "IC Coder"
},
{
"command": "ic-coder.addCodeToChat",
"title": "添加到 IC Coder 对话",
"category": "IC Coder"
}
],
"menus": {
"editor/context": [
{
"command": "ic-coder.addCodeToChat",
"when": "editorHasSelection",
"group": "9_cutcopypaste"
}
]
},
"keybindings": [
{
"command": "ic-coder.addCodeToChat",
"key": "ctrl+l",
"mac": "cmd+l",
"when": "editorTextFocus && editorHasSelection"
} }
], ],
"viewsContainers": { "viewsContainers": {

View File

@ -29,6 +29,9 @@ export interface IccoderConfig {
serviceTier: ServiceTier; serviceTier: ServiceTier;
} }
/** 自定义配置缓存 */
let customConfig: Partial<IccoderConfig> | null = null;
/** 环境配置 */ /** 环境配置 */
const ENV_CONFIG: Record<Environment, IccoderConfig> = { const ENV_CONFIG: Record<Environment, IccoderConfig> = {
/** 本地开发环境 - 通过 Gateway 路由 */ /** 本地开发环境 - 通过 Gateway 路由 */
@ -38,7 +41,7 @@ const ENV_CONFIG: Record<Environment, IccoderConfig> = {
loginUrl: "http://localhost/login", loginUrl: "http://localhost/login",
timeout: 300000, timeout: 300000,
userId: "default-user", userId: "default-user",
serviceTier: "max", // 默认使用 max serviceTier: "max",
}, },
/** 测试服务器环境 - 通过 Gateway 路由 */ /** 测试服务器环境 - 通过 Gateway 路由 */
test: { test: {
@ -60,6 +63,13 @@ const ENV_CONFIG: Record<Environment, IccoderConfig> = {
}, },
}; };
/**
* 设置自定义配置
*/
export function setCustomConfig(config: Partial<IccoderConfig>) {
customConfig = config;
}
/** /**
* 获取当前环境 * 获取当前环境
*/ */
@ -71,7 +81,14 @@ export function getCurrentEnv(): Environment {
* 获取配置项 * 获取配置项
*/ */
export function getConfig(): IccoderConfig { export function getConfig(): IccoderConfig {
return { ...ENV_CONFIG[CURRENT_ENV] }; const baseConfig = { ...ENV_CONFIG[CURRENT_ENV] };
// 合并自定义配置(空字符串表示使用默认)
if (customConfig?.backendUrl && customConfig.backendUrl !== '') {
baseConfig.backendUrl = customConfig.backendUrl;
}
return baseConfig;
} }
/** /**

View File

@ -2,49 +2,82 @@ import * as vscode from "vscode";
import { ICViewProvider } from "./views/ICViewProvider"; import { ICViewProvider } from "./views/ICViewProvider";
import { showICHelperPanel } from "./panels/ICHelperPanel"; import { showICHelperPanel } from "./panels/ICHelperPanel";
import { VCDViewerPanel, VCDViewerEditorProvider } from "./panels/VCDViewerPanel"; import { VCDViewerPanel, VCDViewerEditorProvider } from "./panels/VCDViewerPanel";
import { UserManualPanel } from "./panels/UserManualPanel";
import { ChatHistoryManager } from "./utils/chatHistoryManager"; import { ChatHistoryManager } from "./utils/chatHistoryManager";
import { ICCoderAuthenticationProvider } from "./services/icCoderAuthProvider"; import { ICCoderAuthenticationProvider } from "./services/icCoderAuthProvider";
import { VCDFileServer } from "./services/vcdFileServer"; import { VCDFileServer } from "./services/vcdFileServer";
import { initUserService } from "./services/userService";
import { initCreditsService } from "./services/creditsService";
import { isTokenExpired } from "./utils/jwtUtils"; import { isTokenExpired } from "./utils/jwtUtils";
import { NotificationService } from "./services/notificationService"; import { NotificationService } from "./services/notificationService";
import { InvitationService } from "./services/invitationService"; import { InvitationService } from "./services/invitationService";
import { ICCoderCodeActionProvider } from "./providers/codeActionProvider";
import { setCustomConfig } from "./config/settings";
export async function activate(context: vscode.ExtensionContext) { export async function activate(context: vscode.ExtensionContext) {
console.log("🎉 IC Coder 插件已激活!"); console.log("🎉 IC Coder 插件已激活!");
// 加载保存的配置
const savedSettings = context.globalState.get('generalSettings') as any;
if (savedSettings?.backendUrl) {
setCustomConfig({
backendUrl: savedSettings.backendUrl,
});
}
// 创建装饰类型(代码旁边的提示)
const decorationType = vscode.window.createTextEditorDecorationType({
after: {
contentText: ' Ctrl+L 添加到 IC Coder 对话',
color: '#888',
fontStyle: 'italic',
margin: '0 0 0 1em'
}
});
// 更新装饰
const updateDecorations = () => {
const editor = vscode.window.activeTextEditor;
if (!editor) return;
if (!editor.selection.isEmpty) {
const range = new vscode.Range(editor.selection.end, editor.selection.end);
const decoration = { range };
editor.setDecorations(decorationType, [decoration]);
} else {
editor.setDecorations(decorationType, []);
}
};
context.subscriptions.push(
vscode.window.onDidChangeTextEditorSelection(updateDecorations),
vscode.window.onDidChangeActiveTextEditor(updateDecorations)
);
updateDecorations();
// 初始化通知服务 // 初始化通知服务
const notificationService = NotificationService.getInstance(context); const notificationService = NotificationService.getInstance(context);
console.log('[Extension] 通知服务已初始化'); console.log('[Extension] 通知服务已初始化');
// 【关键】在创建 AuthProvider 之前,先检查并清除过期的 session // 【已禁用】登录和 token 验证 - 无需登录即可使用
const storedSessions = context.globalState.get<any[]>('icCoderSessions', []); // const storedSessions = context.globalState.get<any[]>('icCoderSessions', []);
console.log('[Extension] 检查 sessions 数量:', storedSessions.length); // console.log('[Extension] 检查 sessions 数量:', storedSessions.length);
//
if (storedSessions.length > 0) { // if (storedSessions.length > 0) {
const session = storedSessions[0]; // const session = storedSessions[0];
const token = session.accessToken; // const token = session.accessToken;
console.log('[Extension] 检查 token 是否过期...'); // console.log('[Extension] 检查 token 是否过期...');
//
if (token) { // if (token) {
const expired = isTokenExpired(token); // const expired = isTokenExpired(token);
console.log('[Extension] token 过期检查结果:', expired); // console.log('[Extension] token 过期检查结果:', expired);
//
if (expired) { // if (expired) {
// 必须等待清除完成后再创建 AuthProvider // await context.globalState.update('icCoderSessions', []);
await context.globalState.update('icCoderSessions', []); // await context.globalState.update('icCoderUserInfo', undefined);
await context.globalState.update('icCoderUserInfo', undefined); // console.log('[Extension] Token 已过期,已清除所有登录状态');
console.log('[Extension] Token 已过期,已清除所有登录状态'); // }
} // }
} // }
}
// 初始化用户服务
initUserService(context);
// 初始化 Credits 服务
initCreditsService(context);
// 初始化 VCD 文件服务器 // 初始化 VCD 文件服务器
const vcdFileServer = new VCDFileServer(context.extensionUri); const vcdFileServer = new VCDFileServer(context.extensionUri);
@ -59,25 +92,18 @@ export async function activate(context: vscode.ExtensionContext) {
dispose: () => vcdFileServer.stop() dispose: () => vcdFileServer.stop()
}); });
// 注册 Authentication Provider(此时 icCoderSessions 已经被清除) // 【已禁用】Authentication Provider 注册 - 无需登录
const authProvider = new ICCoderAuthenticationProvider(context); const authProvider = new ICCoderAuthenticationProvider(context);
context.subscriptions.push( // context.subscriptions.push(
vscode.authentication.registerAuthenticationProvider( // vscode.authentication.registerAuthenticationProvider(
"iccoder", // "iccoder",
"IC Coder", // "IC Coder",
authProvider // authProvider
) // )
); // );
// 检查登录状态,如果已登录则自动打开聊天面板 // 【已禁用】登录状态检查 - 直接打开聊天面板
vscode.authentication.getSession("iccoder", [], { createIfNone: false }) vscode.commands.executeCommand("ic-coder.openChat");
.then((session) => {
if (session) {
vscode.commands.executeCommand("ic-coder.openChat");
}
}, () => {
// 未登录,不做任何操作
});
// 注册命令:打开助手面板 // 注册命令:打开助手面板
const openPanelCommand = vscode.commands.registerCommand( const openPanelCommand = vscode.commands.registerCommand(
@ -156,6 +182,14 @@ export async function activate(context: vscode.ExtensionContext) {
} }
); );
// 注册命令:打开用户手册
const openUserManualCommand = vscode.commands.registerCommand(
"ic-coder.openUserManual",
() => {
UserManualPanel.render(context.extensionUri);
}
);
// 注册命令:用户登录 // 注册命令:用户登录
const loginCommand = vscode.commands.registerCommand( const loginCommand = vscode.commands.registerCommand(
"ic-coder.login", "ic-coder.login",
@ -250,6 +284,81 @@ export async function activate(context: vscode.ExtensionContext) {
} }
); );
// 注册命令:将选中代码添加到对话
const addCodeToChat = vscode.commands.registerCommand(
"ic-coder.addCodeToChat",
async () => {
console.log('[addCodeToChat] 命令触发');
const editor = vscode.window.activeTextEditor;
if (!editor) {
console.log('[addCodeToChat] 没有活动编辑器');
return;
}
const selection = editor.selection;
const selectedText = editor.document.getText(selection);
if (!selectedText) {
vscode.window.showWarningMessage("请先选择代码");
return;
}
const fileName = editor.document.fileName;
const startLine = selection.start.line + 1;
const endLine = selection.end.line + 1;
// 检查是否已有打开的面板
let panel = (global as any).currentICHelperPanel;
let needCreatePanel = false;
if (!panel) {
needCreatePanel = true;
} else {
// 尝试访问 webview如果抛出异常说明已销毁
try {
const _ = panel.webview;
} catch (e) {
needCreatePanel = true;
}
}
console.log('[addCodeToChat] 需要创建面板:', needCreatePanel);
if (needCreatePanel) {
console.log('[addCodeToChat] 正在打开面板...');
await showICHelperPanel(context);
panel = (global as any).currentICHelperPanel;
console.log('[addCodeToChat] 面板打开后状态:', panel ? '成功' : '失败');
// 如果面板仍未创建(如未登录),直接返回
if (!panel) {
console.log('[addCodeToChat] 面板创建失败,退出');
return;
}
}
// 发送代码上下文
console.log('[addCodeToChat] 准备发送代码到面板');
setTimeout(() => {
try {
if (panel?.webview) {
console.log('[addCodeToChat] 发送 addCodeContext 消息');
panel.webview.postMessage({
command: 'addCodeContext',
fileName,
startLine,
endLine,
code: selectedText,
languageId: editor.document.languageId
});
}
} catch (e) {
console.log('[addCodeToChat] 发送消息失败:', e);
}
}, 500);
}
);
// 注册命令:查看会话历史 // 注册命令:查看会话历史
// TODO: 这些命令需要根据新的任务架构重新实现 // TODO: 这些命令需要根据新的任务架构重新实现
// 暂时注释掉,等待重新实现 // 暂时注释掉,等待重新实现
@ -312,16 +421,25 @@ export async function activate(context: vscode.ExtensionContext) {
// 注册 VCD 自定义编辑器 // 注册 VCD 自定义编辑器
const vcdEditorProvider = VCDViewerEditorProvider.register(context, vcdFileServer); const vcdEditorProvider = VCDViewerEditorProvider.register(context, vcdFileServer);
// 注册 Code Action Provider
const codeActionProvider = vscode.languages.registerCodeActionsProvider(
{ scheme: 'file' },
new ICCoderCodeActionProvider(),
{ providedCodeActionKinds: [vscode.CodeActionKind.RefactorRewrite] }
);
// 添加到订阅 // 添加到订阅
context.subscriptions.push( context.subscriptions.push(
openPanelCommand, openPanelCommand,
openChatCommand, openChatCommand,
openVCDViewerCommand, openVCDViewerCommand,
openVCDViewerInBrowserCommand, openVCDViewerInBrowserCommand,
openUserManualCommand,
loginCommand, loginCommand,
logoutCommand, logoutCommand,
changeInvitationCodeCommand, changeInvitationCodeCommand,
testNotificationCommand, testNotificationCommand,
addCodeToChat,
// testTrialUserCommand, // testTrialUserCommand,
// testExpiredUserCommand, // testExpiredUserCommand,
// TODO: 等待重新实现这些命令 // TODO: 等待重新实现这些命令
@ -332,7 +450,8 @@ export async function activate(context: vscode.ExtensionContext) {
// clearHistoryCommand, // clearHistoryCommand,
// searchSessionCommand, // searchSessionCommand,
viewRegistration, viewRegistration,
vcdEditorProvider vcdEditorProvider,
codeActionProvider
); );
} }

View File

@ -18,50 +18,12 @@ import {
startChangeSession, startChangeSession,
handleOpenFileDiff, handleOpenFileDiff,
} from "../utils/messageHandler"; } from "../utils/messageHandler";
import { setCustomConfig } from "../config/settings";
import { compactDialog } from "../services/apiClient"; import { compactDialog } from "../services/apiClient";
import { VCDViewerPanel } from "./VCDViewerPanel"; import { VCDViewerPanel } from "./VCDViewerPanel";
import { ChatHistoryManager } from "../utils/chatHistoryManager"; import { ChatHistoryManager } from "../utils/chatHistoryManager";
import { MessageType } from "../types/chatHistory"; import { MessageType } from "../types/chatHistory";
import { getCachedUserInfo } from "../services/userService";
import { isTokenExpired } from "../utils/jwtUtils"; import { isTokenExpired } from "../utils/jwtUtils";
import { setBalanceUpdateCallback } from "../services/creditsService";
/**
* 获取会员等级图标 URI
*/
function getTierIconUri(
webview: vscode.Webview,
context: vscode.ExtensionContext,
tierCode?: string,
): string | undefined {
if (!tierCode) {
return undefined;
}
const tierIconMap: Record<string, string> = {
BASIC: "free.png",
TRIAL: "PRO-Try.png",
ADVANCED: "PRO.png",
PROFESSIONAL: "PRO+.png",
};
const iconFile = tierIconMap[tierCode];
if (!iconFile) {
return undefined;
}
const iconUri = webview.asWebviewUri(
vscode.Uri.joinPath(
context.extensionUri,
"dist",
"assets",
"titleIcon",
iconFile,
),
);
return iconUri.toString();
}
/** /**
* 创建并显示 IC 助手面板 * 创建并显示 IC 助手面板
@ -70,63 +32,35 @@ export async function showICHelperPanel(
context: vscode.ExtensionContext, context: vscode.ExtensionContext,
viewColumn?: vscode.ViewColumn, viewColumn?: vscode.ViewColumn,
) { ) {
// 检查 token 是否过期 // 创建WebView面板
let token: string | undefined; // try {
try { // const session = await vscode.authentication.getSession("iccoder", [], {
const session = await vscode.authentication.getSession("iccoder", [], { // createIfNone: false,
createIfNone: false, // });
}); // if (!session) {
token = session?.accessToken; // vscode.window
} catch (error) { // .showWarningMessage("请先登录后再使用 IC Coder", "立即登录")
console.warn("[ICHelperPanel] 获取 session 失败:", error); // .then((selection) => {
} // if (selection === "立即登录") {
// vscode.commands.executeCommand("ic-coder.login", {
if (token && isTokenExpired(token)) { // forceReauth: true,
// 清除过期的 session // });
await context.globalState.update("icCoderSessions", []); // }
await context.globalState.update("icCoderUserInfo", undefined); // });
// return;
const action = await vscode.window.showWarningMessage( // }
"登录已过期,请重新登录", // } catch (error) {
"立即登录", // vscode.window
); // .showWarningMessage("请先登录后再使用 IC Coder", "立即登录")
if (action === "立即登录") { // .then((selection) => {
vscode.commands.executeCommand("ic-coder.login", { // if (selection === "立即登录") {
forceReauth: true, // vscode.commands.executeCommand("ic-coder.login", {
}); // forceReauth: true,
} // });
return; // }
} // });
// return;
// 检查用户是否已登录 // }
try {
const session = await vscode.authentication.getSession("iccoder", [], {
createIfNone: false,
});
if (!session) {
vscode.window
.showWarningMessage("请先登录后再使用 IC Coder", "立即登录")
.then((selection) => {
if (selection === "立即登录") {
vscode.commands.executeCommand("ic-coder.login", {
forceReauth: true,
});
}
});
return;
}
} catch (error) {
vscode.window
.showWarningMessage("请先登录后再使用 IC Coder", "立即登录")
.then((selection) => {
if (selection === "立即登录") {
vscode.commands.executeCommand("ic-coder.login", {
forceReauth: true,
});
}
});
return;
}
// 创建WebView面板 // 创建WebView面板
const panel = vscode.window.createWebviewPanel( const panel = vscode.window.createWebviewPanel(
@ -143,6 +77,9 @@ export async function showICHelperPanel(
}, },
); );
// 保存 panel 引用到全局
(global as any).currentICHelperPanel = panel;
// 为面板生成唯一ID // 为面板生成唯一ID
const panelId = `panel_${Date.now()}_${Math.random() const panelId = `panel_${Date.now()}_${Math.random()
.toString(36) .toString(36)
@ -227,81 +164,6 @@ export async function showICHelperPanel(
logoUri.toString(), logoUri.toString(),
); );
// 获取并发送用户信息到 webview
try {
// 优先使用缓存的用户信息
let userInfo = getCachedUserInfo();
if (userInfo) {
// 使用缓存的用户信息
console.log("[ICHelperPanel] 使用缓存的用户信息:", userInfo);
console.log("[ICHelperPanel] Credits 余额:", userInfo.credits);
const tierIconUrl = getTierIconUri(
panel.webview,
context,
userInfo.membership?.tierCode,
);
const messageData = {
command: "updateUserInfo",
userInfo: {
userId: userInfo.userId,
nickname: userInfo.nickname,
username: userInfo.username,
credits: userInfo.credits,
membership: userInfo.membership,
},
tierIconUrl: tierIconUrl,
};
console.log("[ICHelperPanel] 发送用户信息到前端:", messageData);
panel.webview.postMessage(messageData);
} else {
// 如果没有缓存,从 session 中获取
const session = await vscode.authentication.getSession("iccoder", [], {
createIfNone: false,
});
if (session) {
console.log(
"[ICHelperPanel] 从 session 获取用户信息, account:",
session.account,
);
panel.webview.postMessage({
command: "updateUserInfo",
userInfo: {
userId: session.account.id,
nickname: session.account.label,
username: session.account.label,
},
});
}
}
} catch (error) {
console.error("[ICHelperPanel] 获取用户信息失败:", error);
}
// 设置余额更新回调
setBalanceUpdateCallback((balance: number) => {
const userInfo = getCachedUserInfo();
if (userInfo) {
userInfo.credits = balance;
const tierIconUrl = getTierIconUri(
panel.webview,
context,
userInfo.membership?.tierCode,
);
panel.webview.postMessage({
command: "updateUserInfo",
userInfo: {
userId: userInfo.userId,
nickname: userInfo.nickname,
username: userInfo.username,
credits: balance,
membership: userInfo.membership,
},
tierIconUrl: tierIconUrl,
});
}
});
// 检查是否有待发送的消息 // 检查是否有待发送的消息
const pendingMessage = context.globalState.get("pendingMessage") as any; const pendingMessage = context.globalState.get("pendingMessage") as any;
if (pendingMessage) { if (pendingMessage) {
@ -491,6 +353,78 @@ export async function showICHelperPanel(
// 退出登录 // 退出登录
vscode.commands.executeCommand("ic-coder.logout"); vscode.commands.executeCommand("ic-coder.logout");
break; break;
case "openFile":
// 打开文件
if (message.filePath) {
const path = require('path');
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
const fullPath = path.isAbsolute(message.filePath) || !workspaceFolder
? message.filePath
: vscode.Uri.joinPath(workspaceFolder.uri, message.filePath).fsPath;
vscode.workspace.openTextDocument(fullPath).then(doc => {
vscode.window.showTextDocument(doc);
});
}
break;
case "openFileWithSelection":
// 打开文件并选中代码
if (message.filePath) {
const path = require('path');
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
const fullPath = path.isAbsolute(message.filePath) || !workspaceFolder
? message.filePath
: vscode.Uri.joinPath(workspaceFolder.uri, message.filePath).fsPath;
vscode.workspace.openTextDocument(fullPath).then(doc => {
vscode.window.showTextDocument(doc).then(editor => {
const start = new vscode.Position(message.startLine - 1, 0);
const end = new vscode.Position(message.endLine - 1, doc.lineAt(message.endLine - 1).text.length);
editor.selection = new vscode.Selection(start, end);
editor.revealRange(new vscode.Range(start, end));
});
});
}
break;
case "openFilePathTag":
// 打开文件路径标签(智能查找)
if (message.filePath) {
const path = require('path');
const fs = require('fs');
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
let fullPath = message.filePath;
// 如果是相对路径且工作区存在
if (!path.isAbsolute(message.filePath) && workspaceFolder) {
const candidatePath = vscode.Uri.joinPath(workspaceFolder.uri, message.filePath).fsPath;
// 检查文件是否存在
if (fs.existsSync(candidatePath)) {
fullPath = candidatePath;
} else {
// 尝试在工作区中搜索该文件
const fileName = path.basename(message.filePath);
const files = await vscode.workspace.findFiles(`**/${fileName}`, '**/node_modules/**', 1);
if (files.length > 0) {
fullPath = files[0].fsPath;
}
}
}
if (message.startLine && message.endLine) {
vscode.workspace.openTextDocument(fullPath).then(doc => {
vscode.window.showTextDocument(doc).then(editor => {
const start = new vscode.Position(message.startLine - 1, 0);
const end = new vscode.Position(message.endLine - 1, doc.lineAt(message.endLine - 1).text.length);
editor.selection = new vscode.Selection(start, end);
editor.revealRange(new vscode.Range(start, end));
});
});
} else {
vscode.workspace.openTextDocument(fullPath).then(doc => {
vscode.window.showTextDocument(doc);
});
}
}
break;
case "acceptChange": case "acceptChange":
// 采纳变更 // 采纳变更
if (message.changeId) { if (message.changeId) {
@ -510,71 +444,17 @@ export async function showICHelperPanel(
} }
break; break;
case "checkInvitationCode": case "checkInvitationCode":
// 检查邀请码验证状态 // 【已禁用】检查邀请码验证状态 - 现在所有用户都可以直接使用
{ {
// 先检查是否是试用用户 // 直接返回已验证,无需登录和邀请码
const { getCachedUserInfo } = require("../services/userService"); panel.webview.postMessage({
const userInfo = getCachedUserInfo(); command: "invitationCodeStatus",
verified: true,
if (userInfo?.isPluginTrial === true) { });
// 试用用户,跳过邀请码验证,直接返回已验证
console.log("[ICHelperPanel] 试用用户,跳过邀请码验证");
panel.webview.postMessage({
command: "invitationCodeStatus",
verified: true,
});
} else {
// 正式用户,检查邀请码
const {
InvitationService,
} = require("../services/invitationService");
const isVerified = await InvitationService.isVerified(context);
panel.webview.postMessage({
command: "invitationCodeStatus",
verified: isVerified,
});
}
} }
break; break;
case "checkWelcomeModal": case "checkWelcomeModal":
// 检查是否需要显示欢迎弹窗 // 【已禁用】检查是否需要显示欢迎弹窗 - 无需登录,不显示欢迎弹窗
{
console.log("[ICHelperPanel] 收到 checkWelcomeModal 消息");
const userInfo = getCachedUserInfo();
console.log("[ICHelperPanel] 用户信息:", userInfo);
console.log("[ICHelperPanel] isPluginTrial:", userInfo?.isPluginTrial);
console.log("[ICHelperPanel] pluginTrialExpiresAt:", userInfo?.pluginTrialExpiresAt);
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({
command: "showWelcomeModal",
});
} else {
console.log("[ICHelperPanel] 非试用用户");
}
}
break; break;
case "checkTrialExpiration": case "checkTrialExpiration":
// 检查试用期是否过期 // 检查试用期是否过期
@ -589,37 +469,13 @@ export async function showICHelperPanel(
} }
break; break;
case "verifyInvitationCode": case "verifyInvitationCode":
// 验证邀请码 // 【已禁用】验证邀请码 - 无需邀请码验证
{ {
const { // 直接返回验证成功
InvitationService, panel.webview.postMessage({
} = require("../services/invitationService"); command: "invitationCodeVerified",
const result = await InvitationService.verifyCode(message.code); success: true,
});
if (result.success) {
// 验证成功,保存状态
await InvitationService.saveVerificationStatus(
context,
message.code,
);
panel.webview.postMessage({
command: "invitationCodeVerified",
success: true,
});
// 延迟显示欢迎弹窗,确保邀请码弹窗已关闭
setTimeout(() => {
panel.webview.postMessage({
command: "showNdtWelcomeModal",
});
}, 300);
} else {
// 验证失败,返回错误信息
panel.webview.postMessage({
command: "invitationCodeVerified",
success: false,
message: result.message,
});
}
} }
break; break;
case "openICCoder": case "openICCoder":
@ -636,7 +492,7 @@ export async function showICHelperPanel(
break; break;
case "openUserManual": case "openUserManual":
// 打开用户手册 // 打开用户手册
vscode.env.openExternal(vscode.Uri.parse("https://www.iccoder.com")); vscode.commands.executeCommand("ic-coder.openUserManual");
break; break;
case "openUserFeedback": case "openUserFeedback":
// 打开用户反馈二维码弹窗 // 打开用户反馈二维码弹窗
@ -829,6 +685,21 @@ export async function showICHelperPanel(
// 退出登录(前端已有确认对话框) // 退出登录(前端已有确认对话框)
vscode.commands.executeCommand("ic-coder.logout"); vscode.commands.executeCommand("ic-coder.logout");
break; break;
case "saveGeneralSettings":
// 保存通用设置
context.globalState.update('generalSettings', message.settings);
// 更新运行时配置(包括清空)
setCustomConfig({ backendUrl: message.settings.backendUrl || '' });
vscode.window.showInformationMessage('设置已保存');
break;
case "loadGeneralSettings":
// 加载通用设置
const settings = context.globalState.get('generalSettings');
panel.webview.postMessage({
command: 'loadedGeneralSettings',
settings: settings
});
break;
} }
}, },
undefined, undefined,

View File

@ -0,0 +1,181 @@
/**
* 用户手册只读预览面板
*/
import * as vscode from "vscode";
export class UserManualPanel {
public static currentPanel: UserManualPanel | undefined;
private readonly _panel: vscode.WebviewPanel;
private _disposables: vscode.Disposable[] = [];
private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
this._panel = panel;
this._panel.onDidDispose(() => this.dispose(), null, this._disposables);
this._update(extensionUri);
}
public static render(extensionUri: vscode.Uri) {
if (UserManualPanel.currentPanel) {
UserManualPanel.currentPanel._panel.reveal(vscode.ViewColumn.One);
} else {
const panel = vscode.window.createWebviewPanel(
"userManual",
"IC Coder 用户手册",
vscode.ViewColumn.One,
{
enableScripts: true,
localResourceRoots: [vscode.Uri.joinPath(extensionUri, "media")],
},
);
UserManualPanel.currentPanel = new UserManualPanel(panel, extensionUri);
}
}
private async _update(extensionUri: vscode.Uri) {
const manualPath = vscode.Uri.joinPath(
extensionUri,
"media",
"USER_MANUAL.md",
);
const markdown = await vscode.workspace.fs.readFile(manualPath);
const content = Buffer.from(markdown).toString("utf-8");
this._panel.webview.html = await this._getHtmlContent(
content,
extensionUri,
);
}
private async _getHtmlContent(
markdown: string,
extensionUri: vscode.Uri,
): Promise<string> {
let inCodeBlock = false;
let inTable = false;
let tableRows: string[] = [];
const lines: string[] = [];
// 先处理图片
markdown = markdown.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_, alt, src) => {
const imgUri = this._panel.webview.asWebviewUri(
vscode.Uri.joinPath(extensionUri, "media", src),
);
return `<img src="${imgUri}" alt="${alt}">`;
});
markdown.split("\n").forEach((line) => {
// 代码块
if (line.startsWith("```")) {
if (inCodeBlock) {
lines.push("</code></pre>");
inCodeBlock = false;
} else {
lines.push("<pre><code>");
inCodeBlock = true;
}
return;
}
if (inCodeBlock) {
lines.push(line);
return;
}
// 表格
if (line.startsWith("|")) {
if (!inTable) inTable = true;
tableRows.push(line);
return;
} else if (inTable) {
// 表格结束
const headers = tableRows[0]
.split("|")
.filter((c) => c.trim())
.map((h) => `<th>${h.trim()}</th>`)
.join("");
const body = tableRows
.slice(2)
.map(
(r) =>
"<tr>" +
r
.split("|")
.filter((c) => c.trim())
.map((c) => `<td>${c.trim()}</td>`)
.join("") +
"</tr>",
)
.join("");
lines.push(
`<table><thead><tr>${headers}</tr></thead><tbody>${body}</tbody></table>`,
);
tableRows = [];
inTable = false;
}
// 其他行
if (line === "---") lines.push("<hr>");
else if (line.startsWith("#### "))
lines.push(`<h4>${line.slice(5)}</h4>`);
else if (line.startsWith("### ")) lines.push(`<h3>${line.slice(4)}</h3>`);
else if (line.startsWith("## ")) lines.push(`<h2>${line.slice(3)}</h2>`);
else if (line.startsWith("# ")) lines.push(`<h1>${line.slice(2)}</h1>`);
else if (line.startsWith("- "))
lines.push(
`<li>${line.slice(2).replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")}</li>`,
);
else if (line.trim() === "") lines.push("<p></p>");
else
lines.push(
`<p>${line.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>").replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2">$1</a>')}</p>`,
);
});
const html = lines
.join("\n")
.replace(/((?:<li>.*<\/li>\n?)+)/g, "<ul>$1</ul>");
return `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
padding: 40px;
line-height: 1.8;
font-size: 16px;
max-width: 1000px;
margin: 0 auto;
}
h1 { font-size: 2em; border-bottom: 3px solid #ddd; padding-bottom: 15px; margin: 30px 0 20px; }
h2 { font-size: 1.6em; margin-top: 40px; border-bottom: 2px solid #eee; padding-bottom: 10px; }
h3 { font-size: 1.3em; margin-top: 30px; }
h4 { font-size: 1.1em; margin-top: 20px; font-weight: 600; }
p { margin: 15px 0; }
img { display: block; margin: 30px auto; max-width: 100%; border: 1px solid #ddd; border-radius: 6px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
table { border-collapse: collapse; width: 100%; margin: 25px 0; font-size: 15px; }
th, td { border: 1px solid #ddd; padding: 12px 16px; text-align: left; }
th { background: #636363; font-weight: 600; }
tr:hover { background: #636363; }
ul { margin: 15px 0; padding-left: 30px; }
li { margin: 8px 0; margin-left: 40px;}
pre { background: #2f2f2f; padding: 20px; border-radius: 6px; overflow-x: auto; margin: 20px 0; border: 1px solid #e0e0e0; }
code { font-family: 'Consolas', 'Monaco', monospace; font-size: 14px; line-height: 1.6; }
hr { border: none; border-top: 2px solid #e0e0e0; margin: 30px 0; }
a { color: #0066cc; text-decoration: none; }
a:hover { text-decoration: underline; }
strong { font-weight: 600; color: #e5e5e5; }
</style>
</head>
<body>${html}</body>
</html>`;
}
public dispose() {
UserManualPanel.currentPanel = undefined;
this._panel.dispose();
while (this._disposables.length) {
this._disposables.pop()?.dispose();
}
}
}

View File

@ -0,0 +1,26 @@
/**
* Code Action Provider - 为选中代码提供快捷操作
* 功能:在小灯泡菜单中显示"添加到 IC Coder 对话"选项
*/
import * as vscode from 'vscode';
export class ICCoderCodeActionProvider implements vscode.CodeActionProvider {
provideCodeActions(
document: vscode.TextDocument,
range: vscode.Range
): vscode.CodeAction[] {
const selectedText = document.getText(range);
if (!selectedText) return [];
const action = new vscode.CodeAction(
'💬 添加到 IC Coder 对话',
vscode.CodeActionKind.RefactorRewrite
);
action.command = {
command: 'ic-coder.addCodeToChat',
title: '添加到对话'
};
return [action];
}
}

View File

@ -30,7 +30,6 @@ import type {
import { submitToolConfirm, submitAnswer, stopDialog } from "./apiClient"; import { submitToolConfirm, submitAnswer, stopDialog } from "./apiClient";
import { ChatHistoryManager } from "../utils/chatHistoryManager"; import { ChatHistoryManager } from "../utils/chatHistoryManager";
import { getUserIdFromToken, isTokenExpired } from "../utils/jwtUtils"; import { getUserIdFromToken, isTokenExpired } from "../utils/jwtUtils";
import { updateCachedBalance } from "./creditsService";
/** /**
* 消息段落类型 * 消息段落类型
@ -129,6 +128,8 @@ export class DialogSession {
private currentTextSegment: MessageSegment | null = null; private currentTextSegment: MessageSegment | null = null;
private completeCallback: ((segments: MessageSegment[]) => void) | null = private completeCallback: ((segments: MessageSegment[]) => void) | null =
null; // 保存完成回调,用于 abort 时触发 null; // 保存完成回调,用于 abort 时触发
private consecutiveToolErrors = 0; // 连续工具错误计数
private readonly MAX_CONSECUTIVE_ERRORS = 5; // 最大连续错误次数
constructor(extensionPath: string, existingTaskId?: string) { constructor(extensionPath: string, existingTaskId?: string) {
// 支持复用现有 taskId用于 Plan 模式确认后继续执行) // 支持复用现有 taskId用于 Plan 模式确认后继续执行)
@ -444,6 +445,7 @@ export class DialogSession {
const expired = isTokenExpired(session.accessToken); const expired = isTokenExpired(session.accessToken);
if (expired === true) { if (expired === true) {
console.error("[DialogSession] token 已过期,需要重新登录"); console.error("[DialogSession] token 已过期,需要重新登录");
/*
vscode.window vscode.window
.showErrorMessage("登录已过期,请重新登录", "重新登录") .showErrorMessage("登录已过期,请重新登录", "重新登录")
.then((selection) => { .then((selection) => {
@ -453,6 +455,7 @@ export class DialogSession {
}); });
} }
}); });
*/
throw new Error("登录已过期,请重新登录"); throw new Error("登录已过期,请重新登录");
} }
@ -899,6 +902,7 @@ export class DialogSession {
data.message.includes("LOGIN_EXPIRED") || data.message.includes("LOGIN_EXPIRED") ||
data.message.includes("登录状态已过期") data.message.includes("登录状态已过期")
) { ) {
/*
vscode.window vscode.window
.showErrorMessage("登录状态已过期,请重新登录", "重新登录") .showErrorMessage("登录状态已过期,请重新登录", "重新登录")
.then((selection) => { .then((selection) => {
@ -908,6 +912,7 @@ export class DialogSession {
}); });
} }
}); });
*/
// 登录过期错误已处理,不再传递给外部 // 登录过期错误已处理,不再传递给外部
return; return;
} }
@ -1017,8 +1022,9 @@ export class DialogSession {
data.remainingCredits data.remainingCredits
); );
// 更新余额缓存 // 更新余额缓存
updateCachedBalance(data.remainingCredits); // updateCachedBalance(data.remainingCredits);
// 资源点余额低于阈值时弹窗提醒 // 资源点余额低于阈值时弹窗提醒
/*
const LOW_CREDIT_THRESHOLD = 5; const LOW_CREDIT_THRESHOLD = 5;
if (data.remainingCredits < LOW_CREDIT_THRESHOLD) { if (data.remainingCredits < LOW_CREDIT_THRESHOLD) {
vscode.window vscode.window
@ -1030,13 +1036,13 @@ export class DialogSession {
) )
.then((selection) => { .then((selection) => {
if (selection === "去充值") { if (selection === "去充值") {
// 打开充值页面
vscode.env.openExternal( vscode.env.openExternal(
vscode.Uri.parse("https://iccoder.com/recharge") vscode.Uri.parse("https://iccoder.com/memberCenter")
); );
} }
}); });
} }
*/
}, },
onOpen: () => { onOpen: () => {

View File

@ -2,7 +2,6 @@ import * as vscode from "vscode";
import * as http from "http"; import * as http from "http";
import * as path from "path"; import * as path from "path";
import * as fs from "fs"; import * as fs from "fs";
import { onTokenReceived, type UserInfo, clearUserInfo } from "./userService";
import { getConfig } from "../config/settings"; import { getConfig } from "../config/settings";
import { resetInvitationVerification } from "./apiClient"; import { resetInvitationVerification } from "./apiClient";
@ -85,7 +84,7 @@ export class ICCoderAuthenticationProvider
const oldSession = this._sessions[0]; const oldSession = this._sessions[0];
this._sessions = []; this._sessions = [];
await this.saveSessions(); await this.saveSessions();
await clearUserInfo(); // await clearUserInfo();
this._onDidChangeSessions.fire({ this._onDidChangeSessions.fire({
added: [], added: [],
removed: [oldSession], removed: [oldSession],
@ -97,15 +96,15 @@ export class ICCoderAuthenticationProvider
const token = await this.login(); const token = await this.login();
// 获取到 token 后立即调用用户信息接口 // 获取到 token 后立即调用用户信息接口
const userInfo = await onTokenReceived(token); // const userInfo = await onTokenReceived(token);
// 创建会话 // 创建会话
const session: vscode.AuthenticationSession = { const session: vscode.AuthenticationSession = {
id: this.generateSessionId(), id: this.generateSessionId(),
accessToken: token, accessToken: token,
account: { account: {
id: userInfo?.userId || "iccoder-user", id: "user",
label: userInfo?.nickname || userInfo?.username || "IC Coder 用户", label: "IC Coder User",
}, },
scopes: [...scopes], scopes: [...scopes],
}; };
@ -158,7 +157,7 @@ export class ICCoderAuthenticationProvider
await this.saveSessions(); await this.saveSessions();
// 3. 清除用户信息缓存 // 3. 清除用户信息缓存
await clearUserInfo(); // await clearUserInfo();
// 4. 触发会话变化事件 // 4. 触发会话变化事件
this._onDidChangeSessions.fire({ this._onDidChangeSessions.fire({
@ -182,14 +181,14 @@ export class ICCoderAuthenticationProvider
*/ */
async clearSessionsForRelogin(): Promise<void> { async clearSessionsForRelogin(): Promise<void> {
if (this._sessions.length === 0) { if (this._sessions.length === 0) {
await clearUserInfo(); // await clearUserInfo();
return; return;
} }
const removed = [...this._sessions]; const removed = [...this._sessions];
this._sessions = []; this._sessions = [];
await this.saveSessions(); await this.saveSessions();
await clearUserInfo(); // await clearUserInfo();
this._onDidChangeSessions.fire({ this._onDidChangeSessions.fire({
added: [], added: [],

View File

@ -161,7 +161,16 @@ export async function startStreamDialog(
const body = JSON.stringify(request); const body = JSON.stringify(request);
console.log(`[SSE] 开始流式对话: taskId=${request.taskId}, mode=${request.mode}, url=${urlString}`); console.log('[SSE] 请求详情:', {
url: urlString,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'text/event-stream',
hasToken: !!request.token,
},
body: request
});
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const options: http.RequestOptions = { const options: http.RequestOptions = {

View File

@ -6,7 +6,7 @@ import * as vscode from "vscode";
import * as path from "path"; import * as path from "path";
import * as os from "os"; import * as os from "os";
import * as fs from "fs"; import * as fs from "fs";
import { readFileContent, readDirectory } from "../utils/readFiles"; import { readFileContent, readDirectory, listDirectory } from "../utils/readFiles";
import { createOrOverwriteFile } from "../utils/createFiles"; import { createOrOverwriteFile } from "../utils/createFiles";
import { resolveWorkspaceFilePath, showFileDiff } from "../utils/fileDiff"; import { resolveWorkspaceFilePath, showFileDiff } from "../utils/fileDiff";
import { changeTracker } from "./changeTracker"; import { changeTracker } from "./changeTracker";
@ -126,8 +126,9 @@ export async function executeToolCall(
// 提交成功结果 // 提交成功结果
const result = createSuccessResult(callId, resultText); const result = createSuccessResult(callId, resultText);
console.log(`[ToolExecutor] 准备提交结果: ${toolName}, callId=${callId}`);
await submitToolResult(result); await submitToolResult(result);
console.log(`[ToolExecutor] 工具执行成功: ${toolName}, callId=${callId}`); console.log(`[ToolExecutor] 结果提交成功: ${toolName}, callId=${callId}`);
} catch (error) { } catch (error) {
const errorMessage = error instanceof Error ? error.message : "未知错误"; const errorMessage = error instanceof Error ? error.message : "未知错误";
console.error( console.error(
@ -136,8 +137,20 @@ export async function executeToolCall(
); );
// 提交错误结果 // 提交错误结果
const result = createBusinessErrorResult(callId, errorMessage); try {
await submitToolResult(result); const result = createBusinessErrorResult(callId, errorMessage);
console.log(`[ToolExecutor] 准备提交错误结果: ${toolName}, callId=${callId}`);
await submitToolResult(result);
console.log(`[ToolExecutor] 错误结果提交成功: ${toolName}, callId=${callId}`);
} catch (submitError) {
console.error(
`[ToolExecutor] 提交错误结果失败: ${toolName}, callId=${callId}`,
submitError,
);
throw submitError;
}
// 重新抛出原始错误,让调用方知道工具执行失败
throw error;
} }
} }
@ -274,10 +287,8 @@ async function executeFileList(args: FileListArgs): Promise<string> {
const dirPath = args.path || "."; const dirPath = args.path || ".";
const extensions = args.extension ? [args.extension] : undefined; const extensions = args.extension ? [args.extension] : undefined;
const files = await readDirectory(dirPath, extensions); const files = await listDirectory(dirPath, extensions);
const fileList = files.map((f) => f.path).join("\n"); return files.join("\n") || "(目录为空)";
return fileList || "(目录为空)";
} }
/** /**
@ -291,7 +302,7 @@ async function executeSyntaxCheck(
// 检查 iverilog 是否可用 // 检查 iverilog 是否可用
const iverilogCheck = await checkIverilogAvailable(context.extensionPath); const iverilogCheck = await checkIverilogAvailable(context.extensionPath);
if (!iverilogCheck.available) { if (!iverilogCheck.available) {
throw new Error(`iverilog 不可用: ${iverilogCheck.message}`); throw new Error(`IC Coder编译器不可用: ${iverilogCheck.message}`);
} }
// 创建临时文件 // 创建临时文件
@ -372,7 +383,7 @@ async function executeIverilog(
// 检查 iverilog 是否可用 // 检查 iverilog 是否可用
const iverilogCheck = await checkIverilogAvailable(context.extensionPath); const iverilogCheck = await checkIverilogAvailable(context.extensionPath);
if (!iverilogCheck.available) { if (!iverilogCheck.available) {
throw new Error(`iverilog 不可用: ${iverilogCheck.message}`); throw new Error(`IC Coder编译器不可用: ${iverilogCheck.message}`);
} }
// 获取工作目录 // 获取工作目录

View File

@ -335,43 +335,21 @@ export async function onTokenReceived(token: string): Promise<UserInfo | null> {
// 保存到持久化存储 // 保存到持久化存储
await saveUserInfo(userInfo); await saveUserInfo(userInfo);
// 判断是否是插件试用用户 // 【已禁用】试用用户和欢迎弹窗逻辑 - 无需登录
console.log('[UserService] 检查用户类型isPluginTrial:', userInfo.isPluginTrial); // if (userInfo.isPluginTrial === true && userInfo.pluginTrialExpiresAt !== null && userInfo.pluginTrialExpiresAt !== undefined) {
console.log('[UserService] extensionContext 是否存在:', !!extensionContext); // const now = Date.now();
// const isExpired = now >= userInfo.pluginTrialExpiresAt;
if (userInfo.isPluginTrial === true && userInfo.pluginTrialExpiresAt !== null && userInfo.pluginTrialExpiresAt !== undefined) { // if (isExpired) {
// 检查是否过期 // console.log('[UserService] 试用已过期,将显示邀请码弹窗');
const now = Date.now(); // } else {
const isExpired = now >= userInfo.pluginTrialExpiresAt; // const hasWelcomed = extensionContext?.globalState.get('pluginTrialWelcomed');
console.log('[UserService] 试用到期时间:', new Date(userInfo.pluginTrialExpiresAt).toLocaleString()); // if (!hasWelcomed && extensionContext) {
console.log('[UserService] 当前时间:', new Date(now).toLocaleString()); // await extensionContext.globalState.update('showWelcomeModal', true);
console.log('[UserService] 是否过期:', isExpired); // await extensionContext.globalState.update('pluginTrialWelcomed', true);
// console.log('[UserService] ✅ 已设置欢迎弹窗标记 showWelcomeModal=true');
if (isExpired) { // }
// 已过期:显示邀请码弹窗 // }
console.log('[UserService] 试用已过期,将显示邀请码弹窗'); // }
} else {
// 未过期:显示欢迎弹窗
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 {
// isPluginTrial=false 或 enterpriseTrialExpires 为 null显示邀请码弹窗
console.log('[UserService] 非试用用户或无过期时间,将显示邀请码弹窗');
}
return userInfo; return userInfo;
} catch (error) { } catch (error) {

View File

@ -7,7 +7,7 @@ import { promisify } from "util";
function execCommand( function execCommand(
command: string, command: string,
args: string[], args: string[],
options: { cwd: string; env?: any } options: { cwd: string; env?: any },
): Promise<{ stdout: string; stderr: string }> { ): Promise<{ stdout: string; stderr: string }> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// 在 Windows 上,如果路径包含空格,不使用 shell直接用 spawn // 在 Windows 上,如果路径包含空格,不使用 shell直接用 spawn
@ -23,25 +23,25 @@ function execCommand(
let stderr = ""; let stderr = "";
// 在 Windows 上使用 GBK 编码解码输出 // 在 Windows 上使用 GBK 编码解码输出
const encoding = process.platform === 'win32' ? 'gbk' : 'utf8'; const encoding = process.platform === "win32" ? "gbk" : "utf8";
child.stdout.on("data", (data) => { child.stdout.on("data", (data) => {
try { try {
// 尝试使用 iconv-lite 解码(如果可用) // 尝试使用 iconv-lite 解码(如果可用)
const iconv = require('iconv-lite'); const iconv = require("iconv-lite");
stdout += iconv.decode(data, encoding); stdout += iconv.decode(data, encoding);
} catch { } catch {
// 如果 iconv-lite 不可用,使用默认解码 // 如果 iconv-lite 不可用,使用默认解码
stdout += data.toString('utf8'); stdout += data.toString("utf8");
} }
}); });
child.stderr.on("data", (data) => { child.stderr.on("data", (data) => {
try { try {
const iconv = require('iconv-lite'); const iconv = require("iconv-lite");
stderr += iconv.decode(data, encoding); stderr += iconv.decode(data, encoding);
} catch { } catch {
stderr += data.toString('utf8'); stderr += data.toString("utf8");
} }
}); });
@ -93,7 +93,7 @@ export interface VCDGenerationResult {
* 检查项目中的 Verilog 文件完整性 * 检查项目中的 Verilog 文件完整性
*/ */
export async function checkVerilogProject( export async function checkVerilogProject(
projectPath: string projectPath: string,
): Promise<VerilogProjectCheck> { ): Promise<VerilogProjectCheck> {
const result: VerilogProjectCheck = { const result: VerilogProjectCheck = {
isComplete: false, isComplete: false,
@ -164,7 +164,7 @@ export async function checkVerilogProject(
return result; return result;
} catch (error) { } catch (error) {
result.errors.push( result.errors.push(
`检查项目时出错: ${error instanceof Error ? error.message : "未知错误"}` `检查项目时出错: ${error instanceof Error ? error.message : "未知错误"}`,
); );
return result; return result;
} }
@ -201,6 +201,33 @@ async function findVerilogFiles(dir: string): Promise<string[]> {
return verilogFiles; return verilogFiles;
} }
/**
* 递归查找目录下所有 VCD 文件
*/
async function findVcdFilesRecursive(dir: string): Promise<string[]> {
const vcdFiles: string[] = [];
async function searchDir(currentDir: string) {
const dirUri = vscode.Uri.file(currentDir);
const entries = await vscode.workspace.fs.readDirectory(dirUri);
for (const [fileName, fileType] of entries) {
const filePath = path.join(currentDir, fileName);
if (fileType === vscode.FileType.Directory) {
if (!fileName.startsWith(".") && fileName !== "node_modules") {
await searchDir(filePath);
}
} else if (fileType === vscode.FileType.File && fileName.endsWith(".vcd")) {
vcdFiles.push(filePath);
}
}
}
await searchDir(dir);
return vcdFiles;
}
/** /**
* 获取 iverilog 可执行文件路径 * 获取 iverilog 可执行文件路径
*/ */
@ -209,12 +236,30 @@ async function getIverilogPath(extensionPath: string): Promise<string> {
let iverilogBin = ""; let iverilogBin = "";
if (platform === "win32") { if (platform === "win32") {
iverilogBin = path.join(extensionPath, "tools", "iverilog", "bin", "iverilog.exe"); iverilogBin = path.join(
extensionPath,
"tools",
"iverilog",
"bin",
"iverilog.exe",
);
} else if (platform === "darwin") { } else if (platform === "darwin") {
iverilogBin = path.join(extensionPath, "tools", "iverilog", "bin", "iverilog"); iverilogBin = path.join(
extensionPath,
"tools",
"iverilog",
"bin",
"iverilog",
);
} else { } else {
// Linux // Linux
iverilogBin = path.join(extensionPath, "tools", "iverilog", "bin", "iverilog"); iverilogBin = path.join(
extensionPath,
"tools",
"iverilog",
"bin",
"iverilog",
);
} }
// 如果插件包中没有,尝试使用系统安装的 iverilog // 如果插件包中没有,尝试使用系统安装的 iverilog
@ -258,7 +303,7 @@ async function getVvpPath(extensionPath: string): Promise<string> {
*/ */
export async function generateVCD( export async function generateVCD(
projectPath: string, projectPath: string,
extensionPath: string extensionPath: string,
): Promise<VCDGenerationResult> { ): Promise<VCDGenerationResult> {
try { try {
// 1. 检查项目完整性 // 1. 检查项目完整性
@ -286,8 +331,8 @@ export async function generateVCD(
IVERILOG_ROOT: path.join(extensionPath, "tools", "iverilog"), IVERILOG_ROOT: path.join(extensionPath, "tools", "iverilog"),
}; };
// 5. 构建 iverilog 编译参数 // 5. 构建 iverilog 编译参数(启用 SystemVerilog 2012 标准)
const compileArgs = ["-o", outputFile, ...projectCheck.allVerilogFiles]; const compileArgs = ["-g2012", "-o", outputFile, ...projectCheck.allVerilogFiles];
console.log("执行编译命令:", iverilogPath, compileArgs.join(" ")); console.log("执行编译命令:", iverilogPath, compileArgs.join(" "));
console.log("IVERILOG_ROOT:", env.IVERILOG_ROOT); console.log("IVERILOG_ROOT:", env.IVERILOG_ROOT);
@ -299,15 +344,97 @@ export async function generateVCD(
cwd: projectPath, cwd: projectPath,
env: env, env: env,
}); });
console.log("编译成功stdout:", compileResult.stdout);
console.log("编译成功stderr:", compileResult.stderr);
} catch (error: any) { } catch (error: any) {
console.error("编译失败:", error);
return { return {
success: false, success: false,
message: `iverilog 编译失败:\n${error.message}`, message: `IC Coder编译器编译失败:\n${error.message}`,
stderr: error.stderr, stderr: error.stderr,
stdout: error.stdout, stdout: error.stdout,
}; };
} }
// 6.1 检查 .vvp 文件是否生成
const fs = require("fs");
if (!fs.existsSync(outputFile)) {
return {
success: false,
message: `编译未生成 .vvp 文件: ${outputFile}`,
stderr: compileResult.stderr,
stdout: compileResult.stdout,
};
}
console.log("已生成 .vvp 文件:", outputFile);
// 6.5. 删除 shebang 行(修复 Windows 上的 vvp 解析错误)
try {
const vvpContent = fs.readFileSync(outputFile, "utf8");
const lines = vvpContent.split("\n");
if (lines.length > 0 && lines[0].startsWith("#!")) {
const cleanedContent = lines.slice(1).join("\n");
fs.writeFileSync(outputFile, cleanedContent, "utf8");
console.log("已删除 .vvp 文件的 shebang 行");
} else {
console.log(".vvp 文件无 shebang 行,跳过");
}
} catch (error) {
console.error("删除 shebang 失败:", error);
return {
success: false,
message: `处理 .vvp 文件失败: ${error instanceof Error ? error.message : "未知错误"}`,
};
}
// 6.6. 检查并创建 VCD 输出目录,并处理 Windows 路径问题
try {
const tbPath = projectCheck.testbenchFile;
if (tbPath && fs.existsSync(tbPath)) {
const tbContent = fs.readFileSync(tbPath, "utf8");
const dumpfileMatch = tbContent.match(/\$dumpfile\s*\(\s*["']([^"']+)["']\s*\)/);
if (dumpfileMatch) {
const vcdPath = dumpfileMatch[1];
const vcdDir = path.dirname(vcdPath);
console.log(`testbench 中的 VCD 路径: ${vcdPath}`);
if (vcdDir && vcdDir !== "." && vcdDir !== "") {
const vcdDirPath = path.join(projectPath, vcdDir);
console.log(`检查 VCD 目录: ${vcdDirPath}`);
if (!fs.existsSync(vcdDirPath)) {
fs.mkdirSync(vcdDirPath, { recursive: true });
console.log(`已创建 VCD 输出目录: ${vcdDirPath}`);
} else {
console.log(`VCD 目录已存在: ${vcdDirPath}`);
}
// Windows 兼容性:修改 .vvp 文件中的路径,将正斜杠替换为反斜杠
if (process.platform === "win32" && vcdPath.includes("/")) {
const vvpContent = fs.readFileSync(outputFile, "utf8");
const windowsPath = vcdPath.replace(/\//g, "\\\\");
const modifiedContent = vvpContent.replace(
new RegExp(`"${vcdPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"`, 'g'),
`"${windowsPath}"`
);
fs.writeFileSync(outputFile, modifiedContent, "utf8");
console.log(`已修正 VCD 路径: ${vcdPath} -> ${windowsPath}`);
}
} else {
console.log("VCD 文件在根目录,无需创建子目录");
}
} else {
console.warn("testbench 中未找到 $dumpfile 语句");
}
}
} catch (error) {
console.error("处理 VCD 路径失败:", error);
return {
success: false,
message: `处理 VCD 路径失败: ${error instanceof Error ? error.message : "未知错误"}`,
};
}
// 7. 执行仿真生成 VCD // 7. 执行仿真生成 VCD
const simArgs = [outputFile]; const simArgs = [outputFile];
console.log("执行仿真命令:", vvpPath, simArgs.join(" ")); console.log("执行仿真命令:", vvpPath, simArgs.join(" "));
@ -318,7 +445,11 @@ export async function generateVCD(
cwd: projectPath, cwd: projectPath,
env: env, env: env,
}); });
console.log("仿真执行完成");
console.log("仿真 stdout:", simResult.stdout);
console.log("仿真 stderr:", simResult.stderr);
} catch (error: any) { } catch (error: any) {
console.error("仿真失败:", error);
return { return {
success: false, success: false,
message: `VVP 仿真失败:\n${error.message}`, message: `VVP 仿真失败:\n${error.message}`,
@ -328,23 +459,46 @@ export async function generateVCD(
} }
// 8. 查找生成的 VCD 文件 // 8. 查找生成的 VCD 文件
const projectUri = vscode.Uri.file(projectPath); let vcdFile: string | null = null;
const entries = await vscode.workspace.fs.readDirectory(projectUri);
const vcdFiles = entries
.filter(([fileName, fileType]) => fileType === vscode.FileType.File && fileName.endsWith('.vcd'))
.map(([fileName]) => fileName);
if (vcdFiles.length === 0) { // 8.1 尝试从 testbench 中提取 VCD 路径
try {
const fs = require("fs");
const tbPath = projectCheck.testbenchFile;
if (tbPath && fs.existsSync(tbPath)) {
const tbContent = fs.readFileSync(tbPath, "utf8");
const dumpfileMatch = tbContent.match(/\$dumpfile\s*\(\s*["']([^"']+)["']\s*\)/);
if (dumpfileMatch) {
const vcdPath = dumpfileMatch[1];
const absoluteVcdPath = path.join(projectPath, vcdPath);
if (fs.existsSync(absoluteVcdPath)) {
vcdFile = absoluteVcdPath;
console.log(`找到 VCD 文件(从 testbench: ${vcdFile}`);
}
}
}
} catch (error) {
console.warn("从 testbench 提取 VCD 路径失败:", error);
}
// 8.2 如果未找到,递归搜索项目目录
if (!vcdFile) {
const foundFiles = await findVcdFilesRecursive(projectPath);
if (foundFiles.length > 0) {
vcdFile = foundFiles[0];
console.log(`找到 VCD 文件(递归搜索): ${vcdFile}`);
}
}
if (!vcdFile) {
return { return {
success: false, success: false,
message: "VCD 文件未生成。请确保 testbench 中包含 $dumpfile 和 $dumpvars 语句。", message:
"VCD 文件未生成。请确保 testbench 中包含 $dumpfile 和 $dumpvars 语句。",
stdout: simResult.stdout, stdout: simResult.stdout,
}; };
} }
// 使用找到的第一个 VCD 文件
const vcdFile = path.join(projectPath, vcdFiles[0]);
// 9. 清理中间文件 // 9. 清理中间文件
try { try {
const outputUri = vscode.Uri.file(outputFile); const outputUri = vscode.Uri.file(outputFile);
@ -373,7 +527,7 @@ export async function generateVCD(
* 检查 iverilog 是否可用 * 检查 iverilog 是否可用
*/ */
export async function checkIverilogAvailable( export async function checkIverilogAvailable(
extensionPath: string extensionPath: string,
): Promise<{ available: boolean; version?: string; message: string }> { ): Promise<{ available: boolean; version?: string; message: string }> {
try { try {
const iverilogPath = await getIverilogPath(extensionPath); const iverilogPath = await getIverilogPath(extensionPath);
@ -385,7 +539,7 @@ export async function checkIverilogAvailable(
} catch (error) { } catch (error) {
return { return {
available: false, available: false,
message: `iverilog 不可用。未找到文件: ${iverilogPath}`, message: `IC Coder编译器不可用。未找到文件: ${iverilogPath}`,
}; };
} }
@ -404,12 +558,12 @@ export async function checkIverilogAvailable(
return { return {
available: true, available: true,
version: version, version: version,
message: `iverilog 可用: ${version}`, message: `IC Coder编译器可用: ${version}`,
}; };
} catch (error: any) { } catch (error: any) {
return { return {
available: false, available: false,
message: `iverilog 执行失败: ${error.message}\n${error.stderr || ""}`, message: `IC Coder编译器执行失败: ${error.message}\n${error.stderr || ""}`,
}; };
} }
} }
@ -418,8 +572,8 @@ export async function checkIverilogAvailable(
* 要 dump 的模块定义 * 要 dump 的模块定义
*/ */
export interface DumpModule { export interface DumpModule {
name: string; // 模块名(用于 VCD 文件名和宏名) name: string; // 模块名(用于 VCD 文件名和宏名)
path: string; // 实例路径(如 dut.u_tx path: string; // 实例路径(如 dut.u_tx
} }
/** /**
@ -444,10 +598,11 @@ export interface MultiVCDResult {
function injectConditionalDump( function injectConditionalDump(
tbContent: string, tbContent: string,
dumpModules: DumpModule[], dumpModules: DumpModule[],
vcdDir: string vcdDir: string,
): string { ): string {
// 匹配 $dumpfile 和 $dumpvars 语句(可能跨多行) // 匹配 $dumpfile 和 $dumpvars 语句(可能跨多行)
const dumpPattern = /(\$dumpfile\s*\([^)]+\)\s*;[\s\S]*?\$dumpvars\s*\([^)]+\)\s*;)/g; const dumpPattern =
/(\$dumpfile\s*\([^)]+\)\s*;[\s\S]*?\$dumpvars\s*\([^)]+\)\s*;)/g;
// 生成条件编译代码 // 生成条件编译代码
const conditionalCode = generateConditionalDumpCode(dumpModules, vcdDir); const conditionalCode = generateConditionalDumpCode(dumpModules, vcdDir);
@ -469,7 +624,7 @@ function injectConditionalDump(
*/ */
function generateConditionalDumpCode( function generateConditionalDumpCode(
dumpModules: DumpModule[], dumpModules: DumpModule[],
vcdDir: string vcdDir: string,
): string { ): string {
if (dumpModules.length === 0) { if (dumpModules.length === 0) {
return '$dumpfile("output.vcd");\n $dumpvars(0, dut);'; return '$dumpfile("output.vcd");\n $dumpvars(0, dut);';
@ -480,7 +635,7 @@ function generateConditionalDumpCode(
dumpModules.forEach((module, index) => { dumpModules.forEach((module, index) => {
const macroName = `DUMP_${module.name.toUpperCase()}`; const macroName = `DUMP_${module.name.toUpperCase()}`;
const vcdPath = `${vcdDir}/${module.name}.vcd`; const vcdPath = `${vcdDir}/${module.name}.vcd`;
const directive = index === 0 ? '`ifdef' : '`elsif'; const directive = index === 0 ? "`ifdef" : "`elsif";
lines.push(`${directive} ${macroName}`); lines.push(`${directive} ${macroName}`);
lines.push(` $dumpfile("${vcdPath}");`); lines.push(` $dumpfile("${vcdPath}");`);
@ -488,12 +643,12 @@ function generateConditionalDumpCode(
}); });
// 添加默认分支(使用第一个模块) // 添加默认分支(使用第一个模块)
lines.push('`else'); lines.push("`else");
lines.push(` $dumpfile("${vcdDir}/${dumpModules[0].name}.vcd");`); lines.push(` $dumpfile("${vcdDir}/${dumpModules[0].name}.vcd");`);
lines.push(` $dumpvars(1, ${dumpModules[0].path});`); lines.push(` $dumpvars(1, ${dumpModules[0].path});`);
lines.push('`endif'); lines.push("`endif");
return lines.join('\n'); return lines.join("\n");
} }
/** /**
@ -504,10 +659,10 @@ export async function generateMultiVCD(
extensionPath: string, extensionPath: string,
tbPath: string, tbPath: string,
dumpModules: DumpModule[], dumpModules: DumpModule[],
vcdDir: string = 'vcd' vcdDir: string = "vcd",
): Promise<MultiVCDResult> { ): Promise<MultiVCDResult> {
const results: MultiVCDResult['vcdFiles'] = []; const results: MultiVCDResult["vcdFiles"] = [];
let allStdout = ''; let allStdout = "";
try { try {
// 1. 创建 vcd 目录 // 1. 创建 vcd 目录
@ -520,16 +675,21 @@ export async function generateMultiVCD(
} }
// 2. 读取原始 testbench // 2. 读取原始 testbench
const tbFullPath = path.isAbsolute(tbPath) ? tbPath : path.join(projectPath, tbPath); const tbFullPath = path.isAbsolute(tbPath)
? tbPath
: path.join(projectPath, tbPath);
const tbUri = vscode.Uri.file(tbFullPath); const tbUri = vscode.Uri.file(tbFullPath);
const tbBytes = await vscode.workspace.fs.readFile(tbUri); const tbBytes = await vscode.workspace.fs.readFile(tbUri);
const originalTb = Buffer.from(tbBytes).toString('utf-8'); const originalTb = Buffer.from(tbBytes).toString("utf-8");
// 3. 注入条件编译代码 // 3. 注入条件编译代码
const modifiedTb = injectConditionalDump(originalTb, dumpModules, vcdDir); const modifiedTb = injectConditionalDump(originalTb, dumpModules, vcdDir);
await vscode.workspace.fs.writeFile(tbUri, Buffer.from(modifiedTb, 'utf-8')); await vscode.workspace.fs.writeFile(
tbUri,
Buffer.from(modifiedTb, "utf-8"),
);
console.log('[generateMultiVCD] Testbench 已修改,开始多次仿真...'); console.log("[generateMultiVCD] Testbench 已修改,开始多次仿真...");
// 4. 获取工具路径 // 4. 获取工具路径
const iverilogPath = await getIverilogPath(extensionPath); const iverilogPath = await getIverilogPath(extensionPath);
@ -551,30 +711,38 @@ export async function generateMultiVCD(
console.log(`[generateMultiVCD] 仿真模块: ${module.name} (${macroName})`); console.log(`[generateMultiVCD] 仿真模块: ${module.name} (${macroName})`);
try { try {
// 编译(带宏定义) // 编译(带宏定义,启用 SystemVerilog 2012 标准
const compileArgs = [ const compileArgs = [
"-g2012",
`-D${macroName}`, `-D${macroName}`,
"-o", outputFile, "-o",
...projectCheck.allVerilogFiles outputFile,
...projectCheck.allVerilogFiles,
]; ];
await execCommand(iverilogPath, compileArgs, { cwd: projectPath, env }); await execCommand(iverilogPath, compileArgs, { cwd: projectPath, env });
// 仿真 // 仿真
const simResult = await execCommand(vvpPath, [outputFile], { cwd: projectPath, env }); const simResult = await execCommand(vvpPath, [outputFile], {
cwd: projectPath,
env,
});
allStdout += `\n[${module.name}] ${simResult.stdout}`; allStdout += `\n[${module.name}] ${simResult.stdout}`;
results.push({ results.push({
moduleName: module.name, moduleName: module.name,
vcdPath: vcdPath, vcdPath: vcdPath,
success: true success: true,
}); });
} catch (error: any) { } catch (error: any) {
console.error(`[generateMultiVCD] 模块 ${module.name} 仿真失败:`, error.message); console.error(
`[generateMultiVCD] 模块 ${module.name} 仿真失败:`,
error.message,
);
results.push({ results.push({
moduleName: module.name, moduleName: module.name,
vcdPath: vcdPath, vcdPath: vcdPath,
success: false, success: false,
error: error.message error: error.message,
}); });
// 继续执行其他模块 // 继续执行其他模块
} }
@ -587,19 +755,18 @@ export async function generateMultiVCD(
// 忽略 // 忽略
} }
const successCount = results.filter(r => r.success).length; const successCount = results.filter((r) => r.success).length;
return { return {
success: successCount > 0, success: successCount > 0,
vcdFiles: results, vcdFiles: results,
message: `生成完成:${successCount}/${dumpModules.length} 个 VCD 文件成功`, message: `生成完成:${successCount}/${dumpModules.length} 个 VCD 文件成功`,
stdout: allStdout stdout: allStdout,
}; };
} catch (error) { } catch (error) {
return { return {
success: false, success: false,
vcdFiles: results, vcdFiles: results,
message: `生成多 VCD 文件失败: ${error instanceof Error ? error.message : '未知错误'}` message: `生成多 VCD 文件失败: ${error instanceof Error ? error.message : "未知错误"}`,
}; };
} }
} }

View File

@ -19,10 +19,6 @@ import { dialogManager, DialogSession } from "../services/dialogService";
import { userInteractionManager } from "../services/userInteraction"; import { userInteractionManager } from "../services/userInteraction";
import { healthCheck } from "../services/apiClient"; import { healthCheck } from "../services/apiClient";
import { isTokenExpired } from "./jwtUtils"; import { isTokenExpired } from "./jwtUtils";
import {
checkBalanceBeforeSend,
fetchBalance,
} from "../services/creditsService";
import { optimizePrompt } from "../services/promptOptimizeService"; import { optimizePrompt } from "../services/promptOptimizeService";
import { NotificationService } from "../services/notificationService"; import { NotificationService } from "../services/notificationService";
import { TrialExpirationService } from "../services/trialExpirationService"; import { TrialExpirationService } from "../services/trialExpirationService";
@ -41,7 +37,14 @@ let currentSession: DialogSession | null = null;
/** 最后一个活跃的 taskId用于压缩等操作 */ /** 最后一个活跃的 taskId用于压缩等操作 */
let lastTaskId: string | null = null; let lastTaskId: string | null = null;
async function trackFileChange(filePath: string, oldContent: string, newContent: string): Promise<void> { /** 离线模式仿真模拟标志(防止重复触发) */
let offlineSimulationTriggered = false;
async function trackFileChange(
filePath: string,
oldContent: string,
newContent: string,
): Promise<void> {
try { try {
changeTracker.trackChange(filePath, oldContent, newContent); changeTracker.trackChange(filePath, oldContent, newContent);
} catch (error) { } catch (error) {
@ -58,17 +61,19 @@ export async function handleUserMessage(
extensionPath?: string, extensionPath?: string,
mode?: RunMode, mode?: RunMode,
serviceTier?: ServiceTier, // 服务等级参数 serviceTier?: ServiceTier, // 服务等级参数
contextItems?: Array<{ id: number; type: string; path: string }> // 上下文项参数 contextItems?: Array<{ id: number; type: string; path: string }>, // 上下文项参数
) { ) {
console.log("收到用户消息:", text); console.log("收到用户消息:", text);
// 检查 token 是否过期 // 【已禁用】检查 token 是否过期 - 无需登录
const context = (panel as any).__context; const context = (panel as any).__context;
if (context) { if (false && context) {
// 从 session 中获取 token // 从 session 中获取 token
let token: string | undefined; let token: string | undefined;
try { try {
const session = await vscode.authentication.getSession("iccoder", [], { createIfNone: false }); const session = await vscode.authentication.getSession("iccoder", [], {
createIfNone: false,
});
token = session?.accessToken; token = session?.accessToken;
} catch (error) { } catch (error) {
console.warn("[MessageHandler] 获取 session 失败:", error); console.warn("[MessageHandler] 获取 session 失败:", error);
@ -78,20 +83,20 @@ export async function handleUserMessage(
console.warn("[MessageHandler] 未登录,阻止发送"); console.warn("[MessageHandler] 未登录,阻止发送");
// 保存待发送的消息 // 保存待发送的消息
await context.globalState.update('pendingMessage', { await context.globalState.update("pendingMessage", {
text, text,
mode, mode,
serviceTier, serviceTier,
timestamp: Date.now() timestamp: Date.now(),
}); });
// 显示弹窗提示 // 显示弹窗提示
const action = await vscode.window.showWarningMessage( const action = await vscode.window.showWarningMessage(
'请先登录后再发送消息', "请先登录后再发送消息",
'立即登录' "立即登录",
); );
if (action === '立即登录') { if (action === "立即登录") {
vscode.commands.executeCommand("ic-coder.login", { vscode.commands.executeCommand("ic-coder.login", {
forceReauth: true, forceReauth: true,
}); });
@ -106,32 +111,34 @@ export async function handleUserMessage(
return; return;
} }
if (isTokenExpired(token)) { if (token && isTokenExpired(token as string)) {
console.warn("[MessageHandler] Token 已过期,阻止发送"); console.warn("[MessageHandler] Token 已过期,阻止发送");
// 保存待发送的消息 // 保存待发送的消息
await context.globalState.update('pendingMessage', { await context.globalState.update("pendingMessage", {
text, text,
mode, mode,
serviceTier, serviceTier,
timestamp: Date.now() timestamp: Date.now(),
}); });
// 清除过期的 session // 清除过期的 session
await context.globalState.update('icCoderSessions', []); await context.globalState.update("icCoderSessions", []);
await context.globalState.update('icCoderUserInfo', undefined); await context.globalState.update("icCoderUserInfo", undefined);
// 显示弹窗提示 // 显示弹窗提示
/*
const action = await vscode.window.showWarningMessage( const action = await vscode.window.showWarningMessage(
'登录已过期,请重新登录', "登录已过期,请重新登录",
'立即登录' "立即登录",
); );
if (action === '立即登录') { if (action === "立即登录") {
vscode.commands.executeCommand("ic-coder.login", { vscode.commands.executeCommand("ic-coder.login", {
forceReauth: true, forceReauth: true,
}); });
} }
*/
// 恢复输入状态 // 恢复输入状态
panel.webview.postMessage({ panel.webview.postMessage({
@ -183,29 +190,6 @@ export async function handleUserMessage(
return; return;
} }
// 发送前检测余额
const balanceCheck = await checkBalanceBeforeSend();
if (!balanceCheck.allowed) {
console.warn("[MessageHandler] 余额不足,阻止发送:", balanceCheck.message);
// 显示错误提示
const selection = await vscode.window.showWarningMessage(
balanceCheck.message || "资源点余额不足",
"去充值"
);
if (selection === "去充值") {
vscode.env.openExternal(
vscode.Uri.parse("https://iccoder.com/memberCenter")
);
}
// 恢复输入状态
panel.webview.postMessage({
command: "updateSegments",
segments: [],
isComplete: true,
});
return;
}
// 尝试使用后端服务 // 尝试使用后端服务
if (useBackendService && extensionPath) { if (useBackendService && extensionPath) {
try { try {
@ -216,14 +200,14 @@ export async function handleUserMessage(
mode, mode,
undefined, undefined,
serviceTier, serviceTier,
contextItems contextItems,
); );
return; return;
} catch (error) { } catch (error) {
console.error("后端服务不可用:", error); console.error("处理用户消息失败:", error);
panel.webview.postMessage({ panel.webview.postMessage({
command: "updateStatus", command: "updateStatus",
text: "后端服务不可用", text: "处理用户消息失败,请稍后重试",
type: "error", type: "error",
}); });
// 恢复输入状态 // 恢复输入状态
@ -254,7 +238,7 @@ async function handleUserMessageWithBackend(
mode?: RunMode, mode?: RunMode,
reuseTaskId?: string, // 可选,复用现有 taskId用于 Plan 模式确认后继续执行) reuseTaskId?: string, // 可选,复用现有 taskId用于 Plan 模式确认后继续执行)
serviceTier?: ServiceTier, // 服务等级参数 serviceTier?: ServiceTier, // 服务等级参数
contextItems?: Array<{ id: number; type: string; path: string }> // 上下文项参数 contextItems?: Array<{ id: number; type: string; path: string }>, // 上下文项参数
): Promise<void> { ): Promise<void> {
const historyManager = ChatHistoryManager.getInstance(); const historyManager = ChatHistoryManager.getInstance();
@ -262,7 +246,7 @@ async function handleUserMessageWithBackend(
let enhancedText = text; let enhancedText = text;
if (contextItems && contextItems.length > 0) { if (contextItems && contextItems.length > 0) {
console.log("[MessageHandler] 处理上下文项:", contextItems.length); console.log("[MessageHandler] 处理上下文项:", contextItems.length);
const paths = contextItems.map(item => item.path).join('\n'); const paths = contextItems.map((item) => item.path).join("\n");
enhancedText = `${paths}\n\n${text}`; enhancedText = `${paths}\n\n${text}`;
} }
@ -273,15 +257,17 @@ async function handleUserMessageWithBackend(
// 创建会话dialogManager 会自动处理旧会话的中止) // 创建会话dialogManager 会自动处理旧会话的中止)
currentSession = dialogManager.createSession( currentSession = dialogManager.createSession(
extensionPath, extensionPath,
taskIdToUse || undefined taskIdToUse || undefined,
); );
// 保存 taskId 用于后续操作(如压缩) // 保存 taskId 用于后续操作(如压缩)
lastTaskId = currentSession.getTaskId(); lastTaskId = currentSession.getTaskId();
// 重置离线模式仿真标志(新会话开始)
offlineSimulationTriggered = false;
console.log( console.log(
"[MessageHandler] 创建会话: taskId=", "[MessageHandler] 创建会话: taskId=",
lastTaskId, lastTaskId,
"来源=", "来源=",
taskIdToUse ? "historyManager" : "新生成" taskIdToUse ? "historyManager" : "新生成",
); );
// 显示状态栏 // 显示状态栏
@ -300,11 +286,58 @@ async function handleUserMessageWithBackend(
}, },
onSegmentUpdate: (segments) => { onSegmentUpdate: (segments) => {
// 过滤掉包含 [调用工具:xxx] 的段落
const filteredSegments = segments.filter(seg => {
if (seg.type === 'text' && typeof seg.content === 'string') {
return !/\[调用工具:.+?\]/.test(seg.content);
}
return true;
});
// 实时发送段落更新,按后端返回顺序展示 // 实时发送段落更新,按后端返回顺序展示
panel.webview.postMessage({ panel.webview.postMessage({
command: "updateSegments", command: "updateSegments",
segments: segments, segments: filteredSegments,
}); });
// 【离线部署模式】检测代码生成完成消息,模拟仿真流程
if (!offlineSimulationTriggered) {
const hasCompletionMessage = segments.some(seg =>
seg.type === 'text' &&
seg.content?.includes('【代码生成完成】') &&
seg.content?.includes('语法检查:已通过')
);
if (hasCompletionMessage) {
offlineSimulationTriggered = true;
console.log('[离线模式] 检测到代码生成完成,开始模拟仿真流程');
// 立即点亮 Simulation 阶段
panel.webview.postMessage({
type: "updateProgress",
step: "simulation"
});
// 随机延时 8-13 秒后完成仿真
const simulationDelay = 8000 + Math.random() * 5000;
setTimeout(() => {
console.log('[离线模式] 模拟仿真完成,进入 Done 阶段');
// Simulation 完成,进入 Done
panel.webview.postMessage({
type: "updateProgress",
step: "done"
});
// 再延时 1 秒完成所有步骤
setTimeout(() => {
console.log('[离线模式] 所有阶段完成');
panel.webview.postMessage({
type: "completeProgress"
});
}, 1000);
}, simulationDelay);
}
}
}, },
onToolStart: (toolName) => { onToolStart: (toolName) => {
@ -324,7 +357,10 @@ async function handleUserMessageWithBackend(
// 工具错误,不需要单独处理,通过 onSegmentUpdate 统一更新 // 工具错误,不需要单独处理,通过 onSegmentUpdate 统一更新
}, },
onQuestion: (askId: string, questions: import("../types/api").QuestionItem[]) => { onQuestion: (
askId: string,
questions: import("../types/api").QuestionItem[],
) => {
// 只更新状态栏,问题显示由 onSegmentUpdate 统一处理 // 只更新状态栏,问题显示由 onSegmentUpdate 统一处理
panel.webview.postMessage({ panel.webview.postMessage({
command: "updateStatus", command: "updateStatus",
@ -351,17 +387,6 @@ async function handleUserMessageWithBackend(
console.error("[MessageHandler] 保存AI响应历史失败:", error); console.error("[MessageHandler] 保存AI响应历史失败:", error);
} }
// 对话完成后重新获取余额(因为已经消耗了 Credits
try {
console.log("[MessageHandler] 对话完成,重新获取余额...");
const newBalance = await fetchBalance();
if (newBalance !== null) {
console.log("[MessageHandler] 余额已更新:", newBalance);
}
} catch (error) {
console.error("[MessageHandler] 获取余额失败:", error);
}
// 尝试更新面板(如果面板已关闭,这些操作会失败,但不影响数据保存) // 尝试更新面板(如果面板已关闭,这些操作会失败,但不影响数据保存)
try { try {
// 隐藏状态栏 // 隐藏状态栏
@ -376,21 +401,29 @@ async function handleUserMessageWithBackend(
isComplete: true, isComplete: true,
}); });
// 发送任务完成消息
panel.webview.postMessage({
command: "taskComplete",
});
// 发送系统通知 - AI 响应完成 // 发送系统通知 - AI 响应完成
const notificationService = NotificationService.getInstance(); const notificationService = NotificationService.getInstance();
notificationService.success( notificationService.success(
'IC Coder - AI 响应完成', "IC Coder - AI 响应完成",
'您的问题已得到回复,点击查看详情', "您的问题已得到回复,点击查看详情",
() => { () => {
// 点击通知时聚焦到面板 // 点击通知时聚焦到面板
panel.reveal(); panel.reveal();
} },
); );
// 发送代码变更到前端 // 发送代码变更到前端
sendChangesToWebview(panel); sendChangesToWebview(panel);
} catch (error) { } catch (error) {
console.warn("[MessageHandler] 更新面板失败(面板可能已关闭):", error); console.warn(
"[MessageHandler] 更新面板失败(面板可能已关闭):",
error,
);
} }
resolve(); resolve();
@ -402,7 +435,7 @@ async function handleUserMessageWithBackend(
}); });
panel.webview.postMessage({ panel.webview.postMessage({
command: "receiveMessage", command: "receiveMessage",
text: `错误: ${message}`, text: `错误: ${message}`,
}); });
// 恢复输入状态 // 恢复输入状态
panel.webview.postMessage({ panel.webview.postMessage({
@ -458,7 +491,7 @@ async function handleUserMessageWithBackend(
}, },
}, },
mode, mode,
serviceTier // 传递服务等级 serviceTier, // 传递服务等级
); );
}); });
} }
@ -470,7 +503,7 @@ export async function handleUserAnswer(
askId: string, askId: string,
selected?: string[], selected?: string[],
customInput?: string, customInput?: string,
answers?: { [questionIndex: string]: string[] } answers?: { [questionIndex: string]: string[] },
): Promise<void> { ): Promise<void> {
if (currentSession) { if (currentSession) {
await currentSession.submitAnswer(askId, selected, customInput, answers); await currentSession.submitAnswer(askId, selected, customInput, answers);
@ -540,7 +573,7 @@ export async function handlePlanAction(
action: string, action: string,
planTitle: string, planTitle: string,
extensionPath: string, extensionPath: string,
serviceTier?: ServiceTier serviceTier?: ServiceTier,
): Promise<void> { ): Promise<void> {
console.log( console.log(
"[handlePlanAction] action:", "[handlePlanAction] action:",
@ -548,7 +581,7 @@ export async function handlePlanAction(
"planTitle:", "planTitle:",
planTitle, planTitle,
"serviceTier:", "serviceTier:",
serviceTier serviceTier,
); );
switch (action) { switch (action) {
@ -564,7 +597,7 @@ export async function handlePlanAction(
`请按照刚才的计划执行:${planTitle}`, `请按照刚才的计划执行:${planTitle}`,
extensionPath, extensionPath,
"agent", "agent",
serviceTier serviceTier,
); );
break; break;
@ -581,7 +614,7 @@ export async function handlePlanAction(
`请根据以下建议修改计划:${modification}`, `请根据以下建议修改计划:${modification}`,
extensionPath, extensionPath,
"plan", "plan",
serviceTier serviceTier,
); );
} }
break; break;
@ -636,7 +669,7 @@ function parseFileOperation(text: string): {
// 匹配重命名文件:将 xxx.ts 重命名为 yyy.ts 或 把 xxx.ts 改名为 yyy.ts优先匹配避免被修改匹配 // 匹配重命名文件:将 xxx.ts 重命名为 yyy.ts 或 把 xxx.ts 改名为 yyy.ts优先匹配避免被修改匹配
const renameMatch = lowerText.match( const renameMatch = lowerText.match(
/(?:将|把)\s*(.+?\.\w+)\s*(?:重命名|改名)\s*(?:为|成)\s*(.+?\.\w+)/ /(?:将|把)\s*(.+?\.\w+)\s*(?:重命名|改名)\s*(?:为|成)\s*(.+?\.\w+)/,
); );
if (renameMatch) { if (renameMatch) {
const oldPath = renameMatch[1].trim(); const oldPath = renameMatch[1].trim();
@ -653,7 +686,7 @@ function parseFileOperation(text: string): {
// 格式2: 将 xxx.ts 文件 "aaa" 替换为 "bbb" // 格式2: 将 xxx.ts 文件 "aaa" 替换为 "bbb"
// 格式3: 将 xxx.ts 文件 'aaa' 替换为 'bbb' // 格式3: 将 xxx.ts 文件 'aaa' 替换为 'bbb'
const replaceMatch1 = lowerText.match( const replaceMatch1 = lowerText.match(
/在\s*(.+?\.\w+)\s*(?:文件)?中?\s*(?:将|把)\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/ /在\s*(.+?\.\w+)\s*(?:文件)?中?\s*(?:将|把)\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/,
); );
if (replaceMatch1) { if (replaceMatch1) {
const filePath = replaceMatch1[1].trim(); const filePath = replaceMatch1[1].trim();
@ -669,7 +702,7 @@ function parseFileOperation(text: string): {
// 格式2: 将 xxx.ts 文件 "aaa" 替换为 "bbb" // 格式2: 将 xxx.ts 文件 "aaa" 替换为 "bbb"
const replaceMatch2 = lowerText.match( const replaceMatch2 = lowerText.match(
/(?:将|把)\s*(.+?\.\w+)\s*(?:文件)?\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/ /(?:将|把)\s*(.+?\.\w+)\s*(?:文件)?\s*["'](.+?)["']\s*替换\s*(?:为|成)\s*["'](.+?)["']/,
); );
if (replaceMatch2) { if (replaceMatch2) {
const filePath = replaceMatch2[1].trim(); const filePath = replaceMatch2[1].trim();
@ -718,7 +751,7 @@ async function handleFileOperation(
newPath?: string; newPath?: string;
searchText?: string; searchText?: string;
replaceText?: string; replaceText?: string;
} },
) { ) {
const historyManager = ChatHistoryManager.getInstance(); const historyManager = ChatHistoryManager.getInstance();
@ -734,7 +767,7 @@ async function handleFileOperation(
text: responseText, text: responseText,
}); });
vscode.window.showInformationMessage( vscode.window.showInformationMessage(
`文件创建成功: ${operation.filePath}` `文件创建成功: ${operation.filePath}`,
); );
await historyManager.addAiMessage(responseText); await historyManager.addAiMessage(responseText);
break; break;
@ -747,7 +780,7 @@ async function handleFileOperation(
text: responseText, text: responseText,
}); });
vscode.window.showInformationMessage( vscode.window.showInformationMessage(
`文件删除成功: ${operation.filePath}` `文件删除成功: ${operation.filePath}`,
); );
await historyManager.addAiMessage(responseText); await historyManager.addAiMessage(responseText);
break; break;
@ -783,7 +816,7 @@ async function handleFileOperation(
text: responseText, text: responseText,
}); });
vscode.window.showInformationMessage( vscode.window.showInformationMessage(
`文件重命名成功: ${operation.filePath}${operation.newPath}` `文件重命名成功: ${operation.filePath}${operation.newPath}`,
); );
await historyManager.addAiMessage(responseText); await historyManager.addAiMessage(responseText);
break; break;
@ -792,21 +825,29 @@ async function handleFileOperation(
if (!operation.searchText || !operation.replaceText) { if (!operation.searchText || !operation.replaceText) {
throw new Error("缺少替换内容"); throw new Error("缺少替换内容");
} }
const oldContentBeforeReplace = await readFileContent(operation.filePath); const oldContentBeforeReplace = await readFileContent(
operation.filePath,
);
await replaceFile( await replaceFile(
operation.filePath, operation.filePath,
operation.searchText, operation.searchText,
operation.replaceText operation.replaceText,
);
const newContentAfterReplace = await readFileContent(
operation.filePath,
);
await trackFileChange(
operation.filePath,
oldContentBeforeReplace,
newContentAfterReplace,
); );
const newContentAfterReplace = await readFileContent(operation.filePath);
await trackFileChange(operation.filePath, oldContentBeforeReplace, newContentAfterReplace);
responseText = `✅ 文件内容替换成功: ${operation.filePath}`; responseText = `✅ 文件内容替换成功: ${operation.filePath}`;
panel.webview.postMessage({ panel.webview.postMessage({
command: "receiveMessage", command: "receiveMessage",
text: responseText, text: responseText,
}); });
vscode.window.showInformationMessage( vscode.window.showInformationMessage(
`文件内容替换成功: ${operation.filePath}` `文件内容替换成功: ${operation.filePath}`,
); );
await historyManager.addAiMessage(responseText); await historyManager.addAiMessage(responseText);
break; break;
@ -866,7 +907,7 @@ function getDefaultContent(filePath: string): string {
*/ */
export async function handleReadFile( export async function handleReadFile(
panel: vscode.WebviewPanel, panel: vscode.WebviewPanel,
filePath: string filePath: string,
) { ) {
try { try {
const content = await readFileContent(filePath); const content = await readFileContent(filePath);
@ -890,7 +931,7 @@ export async function handleCreateFile(
panel: vscode.WebviewPanel, panel: vscode.WebviewPanel,
filePath: string, filePath: string,
content: string, content: string,
overwrite: boolean = false //是否覆盖 overwrite: boolean = false, //是否覆盖
) { ) {
try { try {
if (overwrite) { if (overwrite) {
@ -909,11 +950,14 @@ export async function handleCreateFile(
// 发送系统通知 // 发送系统通知
const notificationService = NotificationService.getInstance(); const notificationService = NotificationService.getInstance();
notificationService.success( notificationService.success(
'IC Coder - 文件创建', "IC Coder - 文件创建",
`文件已创建: ${path.basename(filePath)}`, `文件已创建: ${path.basename(filePath)}`,
() => { () => {
vscode.commands.executeCommand('vscode.open', vscode.Uri.file(filePath)); vscode.commands.executeCommand(
} "vscode.open",
vscode.Uri.file(filePath),
);
},
); );
} catch (error) { } catch (error) {
panel.webview.postMessage({ panel.webview.postMessage({
@ -921,7 +965,7 @@ export async function handleCreateFile(
error: error instanceof Error ? error.message : "创建文件失败", error: error instanceof Error ? error.message : "创建文件失败",
}); });
vscode.window.showErrorMessage( vscode.window.showErrorMessage(
`创建文件失败: ${error instanceof Error ? error.message : "未知错误"}` `创建文件失败: ${error instanceof Error ? error.message : "未知错误"}`,
); );
} }
} }
@ -932,7 +976,7 @@ export async function handleCreateFile(
export async function handleUpdateFile( export async function handleUpdateFile(
panel: vscode.WebviewPanel, panel: vscode.WebviewPanel,
filePath: string, filePath: string,
content: string content: string,
) { ) {
try { try {
const oldContent = await readFileContent(filePath); const oldContent = await readFileContent(filePath);
@ -948,8 +992,8 @@ export async function handleUpdateFile(
// 发送系统通知 // 发送系统通知
const notificationService = NotificationService.getInstance(); const notificationService = NotificationService.getInstance();
notificationService.info( notificationService.info(
'IC Coder - 文件更新', "IC Coder - 文件更新",
`文件已更新: ${path.basename(filePath)}` `文件已更新: ${path.basename(filePath)}`,
); );
} catch (error) { } catch (error) {
panel.webview.postMessage({ panel.webview.postMessage({
@ -957,7 +1001,7 @@ export async function handleUpdateFile(
error: error instanceof Error ? error.message : "更新文件失败", error: error instanceof Error ? error.message : "更新文件失败",
}); });
vscode.window.showErrorMessage( vscode.window.showErrorMessage(
`更新文件失败: ${error instanceof Error ? error.message : "未知错误"}` `更新文件失败: ${error instanceof Error ? error.message : "未知错误"}`,
); );
} }
} }
@ -968,7 +1012,7 @@ export async function handleUpdateFile(
export async function handleRenameFile( export async function handleRenameFile(
panel: vscode.WebviewPanel, panel: vscode.WebviewPanel,
oldPath: string, oldPath: string,
newPath: string newPath: string,
) { ) {
try { try {
await renameFile(oldPath, newPath); await renameFile(oldPath, newPath);
@ -979,7 +1023,7 @@ export async function handleRenameFile(
message: "文件重命名成功", message: "文件重命名成功",
}); });
vscode.window.showInformationMessage( vscode.window.showInformationMessage(
`文件重命名成功: ${oldPath}${newPath}` `文件重命名成功: ${oldPath}${newPath}`,
); );
} catch (error) { } catch (error) {
panel.webview.postMessage({ panel.webview.postMessage({
@ -987,7 +1031,7 @@ export async function handleRenameFile(
error: error instanceof Error ? error.message : "重命名文件失败", error: error instanceof Error ? error.message : "重命名文件失败",
}); });
vscode.window.showErrorMessage( vscode.window.showErrorMessage(
`重命名文件失败: ${error instanceof Error ? error.message : "未知错误"}` `重命名文件失败: ${error instanceof Error ? error.message : "未知错误"}`,
); );
} }
} }
@ -999,7 +1043,7 @@ export async function handleReplaceInFile(
panel: vscode.WebviewPanel, panel: vscode.WebviewPanel,
filePath: string, filePath: string,
searchText: string, searchText: string,
replaceText: string replaceText: string,
) { ) {
try { try {
const oldContent = await readFileContent(filePath); const oldContent = await readFileContent(filePath);
@ -1018,7 +1062,7 @@ export async function handleReplaceInFile(
error: error instanceof Error ? error.message : "替换文件内容失败", error: error instanceof Error ? error.message : "替换文件内容失败",
}); });
vscode.window.showErrorMessage( vscode.window.showErrorMessage(
`替换文件内容失败: ${error instanceof Error ? error.message : "未知错误"}` `替换文件内容失败: ${error instanceof Error ? error.message : "未知错误"}`,
); );
} }
} }
@ -1063,7 +1107,7 @@ function isVCDGenerationCommand(text: string): boolean {
*/ */
async function handleVCDGeneration( async function handleVCDGeneration(
panel: vscode.WebviewPanel, panel: vscode.WebviewPanel,
extensionPath: string extensionPath: string,
) { ) {
try { try {
// 获取当前工作区路径 // 获取当前工作区路径
@ -1090,7 +1134,7 @@ async function handleVCDGeneration(
if (!iverilogCheck.available) { if (!iverilogCheck.available) {
panel.webview.postMessage({ panel.webview.postMessage({
command: "receiveMessage", command: "receiveMessage",
text: `${iverilogCheck.message}\n\n请参考插件文档安装 iverilog 工具`, text: `${iverilogCheck.message}`,
}); });
vscode.window.showErrorMessage(iverilogCheck.message); vscode.window.showErrorMessage(iverilogCheck.message);
return; return;
@ -1165,12 +1209,15 @@ async function handleVCDGeneration(
// 发送系统通知 // 发送系统通知
const notificationService = NotificationService.getInstance(); const notificationService = NotificationService.getInstance();
notificationService.success( notificationService.success(
'IC Coder - 仿真完成', "IC Coder - 仿真完成",
`VCD 文件已生成: ${fileName}`, `VCD 文件已生成: ${fileName}`,
() => { () => {
// 点击通知时打开 VCD 查看器 // 点击通知时打开 VCD 查看器
vscode.commands.executeCommand('ic-coder.openVCDViewer', result.vcdFilePath); vscode.commands.executeCommand(
} "ic-coder.openVCDViewer",
result.vcdFilePath,
);
},
); );
} else { } else {
panel.webview.postMessage({ panel.webview.postMessage({
@ -1199,12 +1246,12 @@ async function handleVCDGeneration(
// 发送系统通知 // 发送系统通知
const notificationService = NotificationService.getInstance(); const notificationService = NotificationService.getInstance();
notificationService.error( notificationService.error(
'IC Coder - 仿真失败', "IC Coder - 仿真失败",
'VCD 文件生成失败,请查看错误信息', "VCD 文件生成失败,请查看错误信息",
() => { () => {
// 点击通知时聚焦到面板 // 点击通知时聚焦到面板
panel.reveal(); panel.reveal();
} },
); );
} }
} catch (error) { } catch (error) {
@ -1222,11 +1269,11 @@ async function handleVCDGeneration(
// 发送系统通知 // 发送系统通知
const notificationService = NotificationService.getInstance(); const notificationService = NotificationService.getInstance();
notificationService.error( notificationService.error(
'IC Coder - 仿真错误', "IC Coder - 仿真错误",
error instanceof Error ? error.message : '生成 VCD 文件时出错', error instanceof Error ? error.message : "生成 VCD 文件时出错",
() => { () => {
panel.reveal(); panel.reveal();
} },
); );
} }
} }
@ -1236,7 +1283,7 @@ async function handleVCDGeneration(
*/ */
export async function handleOptimizePrompt( export async function handleOptimizePrompt(
panel: vscode.WebviewPanel, panel: vscode.WebviewPanel,
prompt: string prompt: string,
): Promise<void> { ): Promise<void> {
console.log("[MessageHandler] ========== 收到提示词优化请求 =========="); console.log("[MessageHandler] ========== 收到提示词优化请求 ==========");
console.log("[MessageHandler] prompt:", prompt); console.log("[MessageHandler] prompt:", prompt);
@ -1268,7 +1315,7 @@ export async function handleOptimizePrompt(
*/ */
export async function handleAcceptChange( export async function handleAcceptChange(
panel: vscode.WebviewPanel, panel: vscode.WebviewPanel,
changeId: string changeId: string,
) { ) {
try { try {
const success = await changeTracker.acceptChange(changeId); const success = await changeTracker.acceptChange(changeId);
@ -1276,14 +1323,14 @@ export async function handleAcceptChange(
panel.webview.postMessage({ panel.webview.postMessage({
command: "changeAccepted", command: "changeAccepted",
changeId: changeId, changeId: changeId,
success: true success: true,
}); });
} else { } else {
panel.webview.postMessage({ panel.webview.postMessage({
command: "changeAccepted", command: "changeAccepted",
changeId: changeId, changeId: changeId,
success: false, success: false,
error: "采纳变更失败" error: "采纳变更失败",
}); });
} }
} catch (error) { } catch (error) {
@ -1292,7 +1339,7 @@ export async function handleAcceptChange(
command: "changeAccepted", command: "changeAccepted",
changeId: changeId, changeId: changeId,
success: false, success: false,
error: String(error) error: String(error),
}); });
} }
} }
@ -1302,7 +1349,7 @@ export async function handleAcceptChange(
*/ */
export async function handleRejectChange( export async function handleRejectChange(
panel: vscode.WebviewPanel, panel: vscode.WebviewPanel,
changeId: string changeId: string,
) { ) {
try { try {
const success = await changeTracker.rejectChange(changeId); const success = await changeTracker.rejectChange(changeId);
@ -1310,14 +1357,14 @@ export async function handleRejectChange(
panel.webview.postMessage({ panel.webview.postMessage({
command: "changeRejected", command: "changeRejected",
changeId: changeId, changeId: changeId,
success: true success: true,
}); });
} else { } else {
panel.webview.postMessage({ panel.webview.postMessage({
command: "changeRejected", command: "changeRejected",
changeId: changeId, changeId: changeId,
success: false, success: false,
error: "拒绝变更失败" error: "拒绝变更失败",
}); });
} }
} catch (error) { } catch (error) {
@ -1326,7 +1373,7 @@ export async function handleRejectChange(
command: "changeRejected", command: "changeRejected",
changeId: changeId, changeId: changeId,
success: false, success: false,
error: String(error) error: String(error),
}); });
} }
} }
@ -1337,18 +1384,18 @@ export async function handleRejectChange(
export function sendChangesToWebview(panel: vscode.WebviewPanel) { export function sendChangesToWebview(panel: vscode.WebviewPanel) {
const session = changeTracker.endSession(); const session = changeTracker.endSession();
if (session && session.changes.length > 0) { if (session && session.changes.length > 0) {
const changesWithDiff = session.changes.map(change => { const changesWithDiff = session.changes.map((change) => {
const diffLines = generateDiff(change.oldContent, change.newContent); const diffLines = generateDiff(change.oldContent, change.newContent);
const diffHtml = renderDiffHtml(diffLines); const diffHtml = renderDiffHtml(diffLines);
return { return {
...change, ...change,
diffHtml diffHtml,
}; };
}); });
panel.webview.postMessage({ panel.webview.postMessage({
command: "showChanges", command: "showChanges",
changes: changesWithDiff changes: changesWithDiff,
}); });
} }
} }
@ -1365,62 +1412,67 @@ export function startChangeSession(sessionId: string) {
*/ */
export async function handleOpenFileDiff( export async function handleOpenFileDiff(
panel: vscode.WebviewPanel, panel: vscode.WebviewPanel,
changeId: string changeId: string,
) { ) {
try { try {
const session = changeTracker.getCurrentSession(); const session = changeTracker.getCurrentSession();
if (!session) { if (!session) {
vscode.window.showErrorMessage('没有找到变更会话'); vscode.window.showErrorMessage("没有找到变更会话");
return; return;
} }
const change = session.changes.find(c => c.changeId === changeId); const change = session.changes.find((c) => c.changeId === changeId);
if (!change) { if (!change) {
vscode.window.showErrorMessage('没有找到该变更'); vscode.window.showErrorMessage("没有找到该变更");
return; return;
} }
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) { if (!workspaceFolder) {
vscode.window.showErrorMessage('没有打开的工作区'); vscode.window.showErrorMessage("没有打开的工作区");
return; return;
} }
// 创建临时文件用于对比 // 创建临时文件用于对比
const filePath = change.filePath; const filePath = change.filePath;
const absolutePath = vscode.Uri.file( const absolutePath = vscode.Uri.file(
path.join(workspaceFolder.uri.fsPath, filePath) path.join(workspaceFolder.uri.fsPath, filePath),
); );
// 创建虚拟文档显示旧内容 // 创建虚拟文档显示旧内容
const oldUri = vscode.Uri.parse( const oldUri = vscode.Uri.parse(
`ic-coder-diff:${filePath}.old?${changeId}` `ic-coder-diff:${filePath}.old?${changeId}`,
).with({ scheme: 'ic-coder-diff' }); ).with({ scheme: "ic-coder-diff" });
// 注册文档内容提供者(如果还没注册) // 注册文档内容提供者(如果还没注册)
if (!(global as any).__diffProviderRegistered) { if (!(global as any).__diffProviderRegistered) {
const provider = new (class implements vscode.TextDocumentContentProvider { const provider = new (class
implements vscode.TextDocumentContentProvider
{
provideTextDocumentContent(uri: vscode.Uri): string { provideTextDocumentContent(uri: vscode.Uri): string {
const changeId = uri.query; const changeId = uri.query;
const session = changeTracker.getCurrentSession(); const session = changeTracker.getCurrentSession();
const change = session?.changes.find(c => c.changeId === changeId); const change = session?.changes.find((c) => c.changeId === changeId);
return change?.oldContent || ''; return change?.oldContent || "";
} }
})(); })();
vscode.workspace.registerTextDocumentContentProvider('ic-coder-diff', provider); vscode.workspace.registerTextDocumentContentProvider(
"ic-coder-diff",
provider,
);
(global as any).__diffProviderRegistered = true; (global as any).__diffProviderRegistered = true;
} }
// 打开 diff 编辑器 // 打开 diff 编辑器
await vscode.commands.executeCommand( await vscode.commands.executeCommand(
'vscode.diff', "vscode.diff",
oldUri, oldUri,
absolutePath, absolutePath,
`${filePath} (变更对比)` `${filePath} (变更对比)`,
); );
} catch (error) { } catch (error) {
console.error('[MessageHandler] 打开 diff 失败:', error); console.error("[MessageHandler] 打开 diff 失败:", error);
vscode.window.showErrorMessage(`打开 diff 失败: ${error}`); vscode.window.showErrorMessage(`打开 diff 失败: ${error}`);
} }
} }

View File

@ -129,6 +129,62 @@ export async function readDirectory(
} }
} }
/**
* 列出目录下的文件和文件夹(不读取内容,仅返回路径)
*/
export async function listDirectory(
dirPath: string,
extensions?: string[]
): Promise<string[]> {
try {
// 如果是相对路径,转换为绝对路径
let absolutePath = dirPath;
if (!path.isAbsolute(dirPath)) {
const workspaceFolders = vscode.workspace.workspaceFolders;
if (workspaceFolders && workspaceFolders.length > 0) {
absolutePath = path.join(workspaceFolders[0].uri.fsPath, dirPath);
}
}
const dirUri = vscode.Uri.file(absolutePath);
// 检查目录是否存在
try {
const stat = await vscode.workspace.fs.stat(dirUri);
if (stat.type !== vscode.FileType.Directory) {
throw new Error(`路径不是目录: ${absolutePath}`);
}
} catch (error) {
throw new Error(`目录不存在: ${absolutePath}`);
}
// 读取目录内容
const entries = await vscode.workspace.fs.readDirectory(dirUri);
const results: string[] = [];
for (const [fileName, fileType] of entries) {
if (fileType === vscode.FileType.Directory) {
results.push(fileName + '/');
} else if (fileType === vscode.FileType.File) {
// 扩展名过滤
if (extensions && extensions.length > 0) {
const ext = path.extname(fileName);
// 规范化扩展名(支持 "v" 和 ".v" 两种格式)
const normalizedExts = extensions.map(e => e.startsWith('.') ? e : '.' + e);
if (!normalizedExts.includes(ext)) {
continue;
}
}
results.push(fileName);
}
}
return results;
} catch (error) {
throw error;
}
}
/** /**
* 获取文件信息 * 获取文件信息
*/ */

View File

@ -13,6 +13,7 @@ import {
abortCurrentDialog, abortCurrentDialog,
handleOptimizePrompt, handleOptimizePrompt,
} from "../utils/messageHandler"; } from "../utils/messageHandler";
import { setCustomConfig } from "../config/settings";
/** /**
* 创建并显示IC 侧边栏视图 * 创建并显示IC 侧边栏视图
@ -124,6 +125,10 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
case "showWarning": case "showWarning":
vscode.window.showWarningMessage(message.message); vscode.window.showWarningMessage(message.message);
break; break;
// 新增:打开用户手册
case "openUserManual":
vscode.commands.executeCommand("ic-coder.openUserManual");
break;
// 新增:处理用户回答 // 新增:处理用户回答
case "submitAnswer": case "submitAnswer":
handleUserAnswer( handleUserAnswer(
@ -141,6 +146,21 @@ export function showICHelperPanel(context: vscode.ExtensionContext) {
case "optimizePrompt": case "optimizePrompt":
handleOptimizePrompt(panel, message.prompt); handleOptimizePrompt(panel, message.prompt);
break; break;
// 保存通用设置
case "saveGeneralSettings":
context.globalState.update('generalSettings', message.settings);
// 更新运行时配置(包括清空)
setCustomConfig({ backendUrl: message.settings.backendUrl || '' });
vscode.window.showInformationMessage('设置已保存');
break;
// 加载通用设置
case "loadGeneralSettings":
const settings = context.globalState.get('generalSettings');
panel.webview.postMessage({
command: 'loadedGeneralSettings',
settings: settings
});
break;
} }
}, },
undefined, undefined,
@ -158,52 +178,21 @@ export class ICViewProvider implements vscode.WebviewViewProvider {
private readonly extensionUri: vscode.Uri, private readonly extensionUri: vscode.Uri,
private readonly context: vscode.ExtensionContext private readonly context: vscode.ExtensionContext
) { ) {
// 监听认证状态变化 // 【已禁用】监听认证状态变化 - 无需登录
this.context.subscriptions.push(
vscode.authentication.onDidChangeSessions((e) => {
if (e.provider.id === "iccoder") {
this.refreshLoginStatus();
}
})
);
} }
/** /**
* 刷新登录状态并更新视图 * 【已禁用】刷新登录状态并更新视图 - 无需登录
*/ */
private async refreshLoginStatus(): Promise<void> { private async refreshLoginStatus(): Promise<void> {
if (this._view) { // 无需刷新登录状态
const isLoggedIn = await this.checkLoginStatus();
this._view.webview.html = this.getWebviewContent(
this._view.webview,
isLoggedIn
);
}
} }
/** /**
* 检查登录状态(使用 Authentication API * 【已禁用】检查登录状态 - 无需登录
*/ */
private async checkLoginStatus(): Promise<boolean> { private async checkLoginStatus(): Promise<boolean> {
try { return true; // 始终返回已登录状态
const session = await vscode.authentication.getSession("iccoder", [], { createIfNone: false });
console.log("[ICViewProvider] 检查登录状态, session:", session ? "存在" : "不存在");
if (!session) {
return false;
}
// 检查 token 是否过期
const expired = isTokenExpired(session.accessToken);
console.log("[ICViewProvider] token 过期检查结果:", expired);
// 只有明确过期才认为未登录,无法判断时认为已登录
if (expired === true) {
console.log("[ICViewProvider] Token 已过期");
return false;
}
return true;
} catch (error) {
console.log("[ICViewProvider] 检查登录状态失败:", error);
return false;
}
} }
resolveWebviewView(webviewView: vscode.WebviewView) { resolveWebviewView(webviewView: vscode.WebviewView) {
@ -223,30 +212,8 @@ export class ICViewProvider implements vscode.WebviewViewProvider {
console.log('[ICViewProvider] Webview options 已设置'); console.log('[ICViewProvider] Webview options 已设置');
console.log('[ICViewProvider] extensionUri:', this.extensionUri.toString()); console.log('[ICViewProvider] extensionUri:', this.extensionUri.toString());
// 【关键修复】先设置默认 HTML避免一直加载 // 【已禁用】登录检查 - 直接显示"开始使用"按钮
try { webviewView.webview.html = this.getWebviewContent(webviewView.webview, true);
const html = this.getWebviewContent(webviewView.webview, false);
console.log('[ICViewProvider] HTML 内容已生成,长度:', html.length);
webviewView.webview.html = html;
console.log('[ICViewProvider] HTML 已设置到 webview');
} catch (error) {
console.error('[ICViewProvider] 设置 HTML 失败:', error);
}
// 异步检查登录状态并更新 UI
this.checkLoginStatus()
.then((isLoggedIn) => {
console.log('[ICViewProvider] 登录状态检查完成:', isLoggedIn);
webviewView.webview.html = this.getWebviewContent(
webviewView.webview,
isLoggedIn
);
})
.catch((error) => {
console.error('[ICViewProvider] 检查登录状态失败:', error);
// 即使失败也显示未登录状态
webviewView.webview.html = this.getWebviewContent(webviewView.webview, false);
});
// 处理侧边栏的消息 // 处理侧边栏的消息
webviewView.webview.onDidReceiveMessage( webviewView.webview.onDidReceiveMessage(
@ -261,11 +228,28 @@ export class ICViewProvider implements vscode.WebviewViewProvider {
} else if (message.command === "openICCoder") { } else if (message.command === "openICCoder") {
// 打开 IC Coder 官网 // 打开 IC Coder 官网
vscode.env.openExternal(vscode.Uri.parse('https://www.iccoder.com')); vscode.env.openExternal(vscode.Uri.parse('https://www.iccoder.com'));
} else if (message.command === "openUserManual") {
// 打开用户手册
vscode.commands.executeCommand("ic-coder.openUserManual");
} else if (message.command === "openExternalUrl") { } else if (message.command === "openExternalUrl") {
// 打开外部链接 // 打开外部链接
if (message.url) { if (message.url) {
vscode.env.openExternal(vscode.Uri.parse(message.url)); vscode.env.openExternal(vscode.Uri.parse(message.url));
} }
} else if (message.command === "saveGeneralSettings") {
// 保存通用设置
this.context.globalState.update('generalSettings', message.settings);
if (message.settings.backendUrl) {
setCustomConfig({ backendUrl: message.settings.backendUrl });
}
vscode.window.showInformationMessage('设置已保存');
} else if (message.command === "loadGeneralSettings") {
// 加载通用设置
const settings = this.context.globalState.get('generalSettings');
webviewView.webview.postMessage({
command: 'loadedGeneralSettings',
settings: settings
});
} }
}, },
undefined, undefined,
@ -338,7 +322,7 @@ export class ICViewProvider implements vscode.WebviewViewProvider {
<img src="${logoUri}" alt="IC Coder" width="120" /> <img src="${logoUri}" alt="IC Coder" width="120" />
<h2>欢迎使用 IC Coder</h2> <h2>欢迎使用 IC Coder</h2>
${isLoggedIn ${isLoggedIn
? '<button class="btn" onclick="openChat()">开始创作</button>' ? '<button class="btn" onclick="openChat()">开始使用</button>'
: '<button class="btn" onclick="login()">登录账户</button>' : '<button class="btn" onclick="login()">登录账户</button>'
} }
</div> </div>

View File

@ -51,7 +51,11 @@ export function getContextDisplayStyles(): string {
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.context-item:hover { .context-item.clickable {
cursor: pointer;
}
.context-item.clickable:hover {
background: var(--vscode-list-hoverBackground); background: var(--vscode-list-hoverBackground);
} }
@ -126,6 +130,11 @@ export function getContextDisplayScript(): string {
return '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M832 64H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32z" fill="currentColor"/></svg>'; return '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M832 64H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32z" fill="currentColor"/></svg>';
} }
// 获取代码图标 SVG
function getCodeIcon() {
return '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32z m-40 728H184V184h656v656z m-484.7-122.1l39.6-39.5 113.1 113.1-39.6 39.5-113.1-113.1z m226.4-290.2l113.1 113.1-39.6 39.5-113.1-113.1 39.6-39.5z" fill="currentColor"/></svg>';
}
// 获取删除图标 SVG // 获取删除图标 SVG
function getRemoveIcon() { function getRemoveIcon() {
return '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9c-4.4 5.2-.7 13.1 6.1 13.1h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z" fill="currentColor"/></svg>'; return '<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9c-4.4 5.2-.7 13.1 6.1 13.1h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z" fill="currentColor"/></svg>';
@ -172,13 +181,17 @@ export function getContextDisplayScript(): string {
case 'folder': icon = getFolderIcon(); break; case 'folder': icon = getFolderIcon(); break;
case 'image': icon = getImageIcon(); break; case 'image': icon = getImageIcon(); break;
case 'document': icon = getDocumentIcon(); break; case 'document': icon = getDocumentIcon(); break;
case 'code': icon = getCodeIcon(); break;
} }
const clickable = item.type !== 'folder' ? 'clickable' : '';
const onclick = item.type !== 'folder' ? \`onclick="window.handleContextItemClick(\${item.id})"\` : '';
return \` return \`
<div class="context-item" title="\${item.path}"> <div class="context-item \${clickable}" title="\${item.path || item.displayPath}" \${onclick}>
\${icon} \${icon}
<span class="context-item-name">\${item.displayPath || getFileName(item.path)}</span> <span class="context-item-name">\${item.displayPath || getFileName(item.path)}</span>
<span class="context-item-remove" onclick="removeContextItem(\${item.id})"> <span class="context-item-remove" onclick="event.stopPropagation(); removeContextItem(\${item.id})">
\${getRemoveIcon()} \${getRemoveIcon()}
</span> </span>
</div> </div>
@ -186,6 +199,27 @@ export function getContextDisplayScript(): string {
}).join(''); }).join('');
} }
// 全局访问函数
window.handleContextItemClick = function(id) {
const item = contextItems.find(i => i.id === id);
if (!item || item.type === 'folder') return;
if (item.type === 'code') {
const codeData = JSON.parse(item.path);
vscode.postMessage({
command: 'openFileWithSelection',
filePath: codeData.fileName,
startLine: codeData.startLine,
endLine: codeData.endLine
});
} else {
vscode.postMessage({
command: 'openFile',
filePath: item.path
});
}
};
// 处理后端返回的文件选择结果 // 处理后端返回的文件选择结果
window.addEventListener('message', event => { window.addEventListener('message', event => {
const message = event.data; const message = event.data;
@ -211,6 +245,18 @@ export function getContextDisplayScript(): string {
message.documents.forEach(doc => addContextItem('document', doc)); message.documents.forEach(doc => addContextItem('document', doc));
} }
break; break;
case 'addCodeContext':
// 添加代码上下文
const displayName = \`\${message.fileName.split(/[\\\\/]/).pop()}:\${message.startLine}-\${message.endLine}\`;
const codeData = {
fileName: message.fileName,
startLine: message.startLine,
endLine: message.endLine,
code: message.code,
languageId: message.languageId
};
addContextItem('code', JSON.stringify(codeData), displayName);
break;
} }
}); });

View File

@ -47,11 +47,7 @@ export function getConversationHistoryBarContent(): string {
</svg> </svg>
</button> </button>
<div class="user-info-container"> <div class="user-info-container" style="display: none;">
<button class="user-avatar-icon-button" id="userAvatarIconButton" style="display: none;" title="查看用户信息" onclick="openUserDetailModal()">
${userAvatarIconSvg}
</button>
${getUserInfoComponentContent()}
</div> </div>
<div class='setting'> <div class='setting'>

View File

@ -4,7 +4,14 @@
export function getExampleShowcaseContent(): string { export function getExampleShowcaseContent(): string {
return ` return `
<div class="example-showcase" id="exampleShowcase"> <div class="example-showcase" id="exampleShowcase">
<div class="showcase-title">示例</div> <div class="showcase-header">
<div class="showcase-title">示例</div>
<button class="refresh-button" onclick="refreshExamples()" title="换一批">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.5 2V8M21.5 8H15.5M21.5 8L18 4.5C16.7429 3.24286 15.1767 2.35596 13.4606 1.93597C11.7446 1.51598 9.94736 1.57986 8.26381 2.12059C6.58027 2.66131 5.07831 3.65985 3.91872 4.99987C2.75913 6.33989 1.98648 7.96902 1.68 9.71M2.5 22V16M2.5 16H8.5M2.5 16L6 19.5C7.25714 20.7571 8.82331 21.644 10.5394 22.064C12.2554 22.484 14.0526 22.4201 15.7362 21.8794C17.4197 21.3387 18.9217 20.3401 20.0813 19.0001C21.2409 17.6601 22.0135 16.031 22.32 14.29" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
</div>
<div class="example-cards"> <div class="example-cards">
<div class="example-card" onclick="sendExample(0)"> <div class="example-card" onclick="sendExample(0)">
<div class="example-icon"> <div class="example-icon">
@ -34,14 +41,6 @@ export function getExampleShowcaseContent(): string {
</div> </div>
</div> </div>
</div> </div>
<div class="web-link">
<a href="https://iccoder.com" target="_blank" class="web-link-button">
<span class="link-icon">🌐</span>
<span>IC Coder Web端</span>
<span class="link-arrow">→</span>
</a>
</div>
</div> </div>
`; `;
} }
@ -62,12 +61,44 @@ export function getExampleShowcaseStyles(): string {
display: none; display: none;
} }
.showcase-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.showcase-title { .showcase-title {
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: 600;
color: var(--vscode-foreground); color: var(--vscode-foreground);
margin-bottom: 12px; }
text-align: left;
.refresh-button {
background: transparent;
border: none;
color: var(--vscode-foreground);
cursor: pointer;
padding: 4px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
transition: all 0.2s ease;
opacity: 0.6;
}
.refresh-button:hover {
opacity: 1;
background: var(--vscode-input-background);
}
.refresh-button svg {
transition: transform 0.3s ease;
}
.refresh-button:active svg {
transform: rotate(180deg);
} }
.example-cards { .example-cards {
@ -165,41 +196,6 @@ export function getExampleShowcaseStyles(): string {
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
} }
.web-link {
display: flex;
justify-content: center;
padding-top: 20px;
border-top: 1px solid var(--vscode-panel-border);
margin-top: 8px;
}
.web-link-button {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
background: transparent;
border: none;
text-decoration: none;
font-size: 14px;
font-weight: 600;
transition: all 0.2s ease;
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 50%, #a855f7 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
outline: none;
}
.web-link-button:focus {
outline: none;
}
.web-link-button:hover {
transform: translateY(-1px);
opacity: 0.8;
}
.link-icon { .link-icon {
font-size: 16px; font-size: 16px;
} }
@ -208,10 +204,6 @@ export function getExampleShowcaseStyles(): string {
font-size: 16px; font-size: 16px;
transition: transform 0.2s ease; transition: transform 0.2s ease;
} }
.web-link-button:hover .link-arrow {
transform: translateX(3px);
}
`; `;
} }
@ -220,15 +212,74 @@ export function getExampleShowcaseStyles(): string {
*/ */
export function getExampleShowcaseScript(): string { export function getExampleShowcaseScript(): string {
return ` return `
// 示例文本数组 // 所有可用的示例
const exampleTexts = [ const allExamples = [
'生成一个SPI控制器', '设计一个算术逻辑单元,完成常见运算',
'生成一个GMII接口的以太网UDP通信模块' '实现一个优先编码器,多个输入同时有效时,只输出优先级最高的那个编号',
'实现一个译码器,把二进制编号转换成 one-hot 输出',
'实现一个移位寄存器,完成串行/并行数据移位与装载',
'实现一个按键消抖模块,解决机械按键抖动问题',
'实现一个跑马灯控制器,控制 LED 形成不同流动效果',
'实现一个序列检测器,检测串行输入中是否出现指定比特序列',
'实现一个LFSR 伪随机数发生器',
'实现一个自动售货机,模拟一个简单售货逻辑',
'实现一个交通灯控制器,控制两方向交通灯的切换',
'实现一个先进先出的数据缓冲区',
'单端口 RAM 读写控制器',
'实现一个移位加法乘法器,不用 * 运算符'
]; ];
// 当前显示的示例文本
let exampleTexts = ['生成一个SPI控制器', '生成一个GMII接口的以太网UDP通信模块'];
// 存储待发送的示例索引 // 存储待发送的示例索引
let pendingExampleIndex = -1; let pendingExampleIndex = -1;
// 节流控制
let refreshing = false;
// 刷新示例
function refreshExamples() {
if (refreshing) return;
refreshing = true;
const used = new Set();
const newExamples = [];
while (newExamples.length < 2) {
const idx = Math.floor(Math.random() * allExamples.length);
if (!used.has(idx)) {
used.add(idx);
newExamples.push(allExamples[idx]);
}
}
exampleTexts = newExamples;
updateExampleCards();
setTimeout(() => { refreshing = false; }, 500);
}
// 更新示例卡片显示
function updateExampleCards() {
const container = document.querySelector('.example-cards');
if (!container) return;
container.innerHTML = exampleTexts.map((text, i) => \`
<div class="example-card" onclick="sendExample(\${i})">
<div class="example-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 2H6C5.46957 2 4.96086 2.21071 4.58579 2.58579C4.21071 2.96086 4 3.46957 4 4V20C4 20.5304 4.21071 21.0391 4.58579 21.4142C4.96086 21.7893 5.46957 22 6 22H18C18.5304 22 19.0391 21.7893 19.4142 21.4142C19.7893 21.0391 20 20.5304 20 20V8L14 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 2V8H20" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16 13H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16 17H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 9H9H8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<div class="example-content">
<div class="example-title">\${text}</div>
</div>
</div>
\`).join('');
}
// 直接发送示例消息 // 直接发送示例消息
function sendExample(index) { function sendExample(index) {
// 先检查邀请码验证状态 // 先检查邀请码验证状态

View File

@ -46,10 +46,21 @@ export function getFilePathTagScript(): string {
return ` return `
// 处理文件路径标签点击 // 处理文件路径标签点击
function handleFilePathClick(filePath) { function handleFilePathClick(filePath) {
vscode.postMessage({ // 解析文件路径,支持 file.v:5-8 格式
command: 'openFile', const match = filePath.match(/^(.+?):(\\d+)-(\\d+)$/);
filePath: filePath if (match) {
}); vscode.postMessage({
command: 'openFilePathTag',
filePath: match[1],
startLine: parseInt(match[2]),
endLine: parseInt(match[3])
});
} else {
vscode.postMessage({
command: 'openFilePathTag',
filePath: filePath
});
}
} }
// 创建文件路径标签 // 创建文件路径标签

View File

@ -4,75 +4,15 @@
export function getGeneralSettingsComponentContent(): string { export function getGeneralSettingsComponentContent(): string {
return ` return `
<div class="general-settings"> <div class="general-settings">
<h3 class="settings-section-title">通用设置</h3> <h3 class="settings-section-title">后端服务配置</h3>
<div class="settings-section"> <div class="settings-section">
<div class="settings-item"> <div class="settings-item">
<div class="settings-item-header"> <div class="settings-item-header">
<label class="settings-item-label">主题</label> <label class="settings-item-label">后端服务地址</label>
<span class="settings-item-description">选择界面主题</span> <span class="settings-item-description">自定义后端 API 地址</span>
</div> </div>
<select class="settings-select" id="themeSelect"> <input type="text" class="settings-input-text" id="backendUrlInput" placeholder="https://iccoder.com">
<option value="auto">跟随系统</option>
<option value="light">浅色</option>
<option value="dark">深色</option>
</select>
</div>
<div class="settings-item">
<div class="settings-item-header">
<label class="settings-item-label">语言</label>
<span class="settings-item-description">选择界面语言</span>
</div>
<select class="settings-select" id="languageSelect">
<option value="zh-CN">简体中文</option>
<option value="en-US">English</option>
</select>
</div>
<div class="settings-item">
<div class="settings-item-header">
<label class="settings-item-label">自动保存</label>
<span class="settings-item-description">自动保存会话历史</span>
</div>
<label class="settings-switch">
<input type="checkbox" id="autoSaveCheckbox" checked>
<span class="settings-switch-slider"></span>
</label>
</div>
<div class="settings-item">
<div class="settings-item-header">
<label class="settings-item-label">显示时间戳</label>
<span class="settings-item-description">在消息中显示时间戳</span>
</div>
<label class="settings-switch">
<input type="checkbox" id="showTimestampCheckbox">
<span class="settings-switch-slider"></span>
</label>
</div>
</div>
<div class="settings-section">
<h4 class="settings-subsection-title">编辑器设置</h4>
<div class="settings-item">
<div class="settings-item-header">
<label class="settings-item-label">字体大小</label>
<span class="settings-item-description">设置编辑器字体大小</span>
</div>
<input type="number" class="settings-input" id="fontSizeInput" value="14" min="10" max="24">
</div>
<div class="settings-item">
<div class="settings-item-header">
<label class="settings-item-label">代码高亮</label>
<span class="settings-item-description">启用代码语法高亮</span>
</div>
<label class="settings-switch">
<input type="checkbox" id="syntaxHighlightCheckbox" checked>
<span class="settings-switch-slider"></span>
</label>
</div> </div>
</div> </div>
@ -176,6 +116,21 @@ export function getGeneralSettingsComponentStyles(): string {
border-color: var(--vscode-focusBorder); border-color: var(--vscode-focusBorder);
} }
.settings-input-text {
width: 300px;
padding: 6px 12px;
background: var(--vscode-input-background);
color: var(--vscode-input-foreground);
border: 1px solid var(--vscode-input-border);
border-radius: 4px;
font-size: 13px;
outline: none;
}
.settings-input-text:focus {
border-color: var(--vscode-focusBorder);
}
.settings-switch { .settings-switch {
position: relative; position: relative;
display: inline-block; display: inline-block;
@ -270,57 +225,37 @@ export function getGeneralSettingsComponentScript(): string {
// 保存通用设置 // 保存通用设置
function saveGeneralSettings() { function saveGeneralSettings() {
const settings = { const settings = {
theme: document.getElementById('themeSelect').value, backendUrl: document.getElementById('backendUrlInput').value,
language: document.getElementById('languageSelect').value,
autoSave: document.getElementById('autoSaveCheckbox').checked,
showTimestamp: document.getElementById('showTimestampCheckbox').checked,
fontSize: document.getElementById('fontSizeInput').value,
syntaxHighlight: document.getElementById('syntaxHighlightCheckbox').checked,
}; };
// 发送消息到扩展
vscode.postMessage({ vscode.postMessage({
command: 'saveGeneralSettings', command: 'saveGeneralSettings',
settings: settings settings: settings
}); });
// 显示保存成功提示
console.log('通用设置已保存', settings); console.log('通用设置已保存', settings);
closeSettingsModal();
} }
// 重置通用设置 // 重置通用设置
function resetGeneralSettings() { function resetGeneralSettings() {
document.getElementById('themeSelect').value = 'auto'; document.getElementById('backendUrlInput').value = '';
document.getElementById('languageSelect').value = 'zh-CN';
document.getElementById('autoSaveCheckbox').checked = true; // 清空保存的配置
document.getElementById('showTimestampCheckbox').checked = false; vscode.postMessage({
document.getElementById('fontSizeInput').value = '14'; command: 'saveGeneralSettings',
document.getElementById('syntaxHighlightCheckbox').checked = true; settings: { backendUrl: '' }
});
console.log('通用设置已重置为默认值'); console.log('通用设置已重置为默认值');
closeSettingsModal();
} }
// 加载通用设置 // 加载通用设置
function loadGeneralSettings(settings) { function loadGeneralSettings(settings) {
if (!settings) return; if (!settings) return;
if (settings.backendUrl) {
if (settings.theme) { document.getElementById('backendUrlInput').value = settings.backendUrl;
document.getElementById('themeSelect').value = settings.theme;
}
if (settings.language) {
document.getElementById('languageSelect').value = settings.language;
}
if (settings.autoSave !== undefined) {
document.getElementById('autoSaveCheckbox').checked = settings.autoSave;
}
if (settings.showTimestamp !== undefined) {
document.getElementById('showTimestampCheckbox').checked = settings.showTimestamp;
}
if (settings.fontSize) {
document.getElementById('fontSizeInput').value = settings.fontSize;
}
if (settings.syntaxHighlight !== undefined) {
document.getElementById('syntaxHighlightCheckbox').checked = settings.syntaxHighlight;
} }
} }
`; `;

View File

@ -432,15 +432,14 @@ export function getInputAreaScript(): string {
// 获取上下文项 // 获取上下文项
const contextItems = window.getContextItems ? window.getContextItems() : []; const contextItems = window.getContextItems ? window.getContextItems() : [];
// 构建显示消息:如果有上下文文件,添加文件路径前缀 // 构建显示消息:如果有上下文,添加路径前缀
let displayText = text; let displayText = text;
if (contextItems.length > 0) { if (contextItems.length > 0) {
const filePaths = contextItems const contextPaths = contextItems
.filter(item => item.type === 'file')
.map(item => item.displayPath || item.path) .map(item => item.displayPath || item.path)
.join(' '); .join(' ');
if (filePaths) { if (contextPaths) {
displayText = filePaths + ' ' + text; displayText = contextPaths + ' ' + text;
} }
} }

View File

@ -382,7 +382,7 @@ export function getMessageAreaStyles(): string {
} }
/* 低调显示的工具调用 - 移除边距和背景 */ /* 低调显示的工具调用 - 移除边距和背景 */
.segment-tool.low-profile { .segment-tool.low-profile {
margin: 5px 0px; margin: 25px 0px;
padding: 0; padding: 0;
background: none; background: none;
} }
@ -549,7 +549,7 @@ export function getMessageAreaStyles(): string {
max-height: 0; max-height: 0;
} }
.tool-segment-description { .tool-segment-description {
margin: 6px 0 0 0px; margin: 25px 0 0 0px;
font-size: 0.9rem; font-size: 0.9rem;
color: var(--vscode-descriptionForeground); color: var(--vscode-descriptionForeground);
line-height: 1.4; line-height: 1.4;
@ -590,9 +590,9 @@ export function getMessageAreaStyles(): string {
} }
.segment-question .question-option { .segment-question .question-option {
padding: 8px 16px; padding: 8px 16px;
background: #007ACC; background: #3d3f41;
color: #ffffff; color: #ffffff;
border: 1px solid #007ACC; border: 1px solid #474747;
border-radius: 6px; border-radius: 6px;
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all 0.2s;
@ -863,8 +863,8 @@ export function getMessageAreaScript(): string {
const textParts = []; const textParts = [];
parts.forEach(part => { parts.forEach(part => {
// 判断是否为文件路径:包含路径分隔符文件扩展名 // 判断是否为文件路径或代码片段:包含路径分隔符文件扩展名或代码片段格式(文件名:行号-行号)
if (part.includes('/') || part.includes('\\\\') || /\\.[a-zA-Z0-9]+$/.test(part)) { if (part.includes('/') || part.includes('\\\\') || /\\.[a-zA-Z0-9]+$/.test(part) || /:[0-9]+-[0-9]+$/.test(part)) {
filePaths.push(part); filePaths.push(part);
} else { } else {
textParts.push(part); textParts.push(part);
@ -1211,7 +1211,7 @@ export function getMessageAreaScript(): string {
const optionsHtml = q.options.map(opt => { const optionsHtml = q.options.map(opt => {
const isSelected = selectedAnswers.includes(opt); const isSelected = selectedAnswers.includes(opt);
return \`<label style="display:flex;align-items:center;gap:6px;cursor:pointer;padding:4px 0;"> return \`<label class="question-option\${isSelected ? ' selected' : ''}" style="display:flex;align-items:center;gap:6px;cursor:pointer;padding:5px 5px 5px 0;">
<input type="\${inputType}" name="\${inputName}" value="\${opt}" \${isSelected ? 'checked' : ''} \${isAnswered ? 'disabled' : ''}> <input type="\${inputType}" name="\${inputName}" value="\${opt}" \${isSelected ? 'checked' : ''} \${isAnswered ? 'disabled' : ''}>
<span>\${opt}</span> <span>\${opt}</span>
</label>\`; </label>\`;
@ -1729,9 +1729,18 @@ export function getMessageAreaScript(): string {
// 标记问题已回答 // 标记问题已回答
segmentDiv.classList.add('answered'); segmentDiv.classList.add('answered');
// 禁用所有输入 // 禁用所有输入并保持选中状态的高亮
const inputs = segmentDiv.querySelectorAll('input'); const inputs = segmentDiv.querySelectorAll('input');
inputs.forEach(input => input.disabled = true); inputs.forEach(input => {
input.disabled = true;
// 确保选中的选项保持高亮
if (input.checked) {
const label = input.closest('.question-option');
if (label) {
label.classList.add('selected');
}
}
});
// 隐藏提交按钮 // 隐藏提交按钮
const submitBtn = segmentDiv.querySelector('.custom-submit'); const submitBtn = segmentDiv.querySelector('.custom-submit');

View File

@ -9,66 +9,16 @@ export function getModelSelectorContent(
autoIcon: string = "", autoIcon: string = "",
liteIcon: string = "", liteIcon: string = "",
syIcon: string = "", syIcon: string = "",
maxIcon: string = "" maxIcon: string = "",
): string { ): string {
return ` return `
<!-- 模型选择 --> <!-- 模型选择 -->
<div class="tooltip"> <div class="tooltip">
<div class="custom-select" id="modelSelect"> <div class="model-display">
<div class="select-trigger" onclick="toggleModelDropdown()"> <img src="${maxIcon || ""}" class="model-icon" alt="Max" style="display: ${maxIcon ? "block" : "none"};">
<span class="select-value" id="modelValue">Auto</span> <span class="model-label">Max</span>
<svg class="select-arrow" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<path d="M507.8 727.728a30.016 30.016 0 0 1-21.288-8.824L231.104 463.496a30.088 30.088 0 0 1 0-42.568 30.088 30.088 0 0 1 42.568 0l234.128 234.128 234.16-234.128a30.088 30.088 0 0 1 42.568 0 30.088 30.088 0 0 1 0 42.568L529.08 718.904a30 30 0 0 1-21.28 8.824z" fill="#8a8a8a"/>
</svg>
</div>
<div class="select-dropdown" id="modelDropdown">
<div class="select-option selected" data-value="auto" onclick="selectModel('auto', 'Auto')">
${
autoIcon
? `<img src="${autoIcon}" class="model-icon" alt="Auto">`
: ""
}
<div class="option-content">
<span class="option-label">Auto</span>
<span class="option-desc">智能匹配最优模型</span>
</div>
</div>
<div class="select-option" data-value="lite" onclick="selectModel('lite', 'Lite')">
${
liteIcon
? `<img src="${liteIcon}" class="model-icon" alt="Lite">`
: ""
}
<div class="option-content">
<span class="option-label">Lite</span>
<span class="option-desc">基础模型,快速相应,适合简单任务</span>
</div>
</div>
<div class="select-option" data-value="syntaxic" onclick="selectModel('syntaxic', 'Syntaxic')">
${
syIcon
? `<img src="${syIcon}" class="model-icon" alt="Syntaxic">`
: ""
}
<div class="option-content">
<span class="option-label">Syntaxic</span>
<span class="option-desc">均衡成本和性能节省credits同时保持可靠输出</span>
</div>
</div>
<div class="select-option" data-value="max" onclick="selectModel('max', 'Max')">
${
maxIcon
? `<img src="${maxIcon}" class="model-icon" alt="Max">`
: ""
}
<div class="option-content">
<span class="option-label">Max</span>
<span class="option-desc">最强性能,质量优先,适合复杂任务</span>
</div>
</div>
</div>
</div> </div>
<span class="tooltiptext">选择模型</span> <span class="tooltiptext">IC Coder自研FPGA专属微调模型</span>
</div> </div>
`; `;
} }
@ -78,72 +28,16 @@ export function getModelSelectorContent(
*/ */
export function getModelSelectorStyles(): string { export function getModelSelectorStyles(): string {
return ` return `
/* 自定义下拉框样式 */ .model-display {
.custom-select {
position: relative;
user-select: none;
}
.select-trigger {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; gap: 6px;
padding: 4px 8px; padding: 4px 8px;
background: var(--vscode-input-background); background: var(--vscode-input-background);
color: var(--vscode-foreground); color: var(--vscode-foreground);
border: none;
border-radius: 4px; border-radius: 4px;
cursor: pointer;
font-size: 12px; font-size: 12px;
transition: background 0.2s ease; cursor: default;
}
.select-trigger:hover {
background: var(--vscode-list-hoverBackground);
}
.select-value {
white-space: nowrap;
}
.select-arrow {
width: 12px;
height: 12px;
flex-shrink: 0;
transition: transform 0.2s ease;
}
.custom-select.active .select-arrow {
transform: rotate(180deg);
}
.select-dropdown {
position: absolute;
bottom: calc(100% + 2px);
left: 0;
min-width: 100%;
background: var(--vscode-dropdown-background);
border: 1px solid var(--vscode-dropdown-border);
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 1100;
display: none;
overflow: visible;
}
.custom-select.active .select-dropdown {
display: block;
}
/* 模型选择器的选项样式 */
#modelDropdown .select-option {
position: relative;
padding: 8px 12px;
cursor: pointer;
transition: background 0.2s ease;
display: flex;
flex-direction: row;
align-items: center;
gap: 10px;
}
#modelDropdown .select-option:hover {
background: rgba(128, 128, 128, 0.3);
}
#modelDropdown .select-option.selected {
background: rgba(128, 128, 128, 0.5);
color: var(--vscode-foreground);
} }
.model-icon { .model-icon {
width: 16px; width: 16px;
@ -151,21 +45,7 @@ export function getModelSelectorStyles(): string {
flex-shrink: 0; flex-shrink: 0;
object-fit: contain; object-fit: contain;
} }
.option-content { .model-label {
display: flex;
flex-direction: column;
gap: 2px;
flex: 1;
}
.option-label {
font-size: 13px;
color: var(--vscode-foreground);
font-weight: 500;
white-space: nowrap;
}
.option-desc {
font-size: 11px;
color: var(--vscode-descriptionForeground);
white-space: nowrap; white-space: nowrap;
} }
`; `;
@ -176,58 +56,9 @@ export function getModelSelectorStyles(): string {
*/ */
export function getModelSelectorScript(): string { export function getModelSelectorScript(): string {
return ` return `
// 模型选择相关变量 // 获取当前选中的模型(固定为 max
let currentModel = 'auto';
// 切换模型下拉框显示/隐藏
function toggleModelDropdown() {
const modelSelect = document.getElementById('modelSelect');
const customSelect = document.getElementById('customSelect');
if (modelSelect) {
modelSelect.classList.toggle('active');
// 关闭模式下拉框
if (customSelect) {
customSelect.classList.remove('active');
}
}
}
// 选择模型
function selectModel(value, label) {
currentModel = value;
const modelValue = document.getElementById('modelValue');
if (modelValue) {
modelValue.textContent = label;
}
// 更新选中状态
const options = document.querySelectorAll('#modelDropdown .select-option');
options.forEach(option => {
if (option.getAttribute('data-value') === value) {
option.classList.add('selected');
} else {
option.classList.remove('selected');
}
});
// 关闭下拉框
const modelSelect = document.getElementById('modelSelect');
if (modelSelect) {
modelSelect.classList.remove('active');
}
}
// 点击外部关闭模型下拉框
document.addEventListener('click', (event) => {
const modelSelect = document.getElementById('modelSelect');
if (modelSelect && !modelSelect.contains(event.target)) {
modelSelect.classList.remove('active');
}
});
// 获取当前选中的模型
function getCurrentModel() { function getCurrentModel() {
return currentModel; return 'max';
} }
`; `;
} }

View File

@ -1,6 +1,6 @@
/** /**
* 更多选项组件 * 更多选项组件
* 包含用户手册和用户反馈入口 * 包含用户手册入口
*/ */
/** /**
@ -28,40 +28,10 @@ export function getMoreOptionsComponentContent(): string {
<div class="option-desc">查看使用文档和帮助</div> <div class="option-desc">查看使用文档和帮助</div>
</div> </div>
</div> </div>
<div class="more-option-item" id="userFeedbackOption">
<div class="option-icon">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 12h-2v-2h2v2zm0-4h-2V6h2v4z" fill="currentColor"/>
</svg>
</div>
<div class="option-text">
<div class="option-label">用户反馈</div>
<div class="option-desc">提交问题和建议</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
<!-- 用户反馈二维码弹窗 -->
<div class="feedback-qrcode-modal" id="feedbackQRCodeModal">
<div class="feedback-qrcode-overlay" onclick="closeFeedbackQRCode()"></div>
<div class="feedback-qrcode-content">
<div class="feedback-qrcode-header">
<span class="feedback-qrcode-title">用户反馈</span>
<button class="feedback-qrcode-close" onclick="closeFeedbackQRCode()">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" fill="currentColor"/>
</svg>
</button>
</div>
<div class="feedback-qrcode-body">
<img class="feedback-qrcode-image" id="feedbackQRCodeImage" alt="微信二维码" />
<p class="feedback-qrcode-text">扫描二维码添加微信反馈</p>
</div>
</div>
</div>
</div> </div>
`; `;
} }
@ -163,125 +133,6 @@ export function getMoreOptionsComponentStyles(): string {
.option-desc { .option-desc {
display: none; display: none;
} }
/* 用户反馈二维码弹窗 */
.feedback-qrcode-modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 20000;
align-items: center;
justify-content: center;
}
.feedback-qrcode-modal.active {
display: flex;
animation: fadeIn 0.2s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.feedback-qrcode-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
cursor: pointer;
}
.feedback-qrcode-content {
position: relative;
background: var(--vscode-editor-background);
border: 1px solid var(--vscode-widget-border);
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
max-width: 400px;
width: 90%;
animation: slideUp 0.2s ease-out;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.feedback-qrcode-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid var(--vscode-widget-border);
}
.feedback-qrcode-title {
font-size: 14px;
font-weight: 600;
color: var(--vscode-foreground);
}
.feedback-qrcode-close {
width: 28px;
height: 28px;
padding: 0;
background: transparent;
border: none;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.15s ease;
}
.feedback-qrcode-close:hover {
background: var(--vscode-toolbar-hoverBackground);
}
.feedback-qrcode-close svg {
width: 16px;
height: 16px;
color: var(--vscode-foreground);
}
.feedback-qrcode-body {
padding: 24px;
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
}
.feedback-qrcode-image {
width: 200px;
height: 200px;
border: 1px solid var(--vscode-widget-border);
border-radius: 8px;
}
.feedback-qrcode-text {
margin: 0;
font-size: 13px;
color: var(--vscode-descriptionForeground);
text-align: center;
}
`; `;
} }
@ -331,29 +182,6 @@ export function getMoreOptionsComponentScript(): string {
closeMoreOptionsDropdown(); closeMoreOptionsDropdown();
} }
// 打开用户反馈
function openUserFeedback() {
console.log('打开用户反馈');
vscode.postMessage({ command: 'openUserFeedback' });
closeMoreOptionsDropdown();
}
// 显示用户反馈二维码弹窗
function showFeedbackQRCode() {
const modal = document.getElementById('feedbackQRCodeModal');
if (modal) {
modal.classList.add('active');
}
}
// 关闭用户反馈二维码弹窗
function closeFeedbackQRCode() {
const modal = document.getElementById('feedbackQRCodeModal');
if (modal) {
modal.classList.remove('active');
}
}
// 绑定更多选项事件 // 绑定更多选项事件
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
// 绑定用户手册选项 // 绑定用户手册选项
@ -362,12 +190,6 @@ export function getMoreOptionsComponentScript(): string {
userManualOption.addEventListener('click', openUserManual); userManualOption.addEventListener('click', openUserManual);
} }
// 绑定用户反馈选项
const userFeedbackOption = document.getElementById('userFeedbackOption');
if (userFeedbackOption) {
userFeedbackOption.addEventListener('click', openUserFeedback);
}
// 点击页面其他地方关闭下拉面板 // 点击页面其他地方关闭下拉面板
document.addEventListener('click', (e) => { document.addEventListener('click', (e) => {
const dropdown = document.getElementById('moreOptionsDropdown'); const dropdown = document.getElementById('moreOptionsDropdown');

View File

@ -25,7 +25,7 @@ export function getProgressBarContent(): string {
<span class="step-number">1</span> <span class="step-number">1</span>
<span class="step-check">✓</span> <span class="step-check">✓</span>
</div> </div>
<div class="step-label">Spec</div> <div class="step-label">Specification</div>
</div> </div>
<div class="progress-line"></div> <div class="progress-line"></div>

View File

@ -3,11 +3,6 @@ import {
getGeneralSettingsComponentStyles, getGeneralSettingsComponentStyles,
getGeneralSettingsComponentScript, getGeneralSettingsComponentScript,
} from "./generalSettingsComponent"; } from "./generalSettingsComponent";
import {
getRulesSettingsComponentContent,
getRulesSettingsComponentStyles,
getRulesSettingsComponentScript,
} from "./rulesSettingsComponent";
/** /**
* 获取设置面板的 HTML 内容 * 获取设置面板的 HTML 内容
@ -31,18 +26,13 @@ export function getSettingsComponentContent(): string {
<button class="settings-nav-item active" data-tab="general" onclick="switchSettingsTab('general')"> <button class="settings-nav-item active" data-tab="general" onclick="switchSettingsTab('general')">
通用 通用
</button> </button>
<button class="settings-nav-item" data-tab="rules" onclick="switchSettingsTab('rules')">
规则
</button>
</div> </div>
<div class="settings-content"> <div class="settings-content">
<div class="settings-tab-content active" id="generalSettings"> <div class="settings-tab-content active" id="generalSettings">
${getGeneralSettingsComponentContent()} ${getGeneralSettingsComponentContent()}
</div> </div>
<div class="settings-tab-content" id="rulesSettings">
${getRulesSettingsComponentContent()}
</div>
</div> </div>
</div> </div>
</div> </div>
@ -186,7 +176,6 @@ export function getSettingsComponentStyles(): string {
} }
${getGeneralSettingsComponentStyles()} ${getGeneralSettingsComponentStyles()}
${getRulesSettingsComponentStyles()}
`; `;
} }
@ -196,13 +185,14 @@ export function getSettingsComponentStyles(): string {
export function getSettingsComponentScript(): string { export function getSettingsComponentScript(): string {
return ` return `
${getGeneralSettingsComponentScript()} ${getGeneralSettingsComponentScript()}
${getRulesSettingsComponentScript()}
// 打开设置面板 // 打开设置面板
function openSettingsModal() { function openSettingsModal() {
const modal = document.getElementById('settingsModal'); const modal = document.getElementById('settingsModal');
if (modal) { if (modal) {
modal.classList.add('active'); modal.classList.add('active');
// 请求加载设置
vscode.postMessage({ command: 'loadGeneralSettings' });
} }
} }

View File

@ -9,57 +9,7 @@
*/ */
export function getUserInfoComponentContent(): string { export function getUserInfoComponentContent(): string {
return ` return `
<div class="user-info-wrapper"> <div class="user-info-wrapper" style="display: none;">
<!-- 用户详情下拉面板 -->
<div class="user-detail-dropdown" id="userDetailDropdown">
<div class="user-detail-content">
<div class="user-detail-header">
<div class="user-info-row">
<div class="user-avatar-small clickable" id="userAvatarClickable">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" fill="currentColor"/>
</svg>
</div>
<div class="user-name-tier">
<div class="user-detail-name clickable" id="userDetailName">加载中...</div>
<img class="tier-icon-inline" id="tierIconInline" style="display: none;" />
</div>
</div>
<!-- 升级到Pro按钮 (仅BASIC会员显示) -->
<!-- <div class="upgrade-pro-wrapper" id="upgradeProWrapper" style="display: none;">
<button class="upgrade-pro-btn" id="upgradeProBtn">升级到 Pro</button>
</div> -->
</div>
<div class="user-detail-body">
<!-- <div class="user-detail-item">
<span class="detail-label">剩余 Credits</span>
<span class="detail-value" id="creditsDetail">-</span>
</div> -->
<div class="user-detail-item logout-item" id="logoutItem">
<span class="detail-label">账户管理</span>
<span class="detail-value logout-link">退出登录</span>
</div>
</div>
</div>
</div>
</div>
<!-- 退出登录确认对话框 -->
<div class="logout-confirm-modal" id="logoutConfirmModal">
<div class="logout-confirm-overlay"></div>
<div class="logout-confirm-content">
<div class="logout-confirm-header">
<h3>确认退出</h3>
</div>
<div class="logout-confirm-body">
<p>确定要退出登录吗?</p>
</div>
<div class="logout-confirm-footer">
<button class="logout-confirm-btn logout-cancel-btn" id="logoutCancelBtn">取消</button>
<button class="logout-confirm-btn logout-ok-btn" id="logoutOkBtn">确定</button>
</div>
</div>
</div> </div>
`; `;
} }

View File

@ -18,6 +18,11 @@ import {
getMessageAreaScript, getMessageAreaScript,
} from "./messageArea"; } from "./messageArea";
import { getAgentCardStyles, getAgentCardScript } from "./agentCard"; import { getAgentCardStyles, getAgentCardScript } from "./agentCard";
import {
getMoreOptionsComponentContent,
getMoreOptionsComponentStyles,
getMoreOptionsComponentScript,
} from "./moreOptionsComponent";
import { import {
getProgressBarContent, getProgressBarContent,
getProgressBarStyles, getProgressBarStyles,
@ -110,6 +115,7 @@ export function getWebviewContent(
} }
${getMessageAreaStyles()} ${getMessageAreaStyles()}
${getAgentCardStyles()} ${getAgentCardStyles()}
${getMoreOptionsComponentStyles()}
${getWaveformPreviewContent()} ${getWaveformPreviewContent()}
${getConversationHistoryBarStyles()} ${getConversationHistoryBarStyles()}
${getProgressBarStyles()} ${getProgressBarStyles()}
@ -375,16 +381,32 @@ export function getWebviewContent(
font-size: 13px; font-size: 13px;
color: var(--vscode-descriptionForeground); color: var(--vscode-descriptionForeground);
} }
.status-bar #statusText {
background: linear-gradient(90deg,
var(--vscode-descriptionForeground) 0%,
var(--vscode-foreground) 50%,
var(--vscode-descriptionForeground) 100%);
background-size: 200% 100%;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
animation: textShimmer 2s linear infinite;
}
@keyframes textShimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.status-indicator { .status-indicator {
width: 8px; width: 8px;
height: 8px; height: 8px;
border-radius: 50%; border-radius: 50%;
background: var(--vscode-charts-blue); background: var(--vscode-charts-blue);
animation: statusPulse 1.5s ease-in-out infinite; animation: statusPulse 1.5s ease-in-out infinite;
box-shadow: 0 0 8px currentColor;
} }
@keyframes statusPulse { @keyframes statusPulse {
0%, 100% { opacity: 1; transform: scale(1); } 0%, 100% { opacity: 1; transform: scale(1.2); }
50% { opacity: 0.5; transform: scale(0.8); } 50% { opacity: 0.3; transform: scale(0.6); }
} }
.status-bar.working .status-indicator { .status-bar.working .status-indicator {
background: var(--vscode-charts-orange); background: var(--vscode-charts-orange);
@ -488,13 +510,17 @@ export function getWebviewContent(
${getNdtWelcomeModalContent(logoUri)} ${getNdtWelcomeModalContent(logoUri)}
${getExpiredModalContent(logoUri)} ${getExpiredModalContent(logoUri)}
<div class="header"> <div class="header">
<div style="display: flex; align-items: center; justify-content: center;"> <div style="display: flex; align-items: flex-end; justify-content: center">
<img src="${logoUri}" alt="IC Coder" style="max-width: 100%; height: auto; max-height: 80px;" /> <img src="${logoUri}" alt="IC Coder" style="max-width: 100%; height: auto; max-height: 80px;" />
<span style="font-size: 23px; font-weight: bold; background: linear-gradient(to bottom, #b2e4ff, #42bcff); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin: 0 0 14px -16px;">企业版</span>
</div> </div>
<p style="font-size: 16px; margin-top: 12px; line-height: 1.5;"> <p style="font-size: 16px; margin-top: 8px; line-height: 1.5;">
The <span style="background: linear-gradient(to right, #42bcff, #4A9EFF); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: bold;">Agentic AI</span> Verilog Coding Platform, The <span style="background: linear-gradient(to right, #42bcff, #4A9EFF); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: bold;">Agentic AI</span> Verilog Coding Platform
<span style="display: block; margin-top: 8px;">将芯片设计与验证的效率提升至少20倍</span> <span style="display: block; margin-top: 8px;">将FPGA研发效率提升至少20倍</span>
</p> </p>
<div style="margin-top: 16px; padding: 8px 20px; background: linear-gradient(135deg, rgba(255, 215, 0, 0.15), rgba(255, 165, 0, 0.15)); border: 1px solid rgba(255, 215, 0, 0.3); border-radius: 6px;">
<p style="font-size: 13px; margin: 0; background: linear-gradient(135deg, #FFD700, #FFA500); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: 600; letter-spacing: 1px;">宁德时代专属定制版</p>
</div>
</div> </div>
<div class="chat-container"> <div class="chat-container">
@ -754,6 +780,11 @@ export function getWebviewContent(
hideLoadingIndicator(); hideLoadingIndicator();
break; break;
case 'taskComplete':
// 显示任务完成提示
addMessage('✅ 任务已完成', 'bot');
break;
case 'workspaceStatus': case 'workspaceStatus':
// 更新工作区状态 // 更新工作区状态
if (typeof hasWorkspace !== 'undefined') { if (typeof hasWorkspace !== 'undefined') {
@ -903,6 +934,13 @@ export function getWebviewContent(
} }
break; break;
case 'loadedGeneralSettings':
// 加载通用设置
if (typeof loadGeneralSettings === 'function') {
loadGeneralSettings(message.settings);
}
break;
default: default:
console.log('[WebView] 未处理的消息类型:', message.command); console.log('[WebView] 未处理的消息类型:', message.command);
} }
@ -910,6 +948,7 @@ export function getWebviewContent(
${getMessageAreaScript()} ${getMessageAreaScript()}
${getAgentCardScript()} ${getAgentCardScript()}
${getMoreOptionsComponentScript()}
${getWaveformPreviewScript()} ${getWaveformPreviewScript()}
${getConversationHistoryBarScript()} ${getConversationHistoryBarScript()}
${getProgressBarScript()} ${getProgressBarScript()}

View File

@ -10,24 +10,23 @@ const CopyWebpackPlugin = require('copy-webpack-plugin');
/** @type WebpackConfig */ /** @type WebpackConfig */
const extensionConfig = { const extensionConfig = {
target: 'node', // VS Code extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ target: 'node',
mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') mode: process.env.NODE_ENV === 'production' ? 'production' : 'none',
entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ entry: './src/extension.ts',
output: { output: {
// the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),
filename: 'extension.js', filename: 'extension.js',
libraryTarget: 'commonjs2' libraryTarget: 'commonjs2',
clean: true // 自动清理旧文件
}, },
externals: { externals: {
vscode: 'commonjs vscode', // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ vscode: 'commonjs vscode',
'node-notifier': 'commonjs node-notifier' // node-notifier 依赖原生模块,必须排除 'node-notifier': 'commonjs node-notifier'
// modules added here also need to be added in the .vscodeignore file
}, },
resolve: { resolve: {
// support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader extensions: ['.ts', '.js'],
extensions: ['.ts', '.js'] mainFields: ['module', 'main']
}, },
module: { module: {
rules: [ rules: [
@ -36,15 +35,21 @@ const extensionConfig = {
exclude: /node_modules/, exclude: /node_modules/,
use: [ use: [
{ {
loader: 'ts-loader' loader: 'ts-loader',
options: {
transpileOnly: true, // 加快编译速度
compilerOptions: {
sourceMap: true
}
}
} }
] ]
} }
] ]
}, },
devtool: 'nosources-source-map', devtool: process.env.NODE_ENV === 'production' ? 'hidden-source-map' : 'nosources-source-map',
infrastructureLogging: { infrastructureLogging: {
level: "log", // enables logging required for problem matchers level: "log",
}, },
plugins: [ plugins: [
new CopyWebpackPlugin({ new CopyWebpackPlugin({
@ -52,6 +57,15 @@ const extensionConfig = {
{ from: 'src/assets', to: 'assets' } { from: 'src/assets', to: 'assets' }
] ]
}) })
] ],
optimization: {
minimize: process.env.NODE_ENV === 'production',
usedExports: true // Tree Shaking
},
performance: {
hints: 'warning',
maxAssetSize: 2 * 1024 * 1024, // 2MB
maxEntrypointSize: 2 * 1024 * 1024
}
}; };
module.exports = [ extensionConfig ]; module.exports = [ extensionConfig ];